feat: 流程表达式 & 菜单图标

This commit is contained in:
dap
2025-07-07 19:11:12 +08:00
parent 991904bd8c
commit 579105bbff
9 changed files with 437 additions and 0 deletions

View File

@@ -242,6 +242,7 @@
"napi",
"nolebase",
"rollup",
"Spel",
"tinymce",
"vitest"
]

View File

@@ -0,0 +1,25 @@
import type { Spel } from './model';
import type { ID, PageQuery, PageResult } from '#/api/common';
import { requestClient } from '#/api/request';
export function spelList(params?: PageQuery) {
return requestClient.get<PageResult<Spel>>('/workflow/spel/list', { params });
}
export function spelInfo(id: ID) {
return requestClient.get<Spel>(`/workflow/spel/${id}`);
}
export function spelAdd(data: Partial<Spel>) {
return requestClient.postWithMsg<Spel>('/workflow/spel', data);
}
export function spelUpdate(data: Partial<Spel>) {
return requestClient.putWithMsg<Spel>('/workflow/spel', data);
}
export function spelDelete(ids: ID[]) {
return requestClient.deleteWithMsg<Spel>(`/workflow/spel/${ids}`);
}

View File

@@ -0,0 +1,10 @@
export interface Spel {
id: number;
componentName: string;
methodName: string;
methodParams: string;
viewSpel: string;
status: string;
remark: string;
createTime: string;
}

View File

@@ -0,0 +1,122 @@
import type { FormSchemaGetter } from '#/adapter/form';
import type { VxeGridProps } from '#/adapter/vxe-table';
import { DictEnum } from '@vben/constants';
import { getDictOptions } from '#/utils/dict';
import { renderDict } from '#/utils/render';
export const querySchema: FormSchemaGetter = () => [
{
component: 'Input',
fieldName: 'componentName',
label: '组件名称',
},
{
component: 'Input',
fieldName: 'methodName',
label: '方法名称',
},
];
export const columns: VxeGridProps['columns'] = [
{ type: 'checkbox', width: 60 },
{
title: '组件名称',
field: 'componentName',
formatter: ({ cellValue }) => cellValue ?? '-',
},
{
title: '方法名称',
field: 'methodName',
formatter: ({ cellValue }) => cellValue ?? '-',
},
{
title: '参数名称',
field: 'methodParams',
},
{
title: 'Spel表达式',
field: 'viewSpel',
},
{
title: '状态',
field: 'status',
width: 120,
slots: {
default: ({ row }) => {
return renderDict(row.status, DictEnum.SYS_NORMAL_DISABLE);
},
},
},
{
title: '备注',
field: 'remark',
},
{
title: '创建时间',
field: 'createTime',
},
{
field: 'action',
fixed: 'right',
slots: { default: 'action' },
title: '操作',
resizable: false,
width: 'auto',
},
];
export const drawerSchema: FormSchemaGetter = () => [
{
component: 'Input',
dependencies: {
show: () => false,
triggerFields: [''],
},
fieldName: 'id',
label: 'id',
},
{
component: 'Input',
fieldName: 'componentName',
label: '组件名称',
// rules: 'required',
},
{
component: 'Input',
fieldName: 'methodName',
label: '方法名称',
// rules: 'required',
},
{
component: 'Input',
fieldName: 'methodParams',
label: '参数名称',
// rules: 'required',
},
{
component: 'Input',
fieldName: 'viewSpel',
label: 'Spel表达式',
// rules: 'required',
},
{
component: 'RadioGroup',
componentProps: {
buttonStyle: 'solid',
options: getDictOptions(DictEnum.SYS_NORMAL_DISABLE),
optionType: 'button',
},
defaultValue: '0',
fieldName: 'status',
label: '状态',
rules: 'required',
},
{
component: 'Textarea',
fieldName: 'remark',
formItemClass: 'items-start',
label: '备注',
},
];

View File

