feat(@vben/common-ui): 新增工作流管理功能

This commit is contained in:
Kven 2025-06-04 17:18:22 +08:00
parent 38c01e8c73
commit d6bee4b911
8 changed files with 342 additions and 42 deletions

View File

@ -1,6 +1,13 @@
import type { RouteRecordRaw } from 'vue-router'; import type { RouteRecordRaw } from 'vue-router';
import { EosRole, IconLog, IconSystem, MdiUser, RiDept } from '@vben/icons'; import {
EosRole,
FluentWorkflow,
IconLog,
IconSystem,
MdiUser,
RiDept,
} from '@vben/icons';
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
{ {
@ -53,6 +60,16 @@ const routes: RouteRecordRaw[] = [
authority: ['system'], authority: ['system'],
}, },
}, },
{
name: 'workflow',
path: '/system/workflow',
component: () => import('#/views/workflow/list.vue'),
meta: {
icon: FluentWorkflow,
title: '工作流管理',
authority: ['system'],
},
},
], ],
}, },
]; ];

View File

@ -152,10 +152,10 @@ export function useColumns(
code: 'append', code: 'append',
text: '新增下级', text: '新增下级',
}, },
{ // {
code: 'auth', // code: 'auth',
text: '权限', // text: '工作流',
}, // },
'edit', // 默认的编辑按钮 'edit', // 默认的编辑按钮
{ {
code: 'delete', // 默认的删除按钮 code: 'delete', // 默认的删除按钮
@ -175,22 +175,22 @@ export function useColumns(
]; ];
} }
export function useAuthColumns(): VxeTableGridOptions['columns'] { // export function useAuthColumns(): VxeTableGridOptions['columns'] {
return [ // return [
{ // {
field: 'id', // field: 'id',
type: 'checkbox', // type: 'checkbox',
width: 100, // width: 100,
}, // },
{ // {
field: 'name', // field: 'name',
title: '名称', // title: '名称',
width: 200, // width: 200,
}, // },
{ // {
field: 'remark', // field: 'type',
minWidth: 100, // minWidth: 100,
title: '备注', // title: '类型',
}, // },
]; // ];
} // }

View File

@ -16,7 +16,7 @@ import { getAllDeptTree, removeDept } from '#/api';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { useColumns, useGridFormSchema } from './data'; import { useColumns, useGridFormSchema } from './data';
import AuthForm from './modules/auth-form.vue'; // import AuthForm from './modules/auth-form.vue';
import Form from './modules/form.vue'; import Form from './modules/form.vue';
const [FormModal, formModalApi] = useVbenModal({ const [FormModal, formModalApi] = useVbenModal({
@ -24,10 +24,10 @@ const [FormModal, formModalApi] = useVbenModal({
destroyOnClose: true, destroyOnClose: true,
}); });
const [AuthFormModal, authFormModalApi] = useVbenModal({ // const [AuthFormModal, authFormModalApi] = useVbenModal({
connectedComponent: AuthForm, // connectedComponent: AuthForm,
destroyOnClose: true, // destroyOnClose: true,
}); // });
/** /**
* 编辑部门 * 编辑部门
@ -84,10 +84,10 @@ function onActionClick({ code, row }: OnActionClickParams<DeptApi.Dept>) {
onAppend(row); onAppend(row);
break; break;
} }
case 'auth': { // case 'auth': {
authFormModalApi.open(); // authFormModalApi.open();
break; // break;
} // }
case 'delete': { case 'delete': {
onDelete(row); onDelete(row);
break; break;
@ -146,7 +146,7 @@ function refreshGrid() {
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<FormModal @success="refreshGrid" /> <FormModal @success="refreshGrid" />
<AuthFormModal @success="refreshGrid" /> <!-- <AuthFormModal @success="refreshGrid" />-->
<Grid table-title="部门列表"> <Grid table-title="部门列表">
<template #toolbar-tools> <template #toolbar-tools>
<Button <Button

View File

@ -68,22 +68,27 @@ const [Drawer, drawerApi] = useVbenDrawer({
} }
if (menuIds.value.length === 0) { if (menuIds.value.length === 0) {
loadPermissions(); await loadPermissions();
} }
} }
}, },
}); });
function convertToTreeNode(item: any) {
return {
key: item.id,
title: item.name,
pid: item.pid || '',
children: item.children?.map(convertToTreeNode) ?? null,
meta: item.meta,
};
}
async function loadPermissions() { async function loadPermissions() {
loadingPermissions.value = true; loadingPermissions.value = true;
try { try {
const res = await queryMenuList('all'); const res = await queryMenuList('all');
menuIds.value = res.map((item) => ({ menuIds.value = res.map((item) => convertToTreeNode(item));
key: item.id,
title: item.name,
children: item.children || null,
meta: item.meta,
}));
} finally { } finally {
loadingPermissions.value = false; loadingPermissions.value = false;
} }
@ -119,7 +124,7 @@ function getNodeClass(node: Recordable<any>) {
:default-expanded-level="2" :default-expanded-level="2"
:get-node-class="getNodeClass" :get-node-class="getNodeClass"
v-bind="slotProps" v-bind="slotProps"
value-field="id" value-field="key"
label-field="meta.title" label-field="meta.title"
icon-field="meta.icon" icon-field="meta.icon"
> >

View File

@ -0,0 +1,80 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
export function useGridFormSchema(): VbenFormSchema[] {
return [
// {
// component: 'Input',
// fieldName: 'type',
// label: '类型',
// },
{
component: 'Select',
componentProps: {
allowClear: true,
options: [
{ label: 'WORD', value: 1 },
{ label: 'PPT', value: 2 },
{ label: 'SPIDER', value: 3 },
],
},
fieldName: 'type',
label: '类型',
},
];
}
export function useFormSchema(): VbenFormSchema[] {
return [
{
component: 'Input',
fieldName: 'deptIds',
formItemClass: 'items-start',
label: '部门',
modelPropName: 'modelValue',
labelWidth: 40,
},
];
}
export function useColumns(
onActionClick?: OnActionClickFn<any>,
): VxeTableGridOptions['columns'] {
return [
{
field: 'id',
title: 'ID',
// type: 'checkbox',
minWidth: 100,
},
{
field: 'mode',
width: 200,
title: '类型',
},
{
field: 'name',
title: '名称',
minWidth: 100,
},
{
align: 'center',
cellRender: {
name: 'CellOperation',
attrs: {
nameField: 'name',
nameTitle: '工作流',
onClick: onActionClick,
},
options: [
'edit', // 默认的编辑按钮
],
},
field: 'operation',
headerAlign: 'center',
showOverflow: false,
title: '操作',
width: 200,
},
];
}

View File

@ -0,0 +1,102 @@
<script lang="ts" setup>
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { UserApi } from '#/api';
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getAppList } from '#/api';
import { useColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
const [FormDrawer] = useVbenDrawer({
destroyOnClose: true,
});
// const selectedRowIds = ref<number[]>([]);
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
destroyOnClose: true,
});
/**
* 编辑工作流
* @param row
*/
function onEdit(row: any) {
formModalApi.setData(row).open();
}
/**
* 表格操作按钮的回调函数
*/
function onActionClick({ code, row }: OnActionClickParams<any>) {
switch (code) {
case 'edit': {
onEdit(row);
break;
}
}
}
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useColumns(onActionClick),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
const res = await getAppList({
page: page.currentPage,
size: page.pageSize,
current: page.currentPage,
...formValues,
});
return {
items: res,
total: res.length,
};
},
},
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
custom: true,
export: false,
refresh: { code: 'query' },
search: true,
zoom: true,
},
} as VxeTableGridOptions<UserApi.User>,
});
// function onRefresh() {
// gridApi.query();
// }
</script>
<template>
<Page auto-content-height>
<FormDrawer />
<FormModal />
<Grid table-title="工作流列表">
<!-- <template #toolbar-tools>-->
<!-- <Button type="primary" @click="onDelete" danger> 批量删除 </Button>-->
<!-- </template>-->
<!-- <template #makeTime="{ row }">-->
<!-- {{ dayjs(row.makeTime).format('YYYY-MM-DD HH:mm') }}-->
<!-- </template>-->
</Grid>
</Page>
</template>

View File

@ -0,0 +1,95 @@
<script setup lang="ts">
import type { DataNode } from 'ant-design-vue/es/tree';
import type { Recordable } from '@vben/types';
import { ref } from 'vue';
import { useVbenModal, VbenTree } from '@vben/common-ui';
import { useVbenForm } from '#/adapter/form';
import { getAllDeptTree } from '#/api';
import { useFormSchema } from '../data';
const emits = defineEmits(['success']);
const formData = ref<string[]>([]);
const deptData = ref<DataNode[]>([]);
function convertToTreeNode(item: any): DataNode {
return {
key: item.id,
title: item.name,
pid: item.pid || '',
children: item.children?.map(convertToTreeNode) ?? null,
};
}
async function getDeptList() {
const res = await getAllDeptTree(0);
deptData.value = res.map((item) => convertToTreeNode(item));
}
const [Form] = useVbenForm({
schema: useFormSchema(),
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
// const values = await formApi.getValues();
if (!formData.value) return;
modalApi.lock();
try {
emits('success');
modalApi.close();
} finally {
modalApi.lock(false);
}
},
async onOpenChange(isOpen) {
if (isOpen) {
await getDeptList();
}
},
});
function getNodeClass(node: Recordable<any>) {
const classes: string[] = [];
if (node.value?.type === 'button') {
classes.push('inline-flex');
if (node.index % 3 >= 1) {
classes.push('!pl-0');
}
}
return classes.join(' ');
}
</script>
<template>
<Modal title="修改工作流">
<Form class="mx-4">
<template #deptIds="slotProps">
<VbenTree
:tree-data="deptData"
multiple
bordered
:default-expanded-level="2"
:get-node-class="getNodeClass"
v-bind="slotProps"
value-field="key"
label-field="title"
>
<template #node="{ value }">
<!-- <IconifyIcon v-if="value.meta.icon" :icon="value.meta.icon" />-->
{{ value.title }}
</template>
</VbenTree>
</template>
</Form>
</Modal>
</template>
<style scoped></style>

View File

@ -19,4 +19,5 @@ export const IconSystem = createIconifyIcon('icon-park-twotone:system');
export const MaterPerson = createIconifyIcon('material-symbols:person'); export const MaterPerson = createIconifyIcon('material-symbols:person');
export const IconLog = createIconifyIcon('icon-park-outline:log'); export const IconLog = createIconifyIcon('icon-park-outline:log');
export const HugeAi = createIconifyIcon('hugeicons:ai-scan'); export const HugeAi = createIconifyIcon('hugeicons:ai-scan');
export const FluentWorkflow = createIconifyIcon('fluent:cloud-flow-24-regular');
// export const MdiUser = createIconifyIcon('mdi:user'); // export const MdiUser = createIconifyIcon('mdi:user');