@@ -0,0 +1,147 @@
<script setup lang="ts">
import type { VbenFormProps } from '@vben/common-ui';
import type { VxeGridProps } from '#/adapter/vxe-table';
import type { Spel } from '#/api/workflow/spel/model';
import { Page, useVbenDrawer } from '@vben/common-ui';
import { getVxePopupContainer } from '@vben/utils';
import { Modal, Popconfirm, Space } from 'ant-design-vue';
import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
import { configRemove } from '#/api/system/config';
import { spelList } from '#/api/workflow/spel';
import { columns, querySchema } from './data';
import spelDrawer from './spel-drawer.vue';
const formOptions: VbenFormProps = {
commonConfig: {
labelWidth: 80,
componentProps: {
allowClear: true,
},
},
schema: querySchema(),
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
};
const gridOptions: VxeGridProps = {
checkboxConfig: {
// 高亮
highlight: true,
// 翻页时保留选中状态
reserve: true,
},
columns,
height: 'auto',
keepSource: true,
pagerConfig: {},
proxyConfig: {
ajax: {
query: async ({ page }, formValues = {}) => {
return await spelList({
pageNum: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
id: 'workflow-spel-index',
showOverflow: false,
};
const [BasicTable, tableApi] = useVbenVxeGrid({
formOptions,
gridOptions,
});
const [SpelDrawer, drawerApi] = useVbenDrawer({
connectedComponent: spelDrawer,
});
function handleAdd() {
drawerApi.setData({});
drawerApi.open();
}
async function handleEdit(record: Spel) {
drawerApi.setData({ id: record.id });
drawerApi.open();
}
async function handleDelete(row: Spel) {
await configRemove([row.id]);
await tableApi.query();
}
function handleMultiDelete() {
const rows = tableApi.grid.getCheckboxRecords();
const ids = rows.map((row: Spel) => row.id);
Modal.confirm({
title: '提示',
okType: 'danger',
content: `确认删除选中的${ids.length}条记录吗?`,
onOk: async () => {
await configRemove(ids);
await tableApi.query();
},
});
}
</script>
<template>
<Page :auto-content-height="true">
<BasicTable table-title="流程表达式列表">
<template #toolbar-tools>
<Space>
<a-button
:disabled="!vxeCheckboxChecked(tableApi)"
danger
type="primary"
v-access:code="['system:config:remove']"
@click="handleMultiDelete"
>
{{ $t('pages.common.delete') }}
</a-button>
<a-button
type="primary"
v-access:code="['system:config:add']"
@click="handleAdd"
>
{{ $t('pages.common.add') }}
</a-button>
</Space>
</template>
<template #action="{ row }">
<Space>
<ghost-button
v-access:code="['system:config:edit']"
@click.stop="handleEdit(row)"
>
{{ $t('pages.common.edit') }}
</ghost-button>
<Popconfirm
:get-popup-container="getVxePopupContainer"
placement="left"
title="确认删除?"
@confirm="handleDelete(row)"
>
<ghost-button
danger
v-access:code="['system:config:remove']"
@click.stop=""
>
{{ $t('pages.common.delete') }}
</ghost-button>
</Popconfirm>
</Space>
</template>
</BasicTable>
<SpelDrawer @reload="tableApi.query()" />
</Page>
</template>

View File

@@ -0,0 +1,101 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { cloneDeep } from '@vben/utils';
import { useVbenForm } from '#/adapter/form';
import { spelAdd, spelInfo, spelUpdate } from '#/api/workflow/spel';
import { defaultFormValueGetter, useBeforeCloseDiff } from '#/utils/popup';
import { drawerSchema } from './data';
import SpelPreviewer from './spel-previewer.vue';
const emit = defineEmits<{ reload: [] }>();
const isUpdate = ref(false);
const title = computed(() => {
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
});
const [BasicForm, formApi] = useVbenForm({
commonConfig: {
formItemClass: 'col-span-2',
componentProps: {
class: 'w-full',
},
labelWidth: 80,
},
schema: drawerSchema(),
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
const { onBeforeClose, markInitialized, resetInitialized } = useBeforeCloseDiff(
{
initializedGetter: defaultFormValueGetter(formApi),
currentGetter: defaultFormValueGetter(formApi),
},
);
const [BasicDrawer, drawerApi] = useVbenDrawer({
onBeforeClose,
onClosed: handleClosed,
onConfirm: handleConfirm,
async onOpenChange(isOpen) {
if (!isOpen) {
return null;
}
drawerApi.drawerLoading(true);
const { id } = drawerApi.getData() as { id?: number | string };
isUpdate.value = !!id;
// 更新 && 赋值
if (isUpdate.value && id) {
const record = await spelInfo(id);
await formApi.setValues(record);
}
await markInitialized();
drawerApi.drawerLoading(false);
},
});
async function handleConfirm() {
try {
drawerApi.lock(true);
const { valid } = await formApi.validate();
if (!valid) {
return;
}
const data = cloneDeep(await formApi.getValues());
await (isUpdate.value ? spelUpdate(data) : spelAdd(data));
resetInitialized();
emit('reload');
drawerApi.close();
} catch (error) {
console.error(error);
} finally {
drawerApi.lock(false);
}
}
async function handleClosed() {
await formApi.resetForm();
resetInitialized();
}
</script>
<template>
<BasicDrawer :title="title" class="w-[600px]">
<BasicForm>
<template #viewSpel>
<SpelPreviewer
:component-name="formApi.form.values.componentName"
:method-name="formApi.form.values.methodName"
:method-params="formApi.form.values.methodParams"
/>
</template>
</BasicForm>
</BasicDrawer>
</template>

View File

@@ -0,0 +1,27 @@
<script setup lang="ts">
import { computed } from 'vue';
interface Props {
componentName?: string;
methodName?: string;
methodParams?: string;
}
const props = defineProps<Props>();
const text = computed(() => {
const { componentName, methodName, methodParams } = props;
if (!componentName || !methodName) {
return '-';
}
const params = methodParams ? methodParams.split(',') : [];
const methodParamsText = params.map((item) => `#${item}`).join(',');
return `#{@${componentName}.${methodName}(${methodParamsText})}`;
});
</script>
<template>
<div class="w-full break-all rounded-[4px] bg-[black]/5 p-2">{{ text }}</div>
</template>

View File

@@ -35,6 +35,7 @@ import table from '@iconify/icons-lucide/table';
import cloudDoneOutlineRounded from '@iconify/icons-material-symbols/cloud-done-outline-rounded';
import generatingTokensOutline from '@iconify/icons-material-symbols/generating-tokens-outline';
import LogoDevOutline from '@iconify/icons-material-symbols/logo-dev-outline';
import expressionIcon from '@iconify/icons-material-symbols/regular-expression-rounded';
import ccOutline from '@iconify/icons-mdi/cc-outline';
import tools from '@iconify/icons-mdi/tools';
import workflowOutline from '@iconify/icons-mdi/workflow-outline';
@@ -118,3 +119,5 @@ addIcon('ant-design:setting-outlined', settingOutline);
addIcon('flat-color-icons:leave', leave);
// flow
addIcon('fluent-mdl2:flow', flow);
// 流程表达式
addIcon('material-symbols:regular-expression-rounded', expressionIcon);

View File

@@ -44,6 +44,7 @@ UPDATE sys_menu SET icon = 'material-symbols:cloud-done-outline-rounded' WHERE m
UPDATE sys_menu SET icon = 'mdi:cc-outline' WHERE menu_id = 11633;
UPDATE sys_menu SET icon = 'fluent-mdl2:leave-user' WHERE menu_id = 11638;
UPDATE sys_menu SET icon = 'fluent:form-24-regular' WHERE menu_id = 11628;
UPDATE sys_menu SET icon = 'material-symbols:regular-expression-rounded' WHERE menu_id = 11801;
/* 从本地迁移菜单管理的跳转菜单 */
UPDATE sys_menu SET icon = 'tabler:code' WHERE menu_id = 116;
UPDATE sys_menu SET icon = 'eos-icons:role-binding-outlined' WHERE menu_id = 130;