refactor: bpm

pull/133/MERGE
xingyu4j 2025-06-06 20:45:45 +08:00
parent 7e8f2a1328
commit 2c3dd668e3
47 changed files with 1454 additions and 1898 deletions

View File

@ -1,13 +1,9 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmCategoryApi } from '#/api/bpm/category';
import { useAccess } from '@vben/access';
import { z } from '#/adapter/form'; import { z } from '#/adapter/form';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
return [ return [
@ -106,9 +102,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = BpmCategoryApi.CategoryVO>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -146,29 +140,10 @@ export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 180, width: 180,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
attrs: {
nameField: 'name',
nameTitle: '流程分类',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['bpm:category:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['bpm:category:delete']),
},
],
},
}, },
]; ];
} }

View File

@ -1,16 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmCategoryApi } from '#/api/bpm/category'; import type { BpmCategoryApi } from '#/api/bpm/category';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteCategory, getCategoryPage } from '#/api/bpm/category'; import { deleteCategory, getCategoryPage } from '#/api/bpm/category';
import { DocAlert } from '#/components/doc-alert'; import { DocAlert } from '#/components/doc-alert';
import { $t } from '#/locales'; import { $t } from '#/locales';
@ -22,12 +18,46 @@ const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form, connectedComponent: Form,
destroyOnClose: true, destroyOnClose: true,
}); });
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建流程分类 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑流程分类 */
function handleEdit(row: BpmCategoryApi.CategoryVO) {
formModalApi.setData(row).open();
}
/** 删除流程分类 */
async function handleDelete(row: BpmCategoryApi.CategoryVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.code]),
key: 'action_key_msg',
});
try {
await deleteCategory(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.code]),
key: 'action_key_msg',
});
onRefresh();
} catch {
hideLoading();
}
}
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -50,54 +80,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
} as VxeTableGridOptions<BpmCategoryApi.CategoryVO>, } as VxeTableGridOptions<BpmCategoryApi.CategoryVO>,
}); });
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmCategoryApi.CategoryVO>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建流程分类 */
function onCreate() {
formModalApi.setData(null).open();
}
/** 编辑流程分类 */
function onEdit(row: BpmCategoryApi.CategoryVO) {
formModalApi.setData(row).open();
}
/** 删除流程分类 */
async function onDelete(row: BpmCategoryApi.CategoryVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.code]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteCategory(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.code]));
onRefresh();
} catch {
hideLoading();
}
}
</script> </script>
<template> <template>
@ -109,14 +91,41 @@ async function onDelete(row: BpmCategoryApi.CategoryVO) {
<FormModal @success="onRefresh" /> <FormModal @success="onRefresh" />
<Grid table-title=""> <Grid table-title="">
<template #toolbar-tools> <template #toolbar-tools>
<Button <TableAction
type="primary" :actions="[
@click="onCreate" {
v-access:code="['bpm:category:create']" label: $t('ui.actionTitle.create', ['流程分类']),
> type: 'primary',
<Plus class="size-5" /> icon: ACTION_ICON.ADD,
{{ $t('ui.actionTitle.create', ['流程分类']) }} auth: ['bpm:category:create'],
</Button> onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['bpm:category:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['bpm:category:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template> </template>
</Grid> </Grid>
</Page> </Page>

View File

@ -1,15 +1,9 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmFormApi } from '#/api/bpm/form';
import { useAccess } from '@vben/access';
import { $t } from '@vben/locales';
import { z } from '#/adapter/form'; import { z } from '#/adapter/form';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
return [ return [
@ -68,9 +62,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = BpmFormApi.FormVO>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -103,41 +95,10 @@ export function useGridColumns<T = BpmFormApi.FormVO>(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 200, width: 240,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
attrs: {
nameField: 'name',
nameTitle: '流程名称',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'copy',
text: $t('ui.actionTitle.copy'),
show: hasAccessByCodes(['bpm:form:update']),
},
{
code: 'edit',
text: $t('ui.actionTitle.edit'),
show: hasAccessByCodes(['bpm:form:update']),
},
{
code: 'detail',
text: $t('ui.actionTitle.detail'),
show: hasAccessByCodes(['bpm:form:query']),
},
{
code: 'delete',
text: $t('ui.actionTitle.delete'),
show: hasAccessByCodes(['bpm:form:delete']),
},
],
},
}, },
]; ];
} }

View File

@ -1,36 +1,98 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmFormApi } from '#/api/bpm/form'; import type { BpmFormApi } from '#/api/bpm/form';
import { ref, watch } from 'vue'; import { watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import FormCreate from '@form-create/ant-design-vue'; import { message } from 'ant-design-vue';
import { Button, message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteForm, getFormDetail, getFormPage } from '#/api/bpm/form'; import { deleteForm, getFormPage } from '#/api/bpm/form';
import { DocAlert } from '#/components/doc-alert'; import { DocAlert } from '#/components/doc-alert';
import { router } from '#/router'; import { router } from '#/router';
import { setConfAndFields2 } from '#/utils';
import { useGridColumns, useGridFormSchema } from './data'; import { useGridColumns, useGridFormSchema } from './data';
import Detail from './modules/detail.vue';
defineOptions({ name: 'BpmForm' }); defineOptions({ name: 'BpmForm' });
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 新增 */
function handleCreate() {
router.push({
name: 'BpmFormEditor',
query: {
type: 'create',
},
});
}
/** 编辑 */
function handleEdit(row: BpmFormApi.FormVO) {
router.push({
name: 'BpmFormEditor',
query: {
id: row.id,
type: 'edit',
},
});
}
/** 复制 */
function handleCopy(row: BpmFormApi.FormVO) {
router.push({
name: 'BpmFormEditor',
query: {
copyId: row.id,
type: 'copy',
},
});
}
/** 删除 */
async function handleDelete(row: BpmFormApi.FormVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
});
try {
await deleteForm(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} finally {
hideLoading();
}
}
async function handleDetail(row: BpmFormApi.FormVO) {
detailModalApi.setData(row).open();
}
/** 详情弹窗 */
const [DetailModal, detailModalApi] = useVbenModal({
connectedComponent: Detail,
destroyOnClose: true,
});
/** 检测路由参数 */
const route = useRoute();
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -57,101 +119,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
} as VxeTableGridOptions<BpmFormApi.FormVO>, } as VxeTableGridOptions<BpmFormApi.FormVO>,
}); });
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<BpmFormApi.FormVO>) {
switch (code) {
case 'copy': {
onCopy(row);
break;
}
case 'delete': {
onDelete(row);
break;
}
case 'detail': {
onDetail(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
/** 复制 */
function onCopy(row: BpmFormApi.FormVO) {
router.push({
name: 'BpmFormEditor',
query: {
copyId: row.id,
type: 'copy',
},
});
}
/** 删除 */
async function onDelete(row: BpmFormApi.FormVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteForm(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
onRefresh();
} finally {
hideLoading();
}
}
/** 详情 */
const formConfig = ref<any>({});
async function onDetail(row: BpmFormApi.FormVO) {
formConfig.value = await getFormDetail(row.id as number);
setConfAndFields2(
formConfig.value,
formConfig.value.conf,
formConfig.value.fields,
);
detailModalApi.open();
}
/** 编辑 */
function onEdit(row: BpmFormApi.FormVO) {
router.push({
name: 'BpmFormEditor',
query: {
id: row.id,
type: 'edit',
},
});
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 新增 */
function onCreate() {
router.push({
name: 'BpmFormEditor',
query: {
type: 'create',
},
});
}
/** 详情弹窗 */
const [DetailModal, detailModalApi] = useVbenModal({
destroyOnClose: true,
footer: false,
});
/** 检测路由参数 */
const route = useRoute();
watch( watch(
() => route.query.refresh, () => route.query.refresh,
(val) => { (val) => {
@ -171,25 +138,59 @@ watch(
url="https://doc.iocoder.cn/bpm/use-bpm-form/" url="https://doc.iocoder.cn/bpm/use-bpm-form/"
/> />
</template> </template>
<DetailModal />
<Grid table-title=""> <Grid table-title="">
<template #toolbar-tools> <template #toolbar-tools>
<Button type="primary" @click="onCreate"> <TableAction
<Plus class="size-5" /> :actions="[
{{ $t('ui.actionTitle.create', ['流程表单']) }} {
</Button> label: $t('ui.actionTitle.create', ['流程表单']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['bpm:form:create'],
onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('ui.actionTitle.copy'),
type: 'link',
icon: ACTION_ICON.COPY,
auth: ['bpm:form:update'],
onClick: handleCopy.bind(null, row),
},
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['bpm:form:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:form:query'],
onClick: handleDetail.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['bpm:form:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template> </template>
</Grid> </Grid>
<DetailModal
title="流程表单详情"
class="w-[800px]"
:body-style="{
maxHeight: '100px',
}"
>
<div class="mx-4">
<FormCreate :option="formConfig.option" :rule="formConfig.rule" />
</div>
</DetailModal>
</Page> </Page>
</template> </template>

View File

@ -0,0 +1,50 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import FormCreate from '@form-create/ant-design-vue';
import { getFormDetail } from '#/api/bpm/form';
import { setConfAndFields2 } from '#/utils';
/** 详情 */
const formConfig = ref<any>({});
const [Modal, modalApi] = useVbenModal({
footer: false,
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
return;
}
//
const data = modalApi.getData();
if (!data || !data.id) {
return;
}
modalApi.lock();
try {
formConfig.value = await getFormDetail(data.id as number);
setConfAndFields2(
formConfig.value,
formConfig.value.conf,
formConfig.value.fields,
);
} finally {
modalApi.unlock();
}
},
});
</script>
<template>
<Modal
class="w-[600px]"
title="流程表单详情"
:body-style="{
maxHeight: '100px',
}"
>
<FormCreate :option="formConfig.option" :rule="formConfig.rule" />
</Modal>
</template>

View File

@ -106,7 +106,7 @@ const [Modal, modalApi] = useVbenModal({
</script> </script>
<template> <template>
<Modal :title="getTitle" class="w-[600px]"> <Modal :title="getTitle" class="w-[40%]">
<Form class="mx-4" /> <Form class="mx-4" />
</Modal> </Modal>
</template> </template>

View File

@ -1,14 +1,10 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmCategoryApi } from '#/api/bpm/category';
import { useAccess } from '@vben/access';
import { z } from '#/adapter/form'; import { z } from '#/adapter/form';
import { getSimpleUserList } from '#/api/system/user'; import { getSimpleUserList } from '#/api/system/user';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
return [ return [
@ -99,10 +95,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = BpmCategoryApi.CategoryVO>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
getMemberNames: (userIds: number[]) => string,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -123,9 +116,7 @@ export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
field: 'userIds', field: 'userIds',
title: '成员', title: '成员',
minWidth: 200, minWidth: 200,
formatter: ({ cellValue }) => { slots: { default: 'userIds' },
return getMemberNames(cellValue);
},
}, },
{ {
field: 'status', field: 'status',
@ -143,29 +134,10 @@ export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 180, width: 180,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
attrs: {
nameField: 'name',
nameTitle: '用户分组',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['bpm:user-group:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['bpm:user-group:delete']),
},
],
},
}, },
]; ];
} }

View File

@ -1,19 +1,15 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmUserGroupApi } from '#/api/bpm/userGroup'; import type { BpmUserGroupApi } from '#/api/bpm/userGroup';
import type { SystemUserApi } from '#/api/system/user'; import type { SystemUserApi } from '#/api/system/user';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue'; import { message, Tag } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteUserGroup, getUserGroupPage } from '#/api/bpm/userGroup'; import { deleteUserGroup, getUserGroupPage } from '#/api/bpm/userGroup';
import { getSimpleUserList } from '#/api/system/user'; import { getSimpleUserList } from '#/api/system/user';
import { DocAlert } from '#/components/doc-alert'; import { DocAlert } from '#/components/doc-alert';
@ -26,12 +22,53 @@ const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form, connectedComponent: Form,
destroyOnClose: true, destroyOnClose: true,
}); });
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建用户分组 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑用户分组 */
function handleEdit(row: BpmUserGroupApi.UserGroupVO) {
formModalApi.setData(row).open();
}
/** 删除用户分组 */
async function handleDelete(row: BpmUserGroupApi.UserGroupVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
});
try {
await deleteUserGroup(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} catch {
hideLoading();
}
}
const userList = ref<SystemUserApi.User[]>([]);
/** 初始化 */
onMounted(async () => {
//
userList.value = await getSimpleUserList();
});
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick, getMemberNames), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -54,73 +91,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
} as VxeTableGridOptions<BpmUserGroupApi.UserGroupVO>, } as VxeTableGridOptions<BpmUserGroupApi.UserGroupVO>,
}); });
/** 获取分组成员姓名 */
function getMemberNames(userIds: number[]) {
const userMap = new Map(
userList.value.map((user) => [user.id, user.nickname]),
);
return userIds
.map((userId) => userMap.get(userId))
.filter(Boolean)
.join('、');
}
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmUserGroupApi.UserGroupVO>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建用户分组 */
function onCreate() {
formModalApi.setData(null).open();
}
/** 编辑用户分组 */
function onEdit(row: BpmUserGroupApi.UserGroupVO) {
formModalApi.setData(row).open();
}
/** 删除用户分组 */
async function onDelete(row: BpmUserGroupApi.UserGroupVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteUserGroup(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
onRefresh();
} catch {
hideLoading();
}
}
//
const userList = ref<SystemUserApi.User[]>([]);
/** 初始化 */
onMounted(async () => {
//
userList.value = await getSimpleUserList();
});
</script> </script>
<template> <template>
@ -132,18 +102,46 @@ onMounted(async () => {
<FormModal @success="onRefresh" /> <FormModal @success="onRefresh" />
<Grid table-title=""> <Grid table-title="">
<template #toolbar-tools> <template #toolbar-tools>
<Button <TableAction
type="primary" :actions="[
@click="onCreate" {
v-access:code="['bpm:category:create']" label: $t('ui.actionTitle.create', ['用户分组']),
> type: 'primary',
<Plus class="size-5" /> icon: ACTION_ICON.ADD,
{{ $t('ui.actionTitle.create', ['用户分组']) }} auth: ['bpm:user-group:create'],
</Button> onClick: handleCreate,
},
]"
/>
</template> </template>
<template #userIds="{ row }">
<template #userIds-cell="{ row }"> <Tag v-for="userId in row.userIds" :key="userId" color="blue">
<span>{{ row.nicknames }}</span> {{ userList.find((u) => u.id === userId)?.nickname }}
</Tag>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['bpm:user-group:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['bpm:user-group:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template> </template>
</Grid> </Grid>
</Page> </Page>

View File

@ -64,7 +64,6 @@ const [Modal, modalApi] = useVbenModal({
}, },
async onOpenChange(isOpen: boolean) { async onOpenChange(isOpen: boolean) {
if (!isOpen) { if (!isOpen) {
formData.value = undefined;
return; return;
} }
// //
@ -85,7 +84,7 @@ const [Modal, modalApi] = useVbenModal({
</script> </script>
<template> <template>
<Modal :title="getTitle"> <Modal :title="getTitle" class="w-[40%]">
<Form class="mx-4" /> <Form class="mx-4" />
</Modal> </Modal>
</template> </template>

View File

@ -60,24 +60,24 @@ const processDesignRef = ref<InstanceType<typeof ProcessDesign>>();
const extraSettingRef = ref<InstanceType<typeof ExtraSetting>>(); const extraSettingRef = ref<InstanceType<typeof ExtraSetting>>();
/** 步骤校验函数 */ /** 步骤校验函数 */
const validateBasic = async () => { async function validateBasic() {
await basicInfoRef.value?.validate(); await basicInfoRef.value?.validate();
}; }
/** 表单设计校验 */ /** 表单设计校验 */
const validateForm = async () => { async function validateForm() {
await formDesignRef.value?.validate(); await formDesignRef.value?.validate();
}; }
/** 流程设计校验 */ /** 流程设计校验 */
const validateProcess = async () => { async function validateProcess() {
await processDesignRef.value?.validate(); await processDesignRef.value?.validate();
}; }
/** 更多设置校验 */ /** 更多设置校验 */
const validateExtra = async () => { async function validateExtra() {
await extraSettingRef.value?.validate(); await extraSettingRef.value?.validate();
}; }
const currentStep = ref(-1); // -1 const currentStep = ref(-1); // -1
@ -139,7 +139,7 @@ const deptList = ref<SystemDeptApi.Dept[]>([]);
/** 初始化数据 */ /** 初始化数据 */
const actionType = route.params.type as string; const actionType = route.params.type as string;
const initData = async () => { async function initData() {
if (actionType === 'definition') { if (actionType === 'definition') {
// //
const definitionId = route.params.id as string; const definitionId = route.params.id as string;
@ -200,7 +200,7 @@ const initData = async () => {
// //
extraSettingRef.value?.initData(); extraSettingRef.value?.initData();
}; }
/** 根据类型切换流程数据 */ /** 根据类型切换流程数据 */
watch( watch(
@ -218,7 +218,7 @@ watch(
); );
/** 校验所有步骤数据是否完整 */ /** 校验所有步骤数据是否完整 */
const validateAllSteps = async () => { async function validateAllSteps() {
// //
try { try {
await validateBasic(); await validateBasic();
@ -254,10 +254,10 @@ const validateAllSteps = async () => {
} }
return true; return true;
}; }
/** 保存操作 */ /** 保存操作 */
const handleSave = async () => { async function handleSave() {
try { try {
// //
const result = await validateAllSteps(); const result = await validateAllSteps();
@ -311,10 +311,10 @@ const handleSave = async () => {
console.error('保存失败:', error); console.error('保存失败:', error);
// message.warning(error.msg || ''); // message.warning(error.msg || '');
} }
}; }
/** 发布操作 */ /** 发布操作 */
const handleDeploy = async () => { async function handleDeploy() {
try { try {
// //
if (!formData.value.id) { if (!formData.value.id) {
@ -345,10 +345,10 @@ const handleDeploy = async () => {
console.error('发布失败:', error); console.error('发布失败:', error);
message.warning(error.message || '发布失败'); message.warning(error.message || '发布失败');
} }
}; }
/** 步骤切换处理 */ /** 步骤切换处理 */
const handleStepClick = async (index: number) => { async function handleStepClick(index: number) {
try { try {
if (index !== 0) { if (index !== 0) {
await validateBasic(); await validateBasic();
@ -370,17 +370,17 @@ const handleStepClick = async (index: number) => {
message.warning('请先完善当前步骤必填信息'); message.warning('请先完善当前步骤必填信息');
} }
} }
}; }
const tabs = useTabs(); const tabs = useTabs();
/** 返回列表页 */ /** 返回列表页 */
const handleBack = () => { function handleBack() {
// //
tabs.closeCurrentTab(); tabs.closeCurrentTab();
// 使 name 'name'+ menuId // 使 name 'name'+ menuId
router.push({ path: '/bpm/manager/model' }); router.push({ path: '/bpm/manager/model' });
}; }
/** 初始化 */ /** 初始化 */
onMounted(async () => { onMounted(async () => {
@ -398,11 +398,9 @@ onBeforeUnmount(() => {
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<div class="mx-auto"> <!-- 主体内容 -->
<!-- 头部导航栏 --> <Card class="mb-4">
<div <template #title>
class="absolute inset-x-0 top-0 z-10 flex h-12 items-center border-b bg-white px-5"
>
<!-- 左侧标题 --> <!-- 左侧标题 -->
<div class="flex w-[200px] items-center overflow-hidden"> <div class="flex w-[200px] items-center overflow-hidden">
<ArrowLeft <ArrowLeft
@ -416,88 +414,82 @@ onBeforeUnmount(() => {
{{ formData.name || '创建流程' }} {{ formData.name || '创建流程' }}
</span> </span>
</div> </div>
</template>
<!-- 步骤条 --> <template #extra>
<div class="flex h-full flex-1 items-center justify-center"> <Button
<div class="flex h-full w-[400px] items-center justify-between"> v-if="actionType === 'update'"
type="primary"
@click="handleDeploy"
>
</Button>
<Button type="primary" @click="handleSave">
<span v-if="actionType === 'definition'"> </span>
<span v-else> </span>
</Button>
</template>
<!-- 步骤条 -->
<div class="flex h-full flex-1 items-center justify-center">
<div class="flex h-full w-[400px] items-center justify-between">
<div
v-for="(step, index) in steps"
:key="index"
class="relative mx-[15px] flex h-full cursor-pointer items-center"
:class="[
currentStep === index
? 'border-b-2 border-solid border-blue-500 text-blue-500'
: 'text-gray-500',
]"
@click="handleStepClick(index)"
>
<div <div
v-for="(step, index) in steps" class="mr-2 flex h-7 w-7 items-center justify-center rounded-full border-2 border-solid text-[15px]"
:key="index"
class="relative mx-[15px] flex h-full cursor-pointer items-center"
:class="[ :class="[
currentStep === index currentStep === index
? 'border-b-2 border-solid border-blue-500 text-blue-500' ? 'border-blue-500 bg-blue-500 text-white'
: 'text-gray-500', : 'border-gray-300 bg-white text-gray-500',
]" ]"
@click="handleStepClick(index)"
> >
<div {{ index + 1 }}
class="mr-2 flex h-7 w-7 items-center justify-center rounded-full border-2 border-solid text-[15px]"
:class="[
currentStep === index
? 'border-blue-500 bg-blue-500 text-white'
: 'border-gray-300 bg-white text-gray-500',
]"
>
{{ index + 1 }}
</div>
<span class="whitespace-nowrap text-base font-bold">{{
step.title
}}</span>
</div> </div>
<span class="whitespace-nowrap text-base font-bold">{{
step.title
}}</span>
</div> </div>
</div> </div>
<!-- 右侧按钮 -->
<div class="flex w-[200px] items-center justify-end gap-2">
<Button
v-if="actionType === 'update'"
type="primary"
@click="handleDeploy"
>
</Button>
<Button type="primary" @click="handleSave">
<span v-if="actionType === 'definition'"> </span>
<span v-else> </span>
</Button>
</div>
</div> </div>
<!-- 主体内容 --> <div class="mt-[50px]">
<Card :body-style="{ padding: '10px' }" class="mb-4"> <!-- 第一步基本信息 -->
<div class="mt-[50px]"> <div v-if="currentStep === 0" class="mx-auto w-4/6">
<!-- 第一步基本信息 --> <BasicInfo
<div v-if="currentStep === 0" class="mx-auto w-4/6">
<BasicInfo
v-model="formData"
:category-list="categoryList"
:user-list="userList"
:dept-list="deptList"
ref="basicInfoRef"
/>
</div>
<!-- 第二步表单设计 -->
<div v-if="currentStep === 1" class="mx-auto w-4/6">
<FormDesign
v-model="formData"
:form-list="formList"
ref="formDesignRef"
/>
</div>
<!-- 第三步流程设计 -->
<ProcessDesign
v-if="currentStep === 2"
v-model="formData" v-model="formData"
ref="processDesignRef" :category-list="categoryList"
:user-list="userList"
:dept-list="deptList"
ref="basicInfoRef"
/> />
<!-- 第四步更多设置 -->
<div v-if="currentStep === 3" class="mx-auto w-4/6">
<ExtraSetting v-model="formData" ref="extraSettingRef" />
</div>
</div> </div>
</Card> <!-- 第二步表单设计 -->
</div> <div v-if="currentStep === 1" class="mx-auto w-4/6">
<FormDesign
v-model="formData"
:form-list="formList"
ref="formDesignRef"
/>
</div>
<!-- 第三步流程设计 -->
<ProcessDesign
v-if="currentStep === 2"
v-model="formData"
ref="processDesignRef"
/>
<!-- 第四步更多设置 -->
<div v-if="currentStep === 3" class="mx-auto w-4/6">
<ExtraSetting v-model="formData" ref="extraSettingRef" />
</div>
</div>
</Card>
</Page> </Page>
</template> </template>

View File

@ -10,6 +10,7 @@ import type { SystemUserApi } from '#/api/system/user';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { CircleHelp, IconifyIcon, Plus, X } from '@vben/icons'; import { CircleHelp, IconifyIcon, Plus, X } from '@vben/icons';
import { import {
@ -41,6 +42,16 @@ const props = defineProps({
}, },
}); });
const [UserSelectModalComp, userSelectModalApi] = useVbenModal({
connectedComponent: UserSelectModal,
destroyOnClose: true,
});
const [DeptSelectModalComp, deptSelectModalApi] = useVbenModal({
connectedComponent: DeptSelectModal,
destroyOnClose: true,
});
// //
const formRef = ref(); const formRef = ref();
@ -52,8 +63,6 @@ const selectedStartDepts = ref<SystemDeptApi.Dept[]>([]);
// //
const selectedManagerUsers = ref<SystemUserApi.User[]>([]); const selectedManagerUsers = ref<SystemUserApi.User[]>([]);
const userSelectFormRef = ref();
const deptSelectFormRef = ref();
const currentSelectType = ref<'manager' | 'start'>('start'); const currentSelectType = ref<'manager' | 'start'>('start');
// //
const selectedUsers = ref<number[]>(); const selectedUsers = ref<number[]>();
@ -98,37 +107,37 @@ watch(
); );
/** 打开发起人选择 */ /** 打开发起人选择 */
const openStartUserSelect = () => { function openStartUserSelect() {
currentSelectType.value = 'start'; currentSelectType.value = 'start';
selectedUsers.value = selectedStartUsers.value.map( selectedUsers.value = selectedStartUsers.value.map(
(user) => user.id, (user) => user.id,
) as number[]; ) as number[];
userSelectFormRef.value.open(selectedUsers.value); userSelectModalApi.setData({ userIds: selectedUsers.value }).open();
}; }
/** 打开部门选择 */ /** 打开部门选择 */
const openStartDeptSelect = () => { function openStartDeptSelect() {
deptSelectFormRef.value.open(selectedStartDepts.value); deptSelectModalApi.setData({ selectedList: selectedStartDepts.value }).open();
}; }
/** 处理部门选择确认 */ /** 处理部门选择确认 */
const handleDeptSelectConfirm = (depts: SystemDeptApi.Dept[]) => { function handleDeptSelectConfirm(depts: SystemDeptApi.Dept[]) {
modelData.value = { modelData.value = {
...modelData.value, ...modelData.value,
startDeptIds: depts.map((d) => d.id), startDeptIds: depts.map((d) => d.id),
}; };
}; }
/** 打开管理员选择 */ /** 打开管理员选择 */
const openManagerUserSelect = () => { function openManagerUserSelect() {
currentSelectType.value = 'manager'; currentSelectType.value = 'manager';
selectedUsers.value = selectedManagerUsers.value.map( selectedUsers.value = selectedManagerUsers.value.map(
(user) => user.id, (user) => user.id,
) as number[]; ) as number[];
userSelectFormRef.value.open(selectedUsers.value); userSelectModalApi.setData({ userIds: selectedUsers.value }).open();
}; }
/** 处理用户选择确认 */ /** 处理用户选择确认 */
const handleUserSelectConfirm = (userList: SystemUserApi.User[]) => { function handleUserSelectConfirm(userList: SystemUserApi.User[]) {
modelData.value = modelData.value =
currentSelectType.value === 'start' currentSelectType.value === 'start'
? { ? {
@ -139,20 +148,20 @@ const handleUserSelectConfirm = (userList: SystemUserApi.User[]) => {
...modelData.value, ...modelData.value,
managerUserIds: userList.map((u) => u.id), managerUserIds: userList.map((u) => u.id),
}; };
}; }
/** 用户选择弹窗关闭 */ /** 用户选择弹窗关闭 */
const handleUserSelectClosed = () => { function handleUserSelectClosed() {
selectedUsers.value = []; selectedUsers.value = [];
}; }
/** 用户选择弹窗取消 */ /** 用户选择弹窗取消 */
const handleUserSelectCancel = () => { function handleUserSelectCancel() {
selectedUsers.value = []; selectedUsers.value = [];
}; }
/** 处理发起人类型变化 */ /** 处理发起人类型变化 */
const handleStartUserTypeChange = (value: SelectValue) => { function handleStartUserTypeChange(value: SelectValue) {
const numValue = Number(value); const numValue = Number(value);
switch (numValue) { switch (numValue) {
case 0: { case 0: {
@ -181,270 +190,266 @@ const handleStartUserTypeChange = (value: SelectValue) => {
break; break;
} }
} }
}; }
/** 移除发起人 */ /** 移除发起人 */
const handleRemoveStartUser = (user: SystemUserApi.User) => { function handleRemoveStartUser(user: SystemUserApi.User) {
modelData.value = { modelData.value = {
...modelData.value, ...modelData.value,
startUserIds: modelData.value.startUserIds.filter( startUserIds: modelData.value.startUserIds.filter(
(id: number) => id !== user.id, (id: number) => id !== user.id,
), ),
}; };
}; }
/** 移除部门 */ /** 移除部门 */
const handleRemoveStartDept = (dept: SystemDeptApi.Dept) => { function handleRemoveStartDept(dept: SystemDeptApi.Dept) {
modelData.value = { modelData.value = {
...modelData.value, ...modelData.value,
startDeptIds: modelData.value.startDeptIds.filter( startDeptIds: modelData.value.startDeptIds.filter(
(id: number) => id !== dept.id, (id: number) => id !== dept.id,
), ),
}; };
}; }
/** 移除管理员 */ /** 移除管理员 */
const handleRemoveManagerUser = (user: SystemUserApi.User) => { function handleRemoveManagerUser(user: SystemUserApi.User) {
modelData.value = { modelData.value = {
...modelData.value, ...modelData.value,
managerUserIds: modelData.value.managerUserIds.filter( managerUserIds: modelData.value.managerUserIds.filter(
(id: number) => id !== user.id, (id: number) => id !== user.id,
), ),
}; };
}; }
/** 表单校验 */ /** 表单校验 */
const validate = async () => { async function validate() {
await formRef.value?.validate(); await formRef.value?.validate();
}; }
defineExpose({ validate }); defineExpose({ validate });
</script> </script>
<template> <template>
<Form <div>
ref="formRef" <Form
:model="modelData" ref="formRef"
:rules="rules" :model="modelData"
:label-col="{ span: 4 }" :rules="rules"
:wrapper-col="{ span: 20 }" :label-col="{ span: 4 }"
class="mt-5" :wrapper-col="{ span: 20 }"
> class="mt-5"
<Form.Item label="流程标识" name="key" class="mb-5"> >
<div class="flex items-center"> <Form.Item label="流程标识" name="key" class="mb-5">
<div class="flex items-center">
<Input
class="w-full"
v-model:value="modelData.key"
:disabled="!!modelData.id"
placeholder="请输入流程标识,以字母或下划线开头"
/>
<Tooltip
:title="
modelData.id ? '流程标识不可修改!' : '新建后,流程标识不可修改!'
"
placement="top"
>
<CircleHelp class="ml-1 size-5 text-gray-900" />
</Tooltip>
</div>
</Form.Item>
<Form.Item label="流程名称" name="name" class="mb-5">
<Input <Input
class="w-full" v-model:value="modelData.name"
v-model:value="modelData.key"
:disabled="!!modelData.id" :disabled="!!modelData.id"
placeholder="请输入流程标识,以字母或下划线开头" allow-clear
placeholder="请输入流程名称"
/> />
<Tooltip </Form.Item>
:title=" <Form.Item label="流程分类" name="category" class="mb-5">
modelData.id ? '流程标识不可修改!' : '新建后,流程标识不可修改!' <Select
" class="w-full"
placement="top" v-model:value="modelData.category"
allow-clear
placeholder="请选择流程分类"
> >
<CircleHelp class="ml-1 size-5 text-gray-900" /> <Select.Option
</Tooltip> v-for="category in categoryList"
</div> :key="category.code"
</Form.Item> :value="category.code"
<Form.Item label="流程名称" name="name" class="mb-5"> >
<Input {{ category.name }}
v-model:value="modelData.name" </Select.Option>
:disabled="!!modelData.id" </Select>
allow-clear </Form.Item>
placeholder="请输入流程名称" <Form.Item label="流程图标" class="mb-5">
/> <ImageUpload v-model:value="modelData.icon" />
</Form.Item> </Form.Item>
<Form.Item label="流程分类" name="category" class="mb-5"> <Form.Item label="流程描述" name="description" class="mb-5">
<Select <Input.TextArea v-model:value="modelData.description" allow-clear />
class="w-full" </Form.Item>
v-model:value="modelData.category" <Form.Item label="流程类型" name="type" class="mb-5">
allow-clear <Radio.Group v-model:value="modelData.type">
placeholder="请选择流程分类" <!-- TODO BPMN 流程类型需要整合暂时禁用 -->
> <Radio
<Select.Option v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
v-for="category in categoryList" :key="dict.value"
:key="category.code" :value="dict.value"
:value="category.code" :disabled="dict.value === 10"
>
{{ dict.label }}
</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="是否可见" name="visible" class="mb-5">
<Radio.Group v-model:value="modelData.visible">
<Radio
v-for="(dict, index) in getBoolDictOptions(
DICT_TYPE.INFRA_BOOLEAN_STRING,
)"
:key="index"
:value="dict.value"
>
{{ dict.label }}
</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="谁可以发起" name="startUserType" class="mb-5">
<Select
v-model:value="modelData.startUserType"
placeholder="请选择谁可以发起"
@change="handleStartUserTypeChange"
> >
{{ category.name }} <Select.Option :value="0">全员</Select.Option>
</Select.Option> <Select.Option :value="1">指定人员</Select.Option>
</Select> <Select.Option :value="2">指定部门</Select.Option>
</Form.Item> </Select>
<Form.Item label="流程图标" class="mb-5">
<ImageUpload v-model:value="modelData.icon" />
</Form.Item>
<Form.Item label="流程描述" name="description" class="mb-5">
<Input.TextArea v-model:value="modelData.description" allow-clear />
</Form.Item>
<Form.Item label="流程类型" name="type" class="mb-5">
<Radio.Group v-model:value="modelData.type">
<!-- TODO BPMN 流程类型需要整合暂时禁用 -->
<Radio
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
:key="dict.value"
:value="dict.value"
:disabled="dict.value === 10"
>
{{ dict.label }}
</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="是否可见" name="visible" class="mb-5">
<Radio.Group v-model:value="modelData.visible">
<Radio
v-for="(dict, index) in getBoolDictOptions(
DICT_TYPE.INFRA_BOOLEAN_STRING,
)"
:key="index"
:value="dict.value"
>
{{ dict.label }}
</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="谁可以发起" name="startUserType" class="mb-5">
<Select
v-model:value="modelData.startUserType"
placeholder="请选择谁可以发起"
@change="handleStartUserTypeChange"
>
<Select.Option :value="0">全员</Select.Option>
<Select.Option :value="1">指定人员</Select.Option>
<Select.Option :value="2">指定部门</Select.Option>
</Select>
<div
v-if="modelData.startUserType === 1"
class="mt-2 flex flex-wrap gap-2"
>
<div <div
v-for="user in selectedStartUsers" v-if="modelData.startUserType === 1"
:key="user.id" class="mt-2 flex flex-wrap gap-2"
class="relative flex h-9 items-center rounded-full bg-gray-100 pr-2"
> >
<Avatar <div
class="m-1" v-for="user in selectedStartUsers"
:size="28" :key="user.id"
v-if="user.avatar" class="bg-destructive text-destructive-foreground hover:bg-destructive-hover relative flex h-9 items-center rounded-full pr-2"
:src="user.avatar" >
/> <Avatar
<Avatar class="m-1" :size="28" v-else> class="m-1"
{{ user.nickname?.substring(0, 1) }} :size="28"
</Avatar> v-if="user.avatar"
{{ user.nickname }} :src="user.avatar"
<X />
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500" <Avatar class="m-1" :size="28" v-else>
@click="handleRemoveStartUser(user)" {{ user.nickname?.substring(0, 1) }}
/> </Avatar>
{{ user.nickname }}
<X
class="ml-2 size-4 cursor-pointer"
@click="handleRemoveStartUser(user)"
/>
</div>
<Button
type="link"
@click="openStartUserSelect"
class="flex items-center"
>
<template #icon>
<IconifyIcon
icon="mdi:account-plus-outline"
class="size-[18px]"
/>
</template>
选择人员
</Button>
</div> </div>
<Button
type="link"
@click="openStartUserSelect"
class="flex items-center"
>
<template #icon>
<IconifyIcon icon="mdi:account-plus-outline" class="size-[18px]" />
</template>
选择人员
</Button>
</div>
<div
v-if="modelData.startUserType === 2"
class="mt-2 flex flex-wrap gap-2"
>
<div <div
v-for="dept in selectedStartDepts" v-if="modelData.startUserType === 2"
:key="dept.id" class="mt-2 flex flex-wrap gap-2"
class="relative flex h-9 items-center rounded-full bg-gray-100 pr-2"
> >
<IconifyIcon icon="ep:office-building" class="size-6 px-1" /> <div
{{ dept.name }} v-for="dept in selectedStartDepts"
<X :key="dept.id"
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500" class="bg-destructive text-destructive-foreground hover:bg-destructive-hover relative flex h-9 items-center rounded-full pr-2 shadow-sm"
@click="handleRemoveStartDept(dept)" >
/> <IconifyIcon icon="ep:office-building" class="size-6 px-1" />
{{ dept.name }}
<X
class="ml-2 size-4 cursor-pointer"
@click="handleRemoveStartDept(dept)"
/>
</div>
<Button
type="link"
@click="openStartDeptSelect"
class="flex items-center"
>
<template #icon>
<Plus class="size-[18px]" />
</template>
选择部门
</Button>
</div> </div>
<Button </Form.Item>
type="link" <Form.Item label="流程管理员" name="managerUserIds" class="mb-5">
@click="openStartDeptSelect" <div class="flex flex-wrap gap-2">
class="flex items-center" <div
> v-for="user in selectedManagerUsers"
<template #icon> :key="user.id"
<Plus class="size-[18px]" /> class="bg-destructive text-destructive-foreground hover:bg-destructive-hover relative flex h-9 items-center rounded-full pr-2"
</template> >
选择部门 <Avatar
</Button> class="m-1"
</div> :size="28"
</Form.Item> v-if="user.avatar"
<Form.Item label="流程管理员" name="managerUserIds" class="mb-5"> :src="user.avatar"
<div class="flex flex-wrap gap-2"> />
<div <Avatar class="m-1" :size="28" v-else>
v-for="user in selectedManagerUsers" {{ user.nickname?.substring(0, 1) }}
:key="user.id" </Avatar>
class="relative flex h-9 items-center rounded-full bg-gray-100 pr-2" {{ user.nickname }}
> <X
<Avatar class="ml-2 size-4 cursor-pointer"
class="m-1" @click="handleRemoveManagerUser(user)"
:size="28" />
v-if="user.avatar" </div>
:src="user.avatar" <Button
/> type="link"
<Avatar class="m-1" :size="28" v-else> @click="openManagerUserSelect"
{{ user.nickname?.substring(0, 1) }} class="flex items-center"
</Avatar> >
{{ user.nickname }} <template #icon>
<X <IconifyIcon
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500" icon="mdi:account-plus-outline"
@click="handleRemoveManagerUser(user)" class="size-[18px]"
/> />
</template>
选择人员
</Button>
</div> </div>
<Button </Form.Item>
type="link" </Form>
@click="openManagerUserSelect"
class="flex items-center"
>
<template #icon>
<IconifyIcon icon="mdi:account-plus-outline" class="size-[18px]" />
</template>
选择人员
</Button>
</div>
</Form.Item>
</Form>
<!-- 用户选择弹窗 --> <!-- 用户选择弹窗 -->
<UserSelectModal <UserSelectModalComp
ref="userSelectFormRef" v-model:value="selectedUsers"
v-model:value="selectedUsers" :multiple="true"
:multiple="true" title="选择用户"
title="选择用户" @confirm="handleUserSelectConfirm"
@confirm="handleUserSelectConfirm" @closed="handleUserSelectClosed"
@closed="handleUserSelectClosed" @cancel="handleUserSelectCancel"
@cancel="handleUserSelectCancel" />
/> <!-- 部门选择对话框 -->
<!-- 部门选择对话框 --> <DeptSelectModalComp
<DeptSelectModal title="发起人部门选择"
ref="deptSelectFormRef" :check-strictly="true"
title="发起人部门选择" @confirm="handleDeptSelectConfirm"
:check-strictly="true" />
@confirm="handleDeptSelectConfirm" </div>
/>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.bg-gray-100 {
background-color: #f5f7fa;
transition: all 0.3s;
&:hover {
background-color: #e6e8eb;
}
}
.upload-img-placeholder { .upload-img-placeholder {
cursor: pointer; cursor: pointer;
background-color: #fafafa;
transition: all 0.3s; transition: all 0.3s;
&:hover { &:hover {

View File

@ -91,9 +91,9 @@ const numberExample = computed(() => {
/** 是否开启流程前置通知 */ /** 是否开启流程前置通知 */
const processBeforeTriggerEnable = ref(false); const processBeforeTriggerEnable = ref(false);
const handleProcessBeforeTriggerEnableChange = ( function handleProcessBeforeTriggerEnableChange(
val: boolean | number | string, val: boolean | number | string,
) => { ) {
modelData.value.processBeforeTriggerSetting = val modelData.value.processBeforeTriggerSetting = val
? { ? {
url: '', url: '',
@ -102,13 +102,11 @@ const handleProcessBeforeTriggerEnableChange = (
response: [], response: [],
} }
: null; : null;
}; }
/** 是否开启流程后置通知 */ /** 是否开启流程后置通知 */
const processAfterTriggerEnable = ref(false); const processAfterTriggerEnable = ref(false);
const handleProcessAfterTriggerEnableChange = ( function handleProcessAfterTriggerEnableChange(val: boolean | number | string) {
val: boolean | number | string,
) => {
modelData.value.processAfterTriggerSetting = val modelData.value.processAfterTriggerSetting = val
? { ? {
url: '', url: '',
@ -117,13 +115,11 @@ const handleProcessAfterTriggerEnableChange = (
response: [], response: [],
} }
: null; : null;
}; }
/** 是否开启任务前置通知 */ /** 是否开启任务前置通知 */
const taskBeforeTriggerEnable = ref(false); const taskBeforeTriggerEnable = ref(false);
const handleTaskBeforeTriggerEnableChange = ( function handleTaskBeforeTriggerEnableChange(val: boolean | number | string) {
val: boolean | number | string,
) => {
modelData.value.taskBeforeTriggerSetting = val modelData.value.taskBeforeTriggerSetting = val
? { ? {
url: '', url: '',
@ -132,11 +128,11 @@ const handleTaskBeforeTriggerEnableChange = (
response: [], response: [],
} }
: null; : null;
}; }
/** 是否开启任务后置通知 */ /** 是否开启任务后置通知 */
const taskAfterTriggerEnable = ref(false); const taskAfterTriggerEnable = ref(false);
const handleTaskAfterTriggerEnableChange = (val: boolean | number | string) => { function handleTaskAfterTriggerEnableChange(val: boolean | number | string) {
modelData.value.taskAfterTriggerSetting = val modelData.value.taskAfterTriggerSetting = val
? { ? {
url: '', url: '',
@ -145,7 +141,7 @@ const handleTaskAfterTriggerEnableChange = (val: boolean | number | string) => {
response: [], response: [],
} }
: null; : null;
}; }
/** 表单选项 */ /** 表单选项 */
const formField = ref<Array<{ field: string; title: string }>>([]); const formField = ref<Array<{ field: string; title: string }>>([]);
@ -181,7 +177,7 @@ const formFieldOptions4Summary = computed(() => {
}); });
/** 兼容以前未配置更多设置的流程 */ /** 兼容以前未配置更多设置的流程 */
const initData = () => { function initData() {
if (!modelData.value.processIdRule) { if (!modelData.value.processIdRule) {
modelData.value.processIdRule = { modelData.value.processIdRule = {
enable: false, enable: false,
@ -218,7 +214,7 @@ const initData = () => {
if (modelData.value.taskAfterTriggerSetting) { if (modelData.value.taskAfterTriggerSetting) {
taskAfterTriggerEnable.value = true; taskAfterTriggerEnable.value = true;
} }
}; }
/** 监听表单 ID 变化,加载表单数据 */ /** 监听表单 ID 变化,加载表单数据 */
watch( watch(
@ -242,9 +238,9 @@ watch(
// //
const formRef = ref(); const formRef = ref();
/** 表单校验 */ /** 表单校验 */
const validate = async () => { async function validate() {
await formRef.value?.validate(); await formRef.value?.validate();
}; }
defineExpose({ initData, validate }); defineExpose({ initData, validate });
</script> </script>

View File

@ -80,9 +80,9 @@ const rules: Record<string, Rule[]> = {
}; };
/** 表单校验 */ /** 表单校验 */
const validate = async () => { async function validate() {
await formRef.value?.validate(); await formRef.value?.validate();
}; }
defineExpose({ validate }); defineExpose({ validate });
</script> </script>

View File

@ -16,7 +16,7 @@ const processData = inject('processData') as Ref;
const simpleDesign = ref(); const simpleDesign = ref();
/** 表单校验 */ /** 表单校验 */
const validate = async () => { async function validate() {
// //
if (!processData.value) { if (!processData.value) {
throw new Error('请设计流程'); throw new Error('请设计流程');
@ -29,9 +29,9 @@ const validate = async () => {
} }
} }
return true; return true;
}; }
/** 处理设计器保存成功 */ /** 处理设计器保存成功 */
const handleDesignSuccess = async (data?: any) => { async function handleDesignSuccess(data?: any) {
if (data) { if (data) {
// //
const newModelData = { const newModelData = {
@ -44,7 +44,7 @@ const handleDesignSuccess = async (data?: any) => {
// //
modelData.value = newModelData; modelData.value = newModelData;
} }
}; }
/** 是否显示设计器 */ /** 是否显示设计器 */
const showDesigner = computed(() => { const showDesigner = computed(() => {

View File

@ -18,15 +18,15 @@ const emit = defineEmits(['success']);
const designerRef = ref(); const designerRef = ref();
/** 保存成功回调 */ /** 保存成功回调 */
const handleSuccess = (data?: any) => { function handleSuccess(data?: any) {
if (data) { if (data) {
emit('success', data); emit('success', data);
} }
}; }
/** 设计器配置校验 */ /** 设计器配置校验 */
const validateConfig = async () => { async function validateConfig() {
return await designerRef.value.validate(); return await designerRef.value.validate();
}; }
defineExpose({ validateConfig }); defineExpose({ validateConfig });
</script> </script>
<template> <template>

View File

@ -4,21 +4,12 @@ import type { ModelCategoryInfo } from '#/api/bpm/model';
import { onActivated, reactive, ref, useTemplateRef, watch } from 'vue'; import { onActivated, reactive, ref, useTemplateRef, watch } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Plus, Search, Settings } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { cloneDeep } from '@vben/utils'; import { cloneDeep } from '@vben/utils';
import { refAutoReset } from '@vueuse/core'; import { refAutoReset } from '@vueuse/core';
import { useSortable } from '@vueuse/integrations/useSortable'; import { useSortable } from '@vueuse/integrations/useSortable';
import { import { Button, Card, Dropdown, Input, Menu, message } from 'ant-design-vue';
Button,
Card,
Divider,
Dropdown,
Form,
Input,
Menu,
message,
} from 'ant-design-vue';
import { import {
getCategorySimpleList, getCategorySimpleList,
@ -72,7 +63,7 @@ watch(
); );
/** 加载数据 */ /** 加载数据 */
const getList = async () => { async function getList() {
modelListSpinning.value = true; modelListSpinning.value = true;
try { try {
const modelList = await getModelList(queryParams.name); const modelList = await getModelList(queryParams.name);
@ -89,27 +80,22 @@ const getList = async () => {
} finally { } finally {
modelListSpinning.value = false; modelListSpinning.value = false;
} }
}; }
/** 初始化 */ /** 初始化 */
onActivated(() => { onActivated(() => {
getList(); getList();
}); });
/** 查询方法 */
const handleQuery = () => {
getList();
};
/** 新增模型 */ /** 新增模型 */
const createModel = () => { function createModel() {
router.push({ router.push({
name: 'BpmModelCreate', name: 'BpmModelCreate',
}); });
}; }
/** 处理下拉菜单命令 */ /** 处理下拉菜单命令 */
const handleCommand = (command: string) => { function handleCommand(command: string) {
if (command === 'handleCategoryAdd') { if (command === 'handleCategoryAdd') {
// //
categoryFormModalApi.open(); categoryFormModalApi.open();
@ -126,10 +112,10 @@ const handleCommand = (command: string) => {
}); });
} }
} }
}; }
/** 取消分类排序 */ /** 取消分类排序 */
const handleCategorySortCancel = () => { function handleCategorySortCancel() {
// //
categoryGroup.value = cloneDeep(originalData.value); categoryGroup.value = cloneDeep(originalData.value);
isCategorySorting.value = false; isCategorySorting.value = false;
@ -137,10 +123,10 @@ const handleCategorySortCancel = () => {
if (sortableInstance.value) { if (sortableInstance.value) {
sortableInstance.value.option('disabled', true); sortableInstance.value.option('disabled', true);
} }
}; }
/** 提交分类排序 */ /** 提交分类排序 */
const handleCategorySortSubmit = async () => { async function handleCategorySortSubmit() {
saveSortLoading.value = true; saveSortLoading.value = true;
try { try {
// //
@ -157,76 +143,56 @@ const handleCategorySortSubmit = async () => {
if (sortableInstance.value) { if (sortableInstance.value) {
sortableInstance.value.option('disabled', true); sortableInstance.value.option('disabled', true);
} }
}; }
</script> </script>
<template> <template>
<Page auto-content-height> <Page auto-content-height>
<!-- TODO @jaosn没头像的图标展示文字头像哈 @芋艿 好像已经展示了文字头像是模型列表中吗? -->
<!-- 流程分类表单弹窗 --> <!-- 流程分类表单弹窗 -->
<CategoryFormModal @success="getList" /> <CategoryFormModal @success="getList" />
<Card <Card
:body-style="{ padding: '10px' }" :body-style="{ padding: '10px' }"
class="mb-4 h-[98%]" class="mb-4 h-[98%]"
title="流程模型"
v-spinning="modelListSpinning" v-spinning="modelListSpinning"
> >
<template #extra>
<Input
v-model:value="queryParams.name"
placeholder="搜索流程"
allow-clear
@press-enter="getList"
class="!w-60"
/>
<Button type="primary" @click="createModel">
<IconifyIcon icon="lucide:plus" /> 新建模型
</Button>
<Dropdown placement="bottomRight" arrow>
<Button>
<template #icon>
<IconifyIcon icon="lucide:settings" />
</template>
</Button>
<template #overlay>
<Menu @click="(e) => handleCommand(e.key as string)">
<Menu.Item key="handleCategoryAdd">
<div class="flex items-center">
<IconifyIcon icon="lucide:plus" />
新建分类
</div>
</Menu.Item>
<Menu.Item key="handleCategorySort">
<div class="flex items-center">
<IconifyIcon icon="lucide:align-start-vertical" />
分类排序
</div>
</Menu.Item>
</Menu>
</template>
</Dropdown>
</template>
<div class="flex h-full items-center justify-between pl-5"> <div class="flex h-full items-center justify-between pl-5">
<span class="-mb-4 text-lg font-extrabold">流程模型</span> <div class="mb-4 mr-6" v-if="isCategorySorting">
<!-- 搜索工作栏 -->
<Form
v-if="!isCategorySorting"
class="-mb-4 mr-2.5 flex"
:model="queryParams"
layout="inline"
>
<Form.Item name="name" class="ml-auto">
<Input
v-model:value="queryParams.name"
placeholder="搜索流程"
allow-clear
@press-enter="handleQuery"
class="!w-60"
>
<template #prefix>
<Search class="mx-2.5" />
</template>
</Input>
</Form.Item>
<!-- 右上角新建模型更多操作 -->
<Form.Item>
<Button type="primary" @click="createModel">
<Plus class="size-5" /> 新建模型
</Button>
</Form.Item>
<Form.Item>
<Dropdown placement="bottomRight" arrow>
<Button>
<template #icon>
<Settings class="size-4" />
</template>
</Button>
<template #overlay>
<Menu @click="(e) => handleCommand(e.key as string)">
<Menu.Item key="handleCategoryAdd">
<div class="flex items-center">
<span
class="icon-[ant-design--plus-outlined] mr-1.5 text-[18px]"
></span>
新建分类
</div>
</Menu.Item>
<Menu.Item key="handleCategorySort">
<div class="flex items-center">
<span class="icon-[fa--sort-amount-desc] mr-1.5"></span>
分类排序
</div>
</Menu.Item>
</Menu>
</template>
</Dropdown>
</Form.Item>
</Form>
<div class="-mb-4 mr-6" v-else>
<Button @click="handleCategorySortCancel" class="mr-3"> <Button @click="handleCategorySortCancel" class="mr-3">
</Button> </Button>
@ -240,7 +206,6 @@ const handleCategorySortSubmit = async () => {
</div> </div>
</div> </div>
<Divider />
<!-- 按照分类展示其所属的模型列表 --> <!-- 按照分类展示其所属的模型列表 -->
<div class="px-5" ref="categoryGroupRef"> <div class="px-5" ref="categoryGroupRef">
<CategoryDraggableModel <CategoryDraggableModel

View File

@ -4,6 +4,7 @@ import type { BpmModelApi, ModelCategoryInfo } from '#/api/bpm/model';
import { computed, ref, watchEffect } from 'vue'; import { computed, ref, watchEffect } from 'vue';
import { confirm, useVbenModal } from '@vben/common-ui'; import { confirm, useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { cloneDeep, formatDateTime, isEqual } from '@vben/utils'; import { cloneDeep, formatDateTime, isEqual } from '@vben/utils';
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core';
@ -36,6 +37,12 @@ const props = defineProps<{
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
//
const [CategoryRenameModal, categoryRenameModalApi] = useVbenModal({
connectedComponent: CategoryRenameForm,
destroyOnClose: true,
});
const isModelSorting = ref(false); const isModelSorting = ref(false);
const originalData = ref<BpmModelApi.ModelVO[]>([]); const originalData = ref<BpmModelApi.ModelVO[]>([]);
const modelList = ref<BpmModelApi.ModelVO[]>([]); const modelList = ref<BpmModelApi.ModelVO[]>([]);
@ -98,7 +105,7 @@ const columns = [
]; ];
/** 处理模型的排序 */ /** 处理模型的排序 */
const handleModelSort = () => { function handleModelSort() {
// //
originalData.value = cloneDeep(props.categoryInfo.modelList); originalData.value = cloneDeep(props.categoryInfo.modelList);
// //
@ -114,10 +121,10 @@ const handleModelSort = () => {
disabled: false, // disabled: false, //
}); });
} }
}; }
/** 处理模型的排序提交 */ /** 处理模型的排序提交 */
const handleModelSortSubmit = async () => { async function handleModelSortSubmit() {
try { try {
// //
const ids = modelList.value.map((item) => item.id); const ids = modelList.value.map((item) => item.id);
@ -129,10 +136,10 @@ const handleModelSortSubmit = async () => {
} catch (error) { } catch (error) {
console.error('排序保存失败', error); console.error('排序保存失败', error);
} }
}; }
/** 处理模型的排序取消 */ /** 处理模型的排序取消 */
const handleModelSortCancel = () => { function handleModelSortCancel() {
// //
modelList.value = cloneDeep(originalData.value); modelList.value = cloneDeep(originalData.value);
isModelSorting.value = false; isModelSorting.value = false;
@ -140,20 +147,20 @@ const handleModelSortCancel = () => {
if (sortableInstance.value) { if (sortableInstance.value) {
sortableInstance.value.option('disabled', true); sortableInstance.value.option('disabled', true);
} }
}; }
/** 处理下拉菜单命令 */ /** 处理下拉菜单命令 */
const handleCommand = (command: string) => { function handleCommand(command: string) {
if (command === 'renameCategory') { if (command === 'renameCategory') {
// //
categoryRenameModalApi.setData(props.categoryInfo).open(); categoryRenameModalApi.setData(props.categoryInfo).open();
} else if (command === 'deleteCategory') { } else if (command === 'deleteCategory') {
handleDeleteCategory(); handleDeleteCategory();
} }
}; }
/** 删除流程分类 */ /** 删除流程分类 */
const handleDeleteCategory = async () => { async function handleDeleteCategory() {
if (props.categoryInfo.modelList.length > 0) { if (props.categoryInfo.modelList.length > 0) {
message.warning('该分类下仍有流程定义,不允许删除'); message.warning('该分类下仍有流程定义,不允许删除');
return; return;
@ -170,13 +177,13 @@ const handleDeleteCategory = async () => {
// //
emit('success'); emit('success');
}); });
}; }
/** 处理表单详情点击 */ /** 处理表单详情点击 */
const handleFormDetail = (row: any) => { function handleFormDetail(row: any) {
// TODO // TODO
console.warn('待实现', row); console.warn('待实现', row);
}; }
/** 更新 modelList 模型列表 */ /** 更新 modelList 模型列表 */
const updateModelList = useDebounceFn(() => { const updateModelList = useDebounceFn(() => {
@ -205,17 +212,11 @@ watchEffect(() => {
}); });
/** 自定义表格行渲染 */ /** 自定义表格行渲染 */
const customRow = (_record: any) => { function customRow(_record: any) {
return { return {
class: isModelSorting.value ? 'cursor-move' : '', class: isModelSorting.value ? 'cursor-move' : '',
}; };
}; }
//
const [CategoryRenameModal, categoryRenameModalApi] = useVbenModal({
connectedComponent: CategoryRenameForm,
destroyOnClose: true,
});
// //
const handleRenameSuccess = () => { const handleRenameSuccess = () => {
@ -268,7 +269,7 @@ const handleRenameSuccess = () => {
@click.stop="handleModelSort" @click.stop="handleModelSort"
> >
<template #icon> <template #icon>
<span class="icon-[fa--sort-amount-desc]"></span> <IconifyIcon icon="lucide:align-start-vertical" />
</template> </template>
排序 排序
</Button> </Button>
@ -279,7 +280,7 @@ const handleRenameSuccess = () => {
class="flex items-center text-[14px]" class="flex items-center text-[14px]"
> >
<template #icon> <template #icon>
<span class="icon-[ant-design--setting-outlined]"></span> <IconifyIcon icon="lucide:settings" />
</template> </template>
分类 分类
</Button> </Button>

View File

@ -152,7 +152,7 @@ function onBack() {
// ============================== ============================== // ============================== ==============================
/** 审批相关:获取审批详情 */ /** 审批相关:获取审批详情 */
const getApprovalDetail = async () => { async function getApprovalDetail() {
try { try {
const data = await getApprovalDetailApi({ const data = await getApprovalDetailApi({
processDefinitionId: processDefinitionId.value, processDefinitionId: processDefinitionId.value,
@ -188,13 +188,12 @@ const getApprovalDetail = async () => {
: []; : [];
} }
} }
} finally { } finally {}
} }
};
/** 审批相关:选择发起人 */ /** 审批相关:选择发起人 */
const selectUserConfirm = (id: string, userList: any[]) => { function selectUserConfirm(id: string, userList: any[]) {
startUserSelectAssignees.value[id] = userList?.map((item: any) => item.id); startUserSelectAssignees.value[id] = userList?.map((item: any) => item.id);
}; }
/** 审批相关:预测流程节点会因为输入的参数值而产生新的预测结果值,所以需重新预测一次, formData.value可改成实际业务中的特定字段 */ /** 审批相关:预测流程节点会因为输入的参数值而产生新的预测结果值,所以需重新预测一次, formData.value可改成实际业务中的特定字段 */
watch( watch(

View File

@ -1,18 +1,14 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmCategoryApi } from '#/api/bpm/category';
import type { DescriptionItemSchema } from '#/components/description'; import type { DescriptionItemSchema } from '#/components/description';
import { h } from 'vue'; import { h } from 'vue';
import { useAccess } from '@vben/access';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { DictTag } from '#/components/dict-tag'; import { DictTag } from '#/components/dict-tag';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils'; import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
return [ return [
@ -118,9 +114,7 @@ export function GridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = BpmCategoryApi.CategoryVO>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -168,39 +162,11 @@ export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
minWidth: 180, minWidth: 180,
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 180, width: 180,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
attrs: {
nameField: 'name',
nameTitle: '请假',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: '详情',
show: hasAccessByCodes(['bpm:oa-leave:query']),
},
{
code: 'progress',
text: '进度',
show: hasAccessByCodes(['bpm:oa-leave:query']),
},
{
code: 'cancel',
text: '取消',
show: (row: any) =>
row.status === 1 && hasAccessByCodes(['bpm:oa-leave:query']),
},
],
},
}, },
]; ];
} }

View File

@ -19,14 +19,14 @@ const detailData = ref<BpmOALeaveApi.LeaveVO>();
const { query } = useRoute(); const { query } = useRoute();
const queryId = computed(() => query.id as string); const queryId = computed(() => query.id as string);
const getDetailData = async () => { async function getDetailData() {
try { try {
datailLoading.value = true; datailLoading.value = true;
detailData.value = await getLeave(Number(props.id || queryId.value)); detailData.value = await getLeave(Number(props.id || queryId.value));
} finally { } finally {
datailLoading.value = false; datailLoading.value = false;
} }
}; }
onMounted(() => { onMounted(() => {
getDetailData(); getDetailData();

View File

@ -1,20 +1,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { PageParam } from '@vben/request'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type {
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmOALeaveApi } from '#/api/bpm/oa/leave'; import type { BpmOALeaveApi } from '#/api/bpm/oa/leave';
import { h } from 'vue'; import { h } from 'vue';
import { Page, prompt } from '@vben/common-ui'; import { Page, prompt } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message, Textarea } from 'ant-design-vue'; import { message, Textarea } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getLeavePage } from '#/api/bpm/oa/leave'; import { getLeavePage } from '#/api/bpm/oa/leave';
import { cancelProcessInstanceByStartUser } from '#/api/bpm/processInstance'; import { cancelProcessInstanceByStartUser } from '#/api/bpm/processInstance';
import { DocAlert } from '#/components/doc-alert'; import { DocAlert } from '#/components/doc-alert';
@ -27,12 +21,12 @@ const [Grid, gridApi] = useVbenVxeGrid({
schema: GridFormSchema(), schema: GridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
ajax: { ajax: {
query: async ({ page }: PageParam, formValues: any) => { query: async ({ page }, formValues) => {
return await getLeavePage({ return await getLeavePage({
pageNo: page.currentPage, pageNo: page.currentPage,
pageSize: page.pageSize, pageSize: page.pageSize,
@ -52,7 +46,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
}); });
/** 创建请假 */ /** 创建请假 */
function onCreate() { function handleCreate() {
router.push({ router.push({
name: 'OALeaveCreate', name: 'OALeaveCreate',
query: { query: {
@ -62,15 +56,15 @@ function onCreate() {
} }
/** 查看请假详情 */ /** 查看请假详情 */
const onDetail = (row: BpmOALeaveApi.LeaveVO) => { function handleDetail(row: BpmOALeaveApi.LeaveVO) {
router.push({ router.push({
name: 'OALeaveDetail', name: 'OALeaveDetail',
query: { id: row.id }, query: { id: row.id },
}); });
}; }
/** 取消请假 */ /** 取消请假 */
const onCancel = (row: BpmOALeaveApi.LeaveVO) => { function handleCancel(row: BpmOALeaveApi.LeaveVO) {
prompt({ prompt({
async beforeClose(scope) { async beforeClose(scope) {
if (scope.isConfirm) { if (scope.isConfirm) {
@ -100,35 +94,14 @@ const onCancel = (row: BpmOALeaveApi.LeaveVO) => {
title: '取消流程', title: '取消流程',
modelPropName: 'value', modelPropName: 'value',
}); });
}; }
/** 审批进度 */ /** 审批进度 */
const onProgress = (row: BpmOALeaveApi.LeaveVO) => { function handleProgress(row: BpmOALeaveApi.LeaveVO) {
router.push({ router.push({
name: 'BpmProcessInstanceDetail', name: 'BpmProcessInstanceDetail',
query: { id: row.processInstanceId }, query: { id: row.processInstanceId },
}); });
};
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmOALeaveApi.LeaveVO>) {
switch (code) {
case 'cancel': {
onCancel(row);
break;
}
case 'detail': {
onDetail(row);
break;
}
case 'progress': {
onProgress(row);
break;
}
}
} }
/** 刷新表格 */ /** 刷新表格 */
@ -146,25 +119,48 @@ function onRefresh() {
<Grid table-title=""> <Grid table-title="">
<template #toolbar-tools> <template #toolbar-tools>
<Button <TableAction
type="primary" :actions="[
@click="onCreate" {
v-access:code="['bpm:category:create']" label: '发起请假',
> type: 'primary',
<Plus class="size-5" /> icon: ACTION_ICON.ADD,
发起请假 auth: ['bpm:oa-leave:create'],
</Button> onClick: handleCreate,
},
]"
/>
</template> </template>
<template #actions="{ row }">
<template #userIds-cell="{ row }"> <TableAction
<span :actions="[
v-for="(userId, index) in row.userIds" {
:key="userId" label: $t('common.detail'),
class="pr-5px" type: 'link',
> icon: ACTION_ICON.VIEW,
{{ dataList.find((user) => user.id === userId)?.nickname }} auth: ['bpm:oa-leave:query'],
<span v-if="index < row.userIds.length - 1"></span> onClick: handleDetail.bind(null, row),
</span> },
{
label: '审批进度',
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:oa-leave:query'],
onClick: handleProgress.bind(null, row),
},
{
label: '取消',
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['bpm:user-group:query'],
popConfirm: {
title: '取消流程',
confirm: handleCancel.bind(null, row),
},
},
]"
/>
</template> </template>
</Grid> </Grid>
</Page> </Page>

View File

@ -1,14 +1,9 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression';
import { useAccess } from '@vben/access';
import { z } from '#/adapter/form'; import { z } from '#/adapter/form';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
return [ return [
@ -88,9 +83,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = BpmProcessExpressionApi.ProcessExpressionVO>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -124,29 +117,10 @@ export function useGridColumns<T = BpmProcessExpressionApi.ProcessExpressionVO>(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 180, width: 180,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
attrs: {
nameField: 'name',
nameTitle: '流程表达式',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['bpm:process-expression:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['bpm:process-expression:delete']),
},
],
},
}, },
]; ];
} }

View File

@ -1,16 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression'; import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
deleteProcessExpression, deleteProcessExpression,
getProcessExpressionPage, getProcessExpressionPage,
@ -25,12 +21,46 @@ const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form, connectedComponent: Form,
destroyOnClose: true, destroyOnClose: true,
}); });
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建流程表达式 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑流程表达式 */
function handleEdit(row: BpmProcessExpressionApi.ProcessExpressionVO) {
formModalApi.setData(row).open();
}
/** 删除流程表达式 */
async function handleDelete(row: BpmProcessExpressionApi.ProcessExpressionVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
});
try {
await deleteProcessExpression(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} finally {
hideLoading();
}
}
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -53,55 +83,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
} as VxeTableGridOptions<BpmProcessExpressionApi.ProcessExpressionVO>, } as VxeTableGridOptions<BpmProcessExpressionApi.ProcessExpressionVO>,
}); });
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmProcessExpressionApi.ProcessExpressionVO>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建流程表达式 */
function onCreate() {
formModalApi.setData(null).open();
}
/** 编辑流程表达式 */
function onEdit(row: BpmProcessExpressionApi.ProcessExpressionVO) {
formModalApi.setData(row).open();
}
/** 删除流程表达式 */
async function onDelete(row: BpmProcessExpressionApi.ProcessExpressionVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteProcessExpression(row.id as number);
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
onRefresh();
} finally {
hideLoading();
}
}
</script> </script>
<template> <template>
@ -115,14 +96,41 @@ async function onDelete(row: BpmProcessExpressionApi.ProcessExpressionVO) {
<FormModal @success="onRefresh" /> <FormModal @success="onRefresh" />
<Grid table-title=""> <Grid table-title="">
<template #toolbar-tools> <template #toolbar-tools>
<Button <TableAction
type="primary" :actions="[
@click="onCreate" {
v-access:code="['bpm:process-expression:create']" label: $t('ui.actionTitle.create', ['流程表达式']),
> type: 'primary',
<Plus class="size-5" /> icon: ACTION_ICON.ADD,
{{ $t('ui.actionTitle.create', ['流程表达式']) }} auth: ['bpm:process-expression:create'],
</Button> onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['bpm:process-expression:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['bpm:process-expression:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template> </template>
</Grid> </Grid>
</Page> </Page>

View File

@ -55,7 +55,6 @@ const [Modal, modalApi] = useVbenModal({
}, },
async onOpenChange(isOpen: boolean) { async onOpenChange(isOpen: boolean) {
if (!isOpen) { if (!isOpen) {
formData.value = undefined;
return; return;
} }
// //
@ -77,7 +76,7 @@ const [Modal, modalApi] = useVbenModal({
</script> </script>
<template> <template>
<Modal :title="getTitle" class="w-[600px]"> <Modal :title="getTitle" class="w-[40%]">
<Form class="mx-4" /> <Form class="mx-4" />
</Modal> </Modal>
</template> </template>

View File

@ -39,7 +39,7 @@ const processDefinitionList = ref<
>([]); // >([]); //
// groupBy // groupBy
const groupBy = (array: any[], key: string) => { function groupBy(array: any[], key: string) {
const result: Record<string, any[]> = {}; const result: Record<string, any[]> = {};
for (const item of array) { for (const item of array) {
const groupKey = item[key]; const groupKey = item[key];
@ -49,10 +49,10 @@ const groupBy = (array: any[], key: string) => {
result[groupKey].push(item); result[groupKey].push(item);
} }
return result; return result;
}; }
/** 查询列表 */ /** 查询列表 */
const getList = async () => { async function getList() {
loading.value = true; loading.value = true;
try { try {
// //
@ -79,20 +79,20 @@ const getList = async () => {
} finally { } finally {
loading.value = false; loading.value = false;
} }
}; }
/** 获取所有流程分类数据 */ /** 获取所有流程分类数据 */
const getCategoryList = async () => { async function getCategoryList() {
try { try {
// //
categoryList.value = await getCategorySimpleList(); categoryList.value = await getCategorySimpleList();
} catch { } catch {
// //
} }
}; }
/** 获取所有流程定义数据 */ /** 获取所有流程定义数据 */
const handleGetProcessDefinitionList = async () => { async function handleGetProcessDefinitionList() {
try { try {
// //
processDefinitionList.value = await getProcessDefinitionList({ processDefinitionList.value = await getProcessDefinitionList({
@ -108,7 +108,7 @@ const handleGetProcessDefinitionList = async () => {
} catch { } catch {
// //
} }
}; }
/** 用于存储搜索过滤后的流程定义 */ /** 用于存储搜索过滤后的流程定义 */
const filteredProcessDefinitionList = ref< const filteredProcessDefinitionList = ref<
@ -116,7 +116,7 @@ const filteredProcessDefinitionList = ref<
>([]); >([]);
/** 搜索流程 */ /** 搜索流程 */
const handleQuery = () => { function handleQuery() {
if (searchName.value.trim()) { if (searchName.value.trim()) {
// //
isSearching.value = true; isSearching.value = true;
@ -141,16 +141,16 @@ const handleQuery = () => {
isSearching.value = false; isSearching.value = false;
filteredProcessDefinitionList.value = processDefinitionList.value; filteredProcessDefinitionList.value = processDefinitionList.value;
} }
}; }
/** 判断流程定义是否匹配搜索 */ /** 判断流程定义是否匹配搜索 */
const isDefinitionMatchSearch = (definition: any) => { function isDefinitionMatchSearch(definition: any) {
if (!isSearching.value) return false; if (!isSearching.value) return false;
return definition.name.toLowerCase().includes(searchName.value.toLowerCase()); return definition.name.toLowerCase().includes(searchName.value.toLowerCase());
}; }
/** 流程定义的分组 */ /** 流程定义的分组 */
const processDefinitionGroup: any = computed(() => { const processDefinitionGroup = computed(() => {
if (!processDefinitionList.value?.length) { if (!processDefinitionList.value?.length) {
return {}; return {};
} }
@ -172,26 +172,26 @@ const processDefinitionGroup: any = computed(() => {
}); });
/** 通过分类 code 获取对应的名称 */ /** 通过分类 code 获取对应的名称 */
const getCategoryName = (categoryCode: string) => { function getCategoryName(categoryCode: string) {
return categoryList.value?.find((ctg: any) => ctg.code === categoryCode) return categoryList.value?.find((ctg: any) => ctg.code === categoryCode)
?.name; ?.name;
}; }
// ========== ========== // ========== ==========
const selectProcessDefinition = ref(); // const selectProcessDefinition = ref(); //
const processDefinitionDetailRef = ref(); const processDefinitionDetailRef = ref();
/** 处理选择流程的按钮操作 */ /** 处理选择流程的按钮操作 */
const handleSelect = async ( async function handleSelect(
row: BpmProcessDefinitionApi.ProcessDefinitionVO, row: BpmProcessDefinitionApi.ProcessDefinitionVO,
formVariables?: any, formVariables?: any,
) => { ) {
// //
selectProcessDefinition.value = row; selectProcessDefinition.value = row;
// //
await nextTick(); await nextTick();
processDefinitionDetailRef.value?.initProcessInfo(row, formVariables); processDefinitionDetailRef.value?.initProcessInfo(row, formVariables);
}; }
/** 过滤出有流程的分类列表。目的:只展示有流程的分类 */ /** 过滤出有流程的分类列表。目的:只展示有流程的分类 */
const availableCategories = computed(() => { const availableCategories = computed(() => {

View File

@ -91,7 +91,7 @@ const activityNodes = ref<ApprovalNodeInfo[]>([]);
const processInstanceStartLoading = ref(false); const processInstanceStartLoading = ref(false);
/** 提交按钮 */ /** 提交按钮 */
const submitForm = async () => { async function submitForm() {
if (!fApi.value || !props.selectProcessDefinition) { if (!fApi.value || !props.selectProcessDefinition) {
return; return;
} }
@ -130,10 +130,10 @@ const submitForm = async () => {
} finally { } finally {
processInstanceStartLoading.value = false; processInstanceStartLoading.value = false;
} }
}; }
/** 设置表单信息、获取流程图数据 */ /** 设置表单信息、获取流程图数据 */
const initProcessInfo = async (row: any, formVariables?: any) => { async function initProcessInfo(row: any, formVariables?: any) {
// //
startUserSelectTasks.value = []; startUserSelectTasks.value = [];
startUserSelectAssignees.value = {}; startUserSelectAssignees.value = {};
@ -177,7 +177,7 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
}); });
// Tab // Tab
} }
}; }
/** 预测流程节点会因为输入的参数值而产生新的预测结果值,所以需重新预测一次 */ /** 预测流程节点会因为输入的参数值而产生新的预测结果值,所以需重新预测一次 */
watch( watch(
@ -200,10 +200,10 @@ watch(
); );
/** 获取审批详情 */ /** 获取审批详情 */
const getApprovalDetail = async (row: { async function getApprovalDetail(row: {
id: string; id: string;
processVariablesStr: string; processVariablesStr: string;
}) => { }) {
try { try {
const data = await getApprovalDetailApi({ const data = await getApprovalDetailApi({
processDefinitionId: row.id, processDefinitionId: row.id,
@ -246,12 +246,12 @@ const getApprovalDetail = async (row: {
message.error('获取审批详情失败'); message.error('获取审批详情失败');
console.error('获取审批详情失败:', error); console.error('获取审批详情失败:', error);
} }
}; }
/** /**
* 设置表单权限 * 设置表单权限
*/ */
const setFieldPermission = (field: string, permission: string) => { function setFieldPermission(field: string, permission: string) {
if (permission === BpmFieldPermissionType.READ) { if (permission === BpmFieldPermissionType.READ) {
// @ts-ignore // @ts-ignore
fApi.value?.disabled(true, field); fApi.value?.disabled(true, field);
@ -264,18 +264,18 @@ const setFieldPermission = (field: string, permission: string) => {
// @ts-ignore // @ts-ignore
fApi.value?.hidden(true, field); fApi.value?.hidden(true, field);
} }
}; }
/** 取消发起审批 */ /** 取消发起审批 */
const handleCancel = () => { function handleCancel() {
emit('cancel'); emit('cancel');
}; }
/** 选择发起人 */ /** 选择发起人 */
const selectUserConfirm = (activityId: string, userList: any[]) => { function selectUserConfirm(activityId: string, userList: any[]) {
if (!activityId || !Array.isArray(userList)) return; if (!activityId || !Array.isArray(userList)) return;
startUserSelectAssignees.value[activityId] = userList.map((item) => item.id); startUserSelectAssignees.value[activityId] = userList.map((item) => item.id);
}; }
defineExpose({ initProcessInfo }); defineExpose({ initProcessInfo });
</script> </script>
@ -293,7 +293,7 @@ defineExpose({ initProcessInfo });
<template #extra> <template #extra>
<Space wrap> <Space wrap>
<Button plain type="default" @click="handleCancel"> <Button plain type="default" @click="handleCancel">
<IconifyIcon icon="mdi:arrow-left" />&nbsp; 返回 <IconifyIcon icon="lucide:arrow-left" />&nbsp; 返回
</Button> </Button>
</Space> </Space>
</template> </template>
@ -345,11 +345,11 @@ defineExpose({ initProcessInfo });
<template v-if="activeTab === 'form'"> <template v-if="activeTab === 'form'">
<Space wrap class="flex w-full justify-center"> <Space wrap class="flex w-full justify-center">
<Button plain type="primary" @click="submitForm"> <Button plain type="primary" @click="submitForm">
<IconifyIcon icon="icon-park-outline:check" /> <IconifyIcon icon="lucide:check" />
发起 发起
</Button> </Button>
<Button plain type="default" @click="handleCancel"> <Button plain type="default" @click="handleCancel">
<IconifyIcon icon="icon-park-outline:close" /> <IconifyIcon icon="lucide:x" />
取消 取消
</Button> </Button>
</Space> </Space>

View File

@ -1,19 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
import { useAccess } from '@vben/access';
import { getCategorySimpleList } from '#/api/bpm/category'; import { getCategorySimpleList } from '#/api/bpm/category';
import { $t } from '#/locales'; import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
import {
BpmProcessInstanceStatus,
DICT_TYPE,
getDictOptions,
getRangePickerDefaultProps,
} from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */ /** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] { export function useGridFormSchema(): VbenFormSchema[] {
@ -88,9 +77,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = BpmProcessInstanceApi.ProcessInstanceVO>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'name', field: 'name',
@ -136,38 +123,11 @@ export function useGridColumns<T = BpmProcessInstanceApi.ProcessInstanceVO>(
minWidth: 180, minWidth: 180,
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 180, width: 180,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
attrs: {
nameField: 'name',
nameTitle: '流程名称',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: $t('ui.actionTitle.detail'),
show: hasAccessByCodes(['bpm:process-instance:query']),
},
{
code: 'cancel',
text: $t('ui.actionTitle.cancel'),
show: (row: BpmProcessInstanceApi.ProcessInstanceVO) => {
return (
row.status === BpmProcessInstanceStatus.RUNNING &&
hasAccessByCodes(['bpm:process-instance:cancel'])
);
},
},
],
},
}, },
]; ];
} }

View File

@ -175,7 +175,7 @@ async function getApprovalDetail() {
} }
/** 获取流程模型视图*/ /** 获取流程模型视图*/
const getProcessModelView = async () => { async function getProcessModelView() {
if (BpmModelType.BPMN === processDefinition.value?.modelType) { if (BpmModelType.BPMN === processDefinition.value?.modelType) {
// BPMN // BPMN
processModelView.value = { processModelView.value = {
@ -186,14 +186,14 @@ const getProcessModelView = async () => {
if (data) { if (data) {
processModelView.value = data; processModelView.value = data;
} }
}; }
// //
const activityNodes = ref<BpmProcessInstanceApi.ApprovalNodeInfo[]>([]); const activityNodes = ref<BpmProcessInstanceApi.ApprovalNodeInfo[]>([]);
/** /**
* 设置表单权限 * 设置表单权限
*/ */
const setFieldPermission = (field: string, permission: string) => { function setFieldPermission(field: string, permission: string) {
if (permission === FieldPermissionType.READ) { if (permission === FieldPermissionType.READ) {
// @ts-ignore // @ts-ignore
fApi.value?.disabled(true, field); fApi.value?.disabled(true, field);
@ -208,7 +208,7 @@ const setFieldPermission = (field: string, permission: string) => {
// @ts-ignore // @ts-ignore
fApi.value?.hidden(true, field); fApi.value?.hidden(true, field);
} }
}; }
/** /**
* 操作成功后刷新 * 操作成功后刷新

View File

@ -7,6 +7,7 @@ import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
import { computed, reactive, ref, watch } from 'vue'; import { computed, reactive, ref, watch } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { useUserStore } from '@vben/stores'; import { useUserStore } from '@vben/stores';
import { isEmpty } from '@vben/utils'; import { isEmpty } from '@vben/utils';
@ -60,6 +61,17 @@ const props = defineProps<{
writableFields: string[]; // writableFields: string[]; //
}>(); // }>(); //
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const [SignatureModal, signatureModalApi] = useVbenModal({
connectedComponent: Signature,
destroyOnClose: true,
});
/** 创建流程表达式 */
function openSignatureModal() {
signatureModalApi.setData(null).open();
}
const router = useRouter(); // const router = useRouter(); //
const userStore = useUserStore(); const userStore = useUserStore();
const userId = userStore.userInfo?.id; const userId = userStore.userInfo?.id;
@ -86,7 +98,6 @@ const nodeTypeName = ref('审批'); // 节点类型名称
// //
const reasonRequire = ref(); const reasonRequire = ref();
const approveFormRef = ref<FormInstance>(); const approveFormRef = ref<FormInstance>();
const signRef = ref();
const approveSignFormRef = ref(); const approveSignFormRef = ref();
const nextAssigneesActivityNode = ref<BpmProcessInstanceApi.ApprovalNodeInfo[]>( const nextAssigneesActivityNode = ref<BpmProcessInstanceApi.ApprovalNodeInfo[]>(
[], [],
@ -235,7 +246,7 @@ watch(
); );
/** 弹出气泡卡 */ /** 弹出气泡卡 */
const openPopover = async (type: string) => { async function openPopover(type: string) {
if (type === 'approve') { if (type === 'approve') {
// //
const valid = await validateNormalForm(); const valid = await validateNormalForm();
@ -258,19 +269,19 @@ const openPopover = async (type: string) => {
}); });
// await nextTick() // await nextTick()
// formRef.value.resetFields() // formRef.value.resetFields()
}; }
/** 关闭气泡卡 */ /** 关闭气泡卡 */
const closePopover = (type: string, formRef: any | FormInstance) => { function closePopover(type: string, formRef: any | FormInstance) {
if (formRef) { if (formRef) {
formRef.resetFields(); formRef.resetFields();
} }
if (popOverVisible.value[type]) popOverVisible.value[type] = false; if (popOverVisible.value[type]) popOverVisible.value[type] = false;
nextAssigneesActivityNode.value = []; nextAssigneesActivityNode.value = [];
}; }
/** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */ /** 流程通过时,根据表单变量查询新的流程节点,判断下一个节点类型是否为自选审批人 */
const initNextAssigneesFormField = async () => { async function initNextAssigneesFormField() {
// , // ,
const variables = getUpdatedProcessInstanceVariables(); const variables = getUpdatedProcessInstanceVariables();
const data = await getNextApprovalNodes({ const data = await getNextApprovalNodes({
@ -293,14 +304,14 @@ const initNextAssigneesFormField = async () => {
} }
}); });
} }
}; }
/** 选择下一个节点的审批人 */ /** 选择下一个节点的审批人 */
const selectNextAssigneesConfirm = (id: string, userList: any[]) => { function selectNextAssigneesConfirm(id: string, userList: any[]) {
approveReasonForm.nextAssignees[id] = userList?.map((item: any) => item.id); approveReasonForm.nextAssignees[id] = userList?.map((item: any) => item.id);
}; }
/** 审批通过时,校验每个自选审批人的节点是否都已配置了审批人 */ /** 审批通过时,校验每个自选审批人的节点是否都已配置了审批人 */
const validateNextAssignees = () => { function validateNextAssignees() {
if (Object.keys(nextAssigneesActivityNode.value).length === 0) { if (Object.keys(nextAssigneesActivityNode.value).length === 0) {
return true; return true;
} }
@ -312,13 +323,10 @@ const validateNextAssignees = () => {
} }
} }
return true; return true;
}; }
/** 处理审批通过和不通过的操作 */ /** 处理审批通过和不通过的操作 */
const handleAudit = async ( async function handleAudit(pass: boolean, formRef: FormInstance | undefined) {
pass: boolean,
formRef: FormInstance | undefined,
) => {
formLoading.value = true; formLoading.value = true;
try { try {
// //
@ -375,10 +383,10 @@ const handleAudit = async (
} finally { } finally {
formLoading.value = false; formLoading.value = false;
} }
}; }
/** 处理抄送 */ /** 处理抄送 */
const handleCopy = async () => { async function handleCopy() {
formLoading.value = true; formLoading.value = true;
try { try {
// 1. // 1.
@ -397,10 +405,10 @@ const handleCopy = async () => {
} finally { } finally {
formLoading.value = false; formLoading.value = false;
} }
}; }
/** 处理转交 */ /** 处理转交 */
const handleTransfer = async () => { async function handleTransfer() {
formLoading.value = true; formLoading.value = true;
try { try {
// 1.1 // 1.1
@ -421,10 +429,10 @@ const handleTransfer = async () => {
} finally { } finally {
formLoading.value = false; formLoading.value = false;
} }
}; }
/** 处理委派 */ /** 处理委派 */
const handleDelegate = async () => { async function handleDelegate() {
formLoading.value = true; formLoading.value = true;
try { try {
// 1.1 // 1.1
@ -446,10 +454,10 @@ const handleDelegate = async () => {
} finally { } finally {
formLoading.value = false; formLoading.value = false;
} }
}; }
/** 处理加签 */ /** 处理加签 */
const handlerAddSign = async (type: string) => { async function handlerAddSign(type: string) {
formLoading.value = true; formLoading.value = true;
try { try {
// 1.1 // 1.1
@ -471,10 +479,10 @@ const handlerAddSign = async (type: string) => {
} finally { } finally {
formLoading.value = false; formLoading.value = false;
} }
}; }
/** 处理退回 */ /** 处理退回 */
const handleReturn = async () => { async function handleReturn() {
formLoading.value = true; formLoading.value = true;
try { try {
// 1.1 // 1.1
@ -496,10 +504,10 @@ const handleReturn = async () => {
} finally { } finally {
formLoading.value = false; formLoading.value = false;
} }
}; }
/** 处理取消 */ /** 处理取消 */
const handleCancel = async () => { async function handleCancel() {
formLoading.value = true; formLoading.value = true;
try { try {
// 1.1 // 1.1
@ -518,26 +526,26 @@ const handleCancel = async () => {
} finally { } finally {
formLoading.value = false; formLoading.value = false;
} }
}; }
/** 处理再次提交 */ /** 处理再次提交 */
const handleReCreate = async () => { async function handleReCreate() {
// //
await router.push({ await router.push({
path: '/bpm/task/create', path: '/bpm/task/create',
query: { processInstanceId: props.processInstance?.id }, query: { processInstanceId: props.processInstance?.id },
}); });
// router.push('/bpm/task/my'); // router.push('/bpm/task/my');
}; }
/** 获取减签人员标签 */ /** 获取减签人员标签 */
const getDeleteSignUserLabel = (task: any): string => { function getDeleteSignUserLabel(task: any): string {
const deptName = task?.assigneeUser?.deptName || task?.ownerUser?.deptName; const deptName = task?.assigneeUser?.deptName || task?.ownerUser?.deptName;
const nickname = task?.assigneeUser?.nickname || task?.ownerUser?.nickname; const nickname = task?.assigneeUser?.nickname || task?.ownerUser?.nickname;
return `${nickname} ( 所属部门:${deptName} )`; return `${nickname} ( 所属部门:${deptName} )`;
}; }
/** 处理减签 */ /** 处理减签 */
const handlerDeleteSign = async () => { async function handlerDeleteSign() {
formLoading.value = true; formLoading.value = true;
try { try {
// 1.1 // 1.1
@ -557,23 +565,23 @@ const handlerDeleteSign = async () => {
} finally { } finally {
formLoading.value = false; formLoading.value = false;
} }
}; }
/** 重新加载数据 */ /** 重新加载数据 */
const reload = () => { function reload() {
emit('success'); emit('success');
}; }
/** 任务是否为处理中状态 */ /** 任务是否为处理中状态 */
const isHandleTaskStatus = () => { function isHandleTaskStatus() {
let canHandle = false; let canHandle = false;
if (BpmTaskStatusEnum.RUNNING === runningTask.value?.status) { if (BpmTaskStatusEnum.RUNNING === runningTask.value?.status) {
canHandle = true; canHandle = true;
} }
return canHandle; return canHandle;
}; }
/** 流程状态是否为结束状态 */ /** 流程状态是否为结束状态 */
const isEndProcessStatus = (status: number) => { function isEndProcessStatus(status: number) {
let isEndStatus = false; let isEndStatus = false;
if ( if (
BpmProcessInstanceStatus.APPROVE === status || BpmProcessInstanceStatus.APPROVE === status ||
@ -583,10 +591,10 @@ const isEndProcessStatus = (status: number) => {
isEndStatus = true; isEndStatus = true;
} }
return isEndStatus; return isEndStatus;
}; }
/** 是否显示按钮 */ /** 是否显示按钮 */
const isShowButton = (btnType: BpmTaskOperationButtonTypeEnum): boolean => { function isShowButton(btnType: BpmTaskOperationButtonTypeEnum): boolean {
let isShow = true; let isShow = true;
if ( if (
runningTask.value?.buttonsSetting && runningTask.value?.buttonsSetting &&
@ -595,10 +603,10 @@ const isShowButton = (btnType: BpmTaskOperationButtonTypeEnum): boolean => {
isShow = runningTask.value.buttonsSetting[btnType].enable; isShow = runningTask.value.buttonsSetting[btnType].enable;
} }
return isShow; return isShow;
}; }
/** 获取按钮的显示名称 */ /** 获取按钮的显示名称 */
const getButtonDisplayName = (btnType: BpmTaskOperationButtonTypeEnum) => { function getButtonDisplayName(btnType: BpmTaskOperationButtonTypeEnum) {
let displayName = OPERATION_BUTTON_NAME.get(btnType); let displayName = OPERATION_BUTTON_NAME.get(btnType);
if ( if (
runningTask.value?.buttonsSetting && runningTask.value?.buttonsSetting &&
@ -607,9 +615,9 @@ const getButtonDisplayName = (btnType: BpmTaskOperationButtonTypeEnum) => {
displayName = runningTask.value.buttonsSetting[btnType].displayName; displayName = runningTask.value.buttonsSetting[btnType].displayName;
} }
return displayName; return displayName;
}; }
const loadTodoTask = (task: any) => { function loadTodoTask(task: any) {
approveForm.value = {}; approveForm.value = {};
runningTask.value = task; runningTask.value = task;
approveFormFApi.value = {}; approveFormFApi.value = {};
@ -629,10 +637,10 @@ const loadTodoTask = (task: any) => {
} else { } else {
approveForm.value = {}; // approveForm.value = {}; //
} }
}; }
/** 校验流程表单 */ /** 校验流程表单 */
const validateNormalForm = async () => { async function validateNormalForm() {
if (props.processDefinition?.formType === BpmModelFormType.NORMAL) { if (props.processDefinition?.formType === BpmModelFormType.NORMAL) {
let valid = true; let valid = true;
try { try {
@ -644,31 +652,31 @@ const validateNormalForm = async () => {
} else { } else {
return true; return true;
} }
}; }
/** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */ /** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */
const getUpdatedProcessInstanceVariables = () => { function getUpdatedProcessInstanceVariables() {
const variables: any = {}; const variables: any = {};
props.writableFields.forEach((field: string) => { props.writableFields.forEach((field: string) => {
if (field && variables[field]) if (field && variables[field])
variables[field] = props.normalFormApi.getValue(field); variables[field] = props.normalFormApi.getValue(field);
}); });
return variables; return variables;
}; }
/** 处理签名完成 */ /** 处理签名完成 */
const handleSignFinish = (url: string) => { function handleSignFinish(url: string) {
approveReasonForm.signPicUrl = url; approveReasonForm.signPicUrl = url;
approveFormRef.value?.validateFields(['signPicUrl']); approveFormRef.value?.validateFields(['signPicUrl']);
}; }
/** 处理弹窗可见性 */ /** 处理弹窗可见性 */
const handlePopoverVisible = (visible: boolean) => { function handlePopoverVisible(visible: boolean) {
if (!visible) { if (!visible) {
// //
popOverVisible.value.approve = true; popOverVisible.value.approve = true;
} }
}; }
defineExpose({ loadTodoTask }); defineExpose({ loadTodoTask });
</script> </script>
@ -745,7 +753,7 @@ defineExpose({ loadTodoTask });
name="signPicUrl" name="signPicUrl"
ref="approveSignFormRef" ref="approveSignFormRef"
> >
<Button @click="signRef.open()" type="primary"> <Button @click="openSignatureModal" type="primary">
{{ approveReasonForm.signPicUrl ? '重新签名' : '点击签名' }} {{ approveReasonForm.signPicUrl ? '重新签名' : '点击签名' }}
</Button> </Button>
@ -802,7 +810,7 @@ defineExpose({ loadTodoTask });
" "
> >
<Button ghost danger type="primary" @click="openPopover('reject')"> <Button ghost danger type="primary" @click="openPopover('reject')">
<IconifyIcon icon="icon-park-outline:close" /> <IconifyIcon icon="lucide:x" />
{{ getButtonDisplayName(BpmTaskOperationButtonTypeEnum.REJECT) }} {{ getButtonDisplayName(BpmTaskOperationButtonTypeEnum.REJECT) }}
</Button> </Button>
<template #content> <template #content>
@ -862,7 +870,7 @@ defineExpose({ loadTodoTask });
" "
> >
<Button type="dashed" @click="openPopover('copy')"> <Button type="dashed" @click="openPopover('copy')">
<IconifyIcon icon="icon-park-outline:copy" /> <IconifyIcon icon="lucide:copy" />
{{ getButtonDisplayName(BpmTaskOperationButtonTypeEnum.COPY) }} {{ getButtonDisplayName(BpmTaskOperationButtonTypeEnum.COPY) }}
</Button> </Button>
<template #content> <template #content>
@ -1387,5 +1395,5 @@ defineExpose({ loadTodoTask });
</div> </div>
<!-- 签名弹窗 --> <!-- 签名弹窗 -->
<Signature ref="signRef" @success="handleSignFinish" /> <SignatureModal @success="handleSignFinish" />
</template> </template>

View File

@ -16,6 +16,8 @@ defineOptions({
const emits = defineEmits(['success']); const emits = defineEmits(['success']);
const signature = ref<InstanceType<typeof Vue3Signature>>();
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
title: '流程签名', title: '流程签名',
onOpenChange(visible) { onOpenChange(visible) {
@ -23,42 +25,30 @@ const [Modal, modalApi] = useVbenModal({
modalApi.close(); modalApi.close();
} }
}, },
onConfirm: () => { async onConfirm() {
submit(); message.success({
content: '签名上传中请稍等。。。',
});
const signFileUrl = await uploadFile({
file: download.base64ToFile(
signature?.value?.save('image/jpeg') || '',
'签名',
),
});
emits('success', signFileUrl);
modalApi.close();
}, },
}); });
const signature = ref<InstanceType<typeof Vue3Signature>>();
const open = async () => {
modalApi.open();
};
defineExpose({ open });
const submit = async () => {
message.success({
content: '签名上传中请稍等。。。',
});
const signFileUrl = await uploadFile({
file: download.base64ToFile(
signature?.value?.save('image/jpeg') || '',
'签名',
),
});
emits('success', signFileUrl);
modalApi.close();
};
</script> </script>
<template> <template>
<Modal class="h-[500px] w-[900px]"> <Modal class="h-[40%] w-[60%]">
<div class="mb-2 flex justify-end"> <div class="mb-2 flex justify-end">
<Space> <Space>
<Tooltip title="撤销上一步操作"> <Tooltip title="撤销上一步操作">
<Button @click="signature?.undo()"> <Button @click="signature?.undo()">
<template #icon> <template #icon>
<IconifyIcon icon="mi:undo" class="mb-[4px] size-[16px]" /> <IconifyIcon icon="lucide:undo" class="mb-[4px] size-[16px]" />
</template> </template>
撤销 撤销
</Button> </Button>
@ -67,10 +57,7 @@ const submit = async () => {
<Tooltip title="清空画布"> <Tooltip title="清空画布">
<Button @click="signature?.clear()"> <Button @click="signature?.clear()">
<template #icon> <template #icon>
<IconifyIcon <IconifyIcon icon="lucide:trash" class="mb-[4px] size-[16px]" />
icon="mdi:delete-outline"
class="mb-[4px] size-[16px]"
/>
</template> </template>
<span>清除</span> <span>清除</span>
</Button> </Button>

View File

@ -14,7 +14,7 @@ import { Button } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { getTaskListByProcessInstanceId } from '#/api/bpm/task'; import { getTaskListByProcessInstanceId } from '#/api/bpm/task';
import { DICT_TYPE, formatPast2, setConfAndFields2 } from '#/utils'; import { DICT_TYPE, setConfAndFields2 } from '#/utils';
defineOptions({ defineOptions({
name: 'BpmProcessInstanceTaskList', name: 'BpmProcessInstanceTaskList',
@ -75,11 +75,7 @@ const columns = shallowRef([
field: 'durationInMillis', field: 'durationInMillis',
title: '耗时', title: '耗时',
minWidth: 180, minWidth: 180,
slots: { formatter: 'formatPast2',
default: ({ row }: { row: BpmTaskApi.TaskManagerVO }) => {
return formatPast2(row.durationInMillis);
},
},
}, },
]); ]);
@ -116,9 +112,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
/** /**
* 刷新表格数据 * 刷新表格数据
*/ */
const refresh = (): void => { function refresh() {
gridApi.query(); gridApi.query();
}; }
// //
interface TaskForm { interface TaskForm {

View File

@ -108,12 +108,12 @@ const nodeTypeSvgMap = {
const onlyStatusIconShow = [-1, 0, 1]; const onlyStatusIconShow = [-1, 0, 1];
// //
const getApprovalNodeTypeIcon = (nodeType: BpmNodeTypeEnum) => { function getApprovalNodeTypeIcon(nodeType: BpmNodeTypeEnum) {
return nodeTypeSvgMap[nodeType]?.icon; return nodeTypeSvgMap[nodeType]?.icon;
}; }
// //
const getApprovalNodeIcon = (taskStatus: number, nodeType: BpmNodeTypeEnum) => { function getApprovalNodeIcon(taskStatus: number, nodeType: BpmNodeTypeEnum) {
if (taskStatus === BpmTaskStatusEnum.NOT_START) { if (taskStatus === BpmTaskStatusEnum.NOT_START) {
return statusIconMap[taskStatus]?.icon || 'mdi:clock-outline'; return statusIconMap[taskStatus]?.icon || 'mdi:clock-outline';
} }
@ -128,15 +128,15 @@ const getApprovalNodeIcon = (taskStatus: number, nodeType: BpmNodeTypeEnum) => {
return statusIconMap[taskStatus]?.icon || 'mdi:clock-outline'; return statusIconMap[taskStatus]?.icon || 'mdi:clock-outline';
} }
return 'mdi:clock-outline'; return 'mdi:clock-outline';
}; }
// //
const getApprovalNodeColor = (taskStatus: number) => { function getApprovalNodeColor(taskStatus: number) {
return statusIconMap[taskStatus]?.color; return statusIconMap[taskStatus]?.color;
}; }
// //
const getApprovalNodeTime = (node: BpmProcessInstanceApi.ApprovalNodeInfo) => { function getApprovalNodeTime(node: BpmProcessInstanceApi.ApprovalNodeInfo) {
if (node.nodeType === BpmNodeTypeEnum.START_USER_NODE && node.startTime) { if (node.nodeType === BpmNodeTypeEnum.START_USER_NODE && node.startTime) {
return formatDateTime(node.startTime); return formatDateTime(node.startTime);
} }
@ -147,7 +147,7 @@ const getApprovalNodeTime = (node: BpmProcessInstanceApi.ApprovalNodeInfo) => {
return formatDateTime(node.startTime); return formatDateTime(node.startTime);
} }
return ''; return '';
}; }
// //
const userSelectFormRef = ref(); const userSelectFormRef = ref();
@ -164,26 +164,26 @@ const handleSelectUser = (activityId: string, selectedList: any[]) => {
// //
const selectedUsers = ref<number[]>([]); const selectedUsers = ref<number[]>([]);
const handleUserSelectConfirm = (userList: any[]) => { function handleUserSelectConfirm(userList: any[]) {
customApproveUsers.value[selectedActivityNodeId.value] = userList || []; customApproveUsers.value[selectedActivityNodeId.value] = userList || [];
emit('selectUserConfirm', selectedActivityNodeId.value, userList); emit('selectUserConfirm', selectedActivityNodeId.value, userList);
}; }
/** 跳转子流程 */ /** 跳转子流程 */
const handleChildProcess = (activity: any) => { function handleChildProcess(activity: any) {
push({ push({
name: 'BpmProcessInstanceDetail', name: 'BpmProcessInstanceDetail',
query: { query: {
id: activity.processInstanceId, id: activity.processInstanceId,
}, },
}); });
}; }
// //
const shouldShowCustomUserSelect = ( function shouldShowCustomUserSelect(
activity: BpmProcessInstanceApi.ApprovalNodeInfo, activity: BpmProcessInstanceApi.ApprovalNodeInfo,
) => { ) {
return ( return (
isEmpty(activity.tasks) && isEmpty(activity.tasks) &&
isEmpty(activity.candidateUsers) && isEmpty(activity.candidateUsers) &&
@ -192,27 +192,27 @@ const shouldShowCustomUserSelect = (
BpmCandidateStrategyEnum.APPROVE_USER_SELECT === BpmCandidateStrategyEnum.APPROVE_USER_SELECT ===
activity.candidateStrategy) activity.candidateStrategy)
); );
}; }
// //
const shouldShowApprovalReason = (task: any, nodeType: BpmNodeTypeEnum) => { function shouldShowApprovalReason(task: any, nodeType: BpmNodeTypeEnum) {
return ( return (
task.reason && task.reason &&
[BpmNodeTypeEnum.END_EVENT_NODE, BpmNodeTypeEnum.USER_TASK_NODE].includes( [BpmNodeTypeEnum.END_EVENT_NODE, BpmNodeTypeEnum.USER_TASK_NODE].includes(
nodeType, nodeType,
) )
); );
}; }
// //
const handleUserSelectClosed = () => { function handleUserSelectClosed() {
selectedUsers.value = []; selectedUsers.value = [];
}; }
// //
const handleUserSelectCancel = () => { function handleUserSelectCancel() {
selectedUsers.value = []; selectedUsers.value = [];
}; }
</script> </script>
<template> <template>

View File

@ -1,8 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task'; import type { BpmTaskApi } from '#/api/bpm/task';
import { h } from 'vue'; import { h } from 'vue';
@ -11,7 +8,7 @@ import { Page, prompt } from '@vben/common-ui';
import { Button, message, Textarea } from 'ant-design-vue'; import { Button, message, Textarea } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
cancelProcessInstanceByStartUser, cancelProcessInstanceByStartUser,
getProcessInstanceMyPage, getProcessInstanceMyPage,
@ -25,54 +22,21 @@ import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmProcessInstanceMy' }); defineOptions({ name: 'BpmProcessInstanceMy' });
const [Grid, gridApi] = useVbenVxeGrid({ /** 刷新表格 */
formOptions: { function onRefresh() {
schema: useGridFormSchema(), gridApi.query();
}, }
gridOptions: {
columns: useGridColumns(onActionClick),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getProcessInstanceMyPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
refresh: { code: 'query' },
search: true,
},
cellConfig: {
height: 64,
},
} as VxeTableGridOptions<BpmTaskApi.TaskVO>,
});
/** 表格操作按钮的回调函数 */ /** 查看流程实例 */
function onActionClick({ code, row }: OnActionClickParams<BpmTaskApi.TaskVO>) { function handleDetail(row: BpmTaskApi.TaskVO) {
switch (code) { router.push({
case 'cancel': { name: 'BpmProcessInstanceDetail',
onCancel(row); query: { id: row.id },
break; });
}
case 'detail': {
onDetail(row);
break;
}
}
} }
/** 取消流程实例 */ /** 取消流程实例 */
function onCancel(row: BpmTaskApi.TaskVO) { function handleCancel(row: BpmTaskApi.TaskVO) {
prompt({ prompt({
async beforeClose(scope) { async beforeClose(scope) {
if (scope.isConfirm) { if (scope.isConfirm) {
@ -104,19 +68,37 @@ function onCancel(row: BpmTaskApi.TaskVO) {
}); });
} }
/** 查看流程实例 */ const [Grid, gridApi] = useVbenVxeGrid({
function onDetail(row: BpmTaskApi.TaskVO) { formOptions: {
console.warn(row); schema: useGridFormSchema(),
router.push({ },
name: 'BpmProcessInstanceDetail', gridOptions: {
query: { id: row.id }, columns: useGridColumns(),
}); height: 'auto',
} keepSource: true,
proxyConfig: {
/** 刷新表格 */ ajax: {
function onRefresh() { query: async ({ page }, formValues) => {
gridApi.query(); return await getProcessInstanceMyPage({
} pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
refresh: { code: 'query' },
search: true,
},
cellConfig: {
height: 64,
},
} as VxeTableGridOptions<BpmTaskApi.TaskVO>,
});
</script> </script>
<template> <template>
@ -126,7 +108,6 @@ function onRefresh() {
url="https://doc.iocoder.cn/bpm/process-instance" url="https://doc.iocoder.cn/bpm/process-instance"
/> />
<FormModal @success="onRefresh" />
<Grid table-title=""> <Grid table-title="">
<!-- 摘要 --> <!-- 摘要 -->
<template #slot-summary="{ row }"> <template #slot-summary="{ row }">
@ -154,7 +135,7 @@ function onRefresh() {
<!-- 单人审批 --> <!-- 单人审批 -->
<template v-if="row.tasks.length === 1"> <template v-if="row.tasks.length === 1">
<span> <span>
<Button type="link" @click="onDetail(row)"> <Button type="link" @click="handleDetail(row)">
{{ row.tasks[0].assigneeUser?.nickname }} {{ row.tasks[0].assigneeUser?.nickname }}
</Button> </Button>
({{ row.tasks[0].name }}) 审批中 ({{ row.tasks[0].name }}) 审批中
@ -163,7 +144,7 @@ function onRefresh() {
<!-- 多人审批 --> <!-- 多人审批 -->
<template v-else> <template v-else>
<span> <span>
<Button type="link" @click="onDetail(row)"> <Button type="link" @click="handleDetail(row)">
{{ row.tasks[0].assigneeUser?.nickname }} {{ row.tasks[0].assigneeUser?.nickname }}
</Button> </Button>
{{ row.tasks.length }} ({{ row.tasks[0].name }})审批中 {{ row.tasks.length }} ({{ row.tasks[0].name }})审批中
@ -178,6 +159,28 @@ function onRefresh() {
/> />
</template> </template>
</template> </template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:process-instance:query'],
onClick: handleDetail.bind(null, row),
},
{
label: $t('ui.actionTitle.cancel'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
ifShow: row.status === BpmProcessInstanceStatus.RUNNING,
auth: ['bpm:process-instance:cancel'],
onClick: handleCancel.bind(null, row),
},
]"
/>
</template>
</Grid> </Grid>
</Page> </Page>
</template> </template>

View File

@ -1,26 +1,14 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance'; import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
import { h } from 'vue'; import { h } from 'vue';
import { useAccess } from '@vben/access';
import { Button } from 'ant-design-vue'; import { Button } from 'ant-design-vue';
import { getCategorySimpleList } from '#/api/bpm/category'; import { getCategorySimpleList } from '#/api/bpm/category';
import { getSimpleUserList } from '#/api/system/user'; import { getSimpleUserList } from '#/api/system/user';
import { $t } from '#/locales'; import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
import {
DICT_TYPE,
formatPast2,
getDictOptions,
getRangePickerDefaultProps,
} from '#/utils';
import { BpmProcessInstanceStatus } from '../../../../utils/constants';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */ /** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] { export function useGridFormSchema(): VbenFormSchema[] {
@ -95,8 +83,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = BpmProcessInstanceApi.ProcessInstanceVO>( export function useGridColumns(
onActionClick: OnActionClickFn<T>,
onTaskClick: (task: BpmProcessInstanceApi.Task) => void, onTaskClick: (task: BpmProcessInstanceApi.Task) => void,
): VxeTableGridOptions['columns'] { ): VxeTableGridOptions['columns'] {
return [ return [
@ -153,13 +140,7 @@ export function useGridColumns<T = BpmProcessInstanceApi.ProcessInstanceVO>(
field: 'durationInMillis', field: 'durationInMillis',
title: '流程耗时', title: '流程耗时',
minWidth: 180, minWidth: 180,
slots: { formatter: 'formatPast2',
default: ({ row }) => {
return row.durationInMillis > 0
? formatPast2(row.durationInMillis)
: '-';
},
},
}, },
// 当前审批任务 tasks // 当前审批任务 tasks
@ -192,36 +173,10 @@ export function useGridColumns<T = BpmProcessInstanceApi.ProcessInstanceVO>(
minWidth: 320, minWidth: 320,
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 180, width: 180,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
attrs: {
nameField: 'name',
nameTitle: '流程分类',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: $t('ui.actionTitle.detail'),
show: hasAccessByCodes(['bpm:process-instance:query']),
},
{
code: 'cancel',
text: $t('ui.actionTitle.cancel'),
show: (row: BpmProcessInstanceApi.ProcessInstanceVO) => {
return (
row.status === BpmProcessInstanceStatus.RUNNING &&
hasAccessByCodes(['bpm:process-instance:cancel'])
);
},
},
],
},
}, },
]; ];
} }

View File

@ -1,8 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance'; import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
import { h } from 'vue'; import { h } from 'vue';
@ -11,62 +8,22 @@ import { Page, prompt } from '@vben/common-ui';
import { message, Textarea } from 'ant-design-vue'; import { message, Textarea } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
cancelProcessInstanceByAdmin, cancelProcessInstanceByAdmin,
getProcessInstanceManagerPage, getProcessInstanceManagerPage,
} from '#/api/bpm/processInstance'; } from '#/api/bpm/processInstance';
import { DocAlert } from '#/components/doc-alert'; import { DocAlert } from '#/components/doc-alert';
import { router } from '#/router'; import { router } from '#/router';
import { BpmProcessInstanceStatus } from '#/utils';
import { useGridColumns, useGridFormSchema } from './data'; import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmProcessInstanceManager' }); defineOptions({ name: 'BpmProcessInstanceManager' });
const [Grid, gridApi] = useVbenVxeGrid({ /** 刷新表格 */
formOptions: { function onRefresh() {
schema: useGridFormSchema(), gridApi.query();
},
gridOptions: {
columns: useGridColumns(onActionClick, onTaskClick),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getProcessInstanceManagerPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<BpmProcessInstanceApi.ProcessInstanceVO>,
});
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmProcessInstanceApi.ProcessInstanceVO>) {
switch (code) {
case 'cancel': {
onCancel(row);
break;
}
case 'detail': {
onDetail(row);
break;
}
}
} }
/** 点击任务 */ /** 点击任务 */
@ -75,8 +32,17 @@ function onTaskClick(task: BpmProcessInstanceApi.Task) {
console.warn(task); console.warn(task);
} }
/** 查看流程实例 */
function handleDetail(row: BpmProcessInstanceApi.ProcessInstanceVO) {
console.warn(row);
router.push({
name: 'BpmProcessInstanceDetail',
query: { id: row.id },
});
}
/** 取消流程实例 */ /** 取消流程实例 */
function onCancel(row: BpmProcessInstanceApi.ProcessInstanceVO) { function handleCancel(row: BpmProcessInstanceApi.ProcessInstanceVO) {
prompt({ prompt({
async beforeClose(scope) { async beforeClose(scope) {
if (scope.isConfirm) { if (scope.isConfirm) {
@ -110,19 +76,34 @@ function onCancel(row: BpmProcessInstanceApi.ProcessInstanceVO) {
.catch(() => {}); .catch(() => {});
} }
/** 查看流程实例 */ const [Grid, gridApi] = useVbenVxeGrid({
function onDetail(row: BpmProcessInstanceApi.ProcessInstanceVO) { formOptions: {
console.warn(row); schema: useGridFormSchema(),
router.push({ },
name: 'BpmProcessInstanceDetail', gridOptions: {
query: { id: row.id }, columns: useGridColumns(onTaskClick),
}); height: 'auto',
} keepSource: true,
proxyConfig: {
/** 刷新表格 */ ajax: {
function onRefresh() { query: async ({ page }, formValues) => {
gridApi.query(); return await getProcessInstanceManagerPage({
} pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
});
},
},
},
rowConfig: {
keyField: 'id',
},
toolbarConfig: {
refresh: { code: 'query' },
search: true,
},
} as VxeTableGridOptions<BpmProcessInstanceApi.ProcessInstanceVO>,
});
</script> </script>
<template> <template>
@ -131,6 +112,29 @@ function onRefresh() {
<DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm" /> <DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm" />
</template> </template>
<Grid table-title="" /> <Grid table-title="">
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.detail'),
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:process-instance:query'],
onClick: handleDetail.bind(null, row),
},
{
label: $t('ui.actionTitle.cancel'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
ifShow: row.status === BpmProcessInstanceStatus.RUNNING,
auth: ['bpm:process-instance:cancel'],
onClick: handleCancel.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page> </Page>
</template> </template>

View File

@ -1,8 +1,5 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmCategoryApi } from '#/api/bpm/category';
import { useAccess } from '@vben/access';
import { z } from '#/adapter/form'; import { z } from '#/adapter/form';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
@ -27,8 +24,6 @@ export const EVENT_OPTIONS = [
{ label: 'timeout', value: 'timeout' }, { label: 'timeout', value: 'timeout' },
]; ];
const { hasAccessByCodes } = useAccess();
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
return [ return [
@ -80,6 +75,16 @@ export function useFormSchema(): VbenFormSchema[] {
allowClear: true, allowClear: true,
}, },
rules: 'required', rules: 'required',
dependencies: {
triggerFields: ['type'],
trigger: (values) => (values.event = undefined),
componentProps: (values) => ({
options:
values.type === 'execution'
? EVENT_EXECUTION_OPTIONS
: EVENT_OPTIONS,
}),
},
}, },
{ {
fieldName: 'valueType', fieldName: 'valueType',
@ -96,9 +101,17 @@ export function useFormSchema(): VbenFormSchema[] {
}, },
{ {
fieldName: 'value', fieldName: 'value',
label: '表达式', label: '类路径|表达式',
component: 'Input', component: 'Input',
rules: 'required', rules: 'required',
dependencies: {
triggerFields: ['valueType'],
trigger: (values) => (values.value = undefined),
componentProps: (values) => ({
placeholder:
values.valueType === 'class' ? '请输入类路径' : '请输入表达式',
}),
},
}, },
]; ];
} }
@ -129,9 +142,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = BpmCategoryApi.CategoryVO>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'id', field: 'id',
@ -178,29 +189,11 @@ export function useGridColumns<T = BpmCategoryApi.CategoryVO>(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation', field: 'actions',
title: '操作', title: '操作',
minWidth: 180, minWidth: 180,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
attrs: {
nameField: 'name',
nameTitle: '流程监听器',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'edit',
show: hasAccessByCodes(['bpm:process-listener:update']),
},
{
code: 'delete',
show: hasAccessByCodes(['bpm:process-listener:delete']),
},
],
},
}, },
]; ];
} }

View File

@ -1,16 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmProcessListenerApi } from '#/api/bpm/processListener'; import type { BpmProcessListenerApi } from '#/api/bpm/processListener';
import { Page, useVbenModal } from '@vben/common-ui'; import { Page, useVbenModal } from '@vben/common-ui';
import { Plus } from '@vben/icons';
import { Button, message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { import {
deleteProcessListener, deleteProcessListener,
getProcessListenerPage, getProcessListenerPage,
@ -25,12 +21,46 @@ const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form, connectedComponent: Form,
destroyOnClose: true, destroyOnClose: true,
}); });
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建流程监听器 */
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑流程监听器 */
function handleEdit(row: BpmProcessListenerApi.ProcessListenerVO) {
formModalApi.setData(row).open();
}
/** 删除流程监听器 */
async function handleDelete(row: BpmProcessListenerApi.ProcessListenerVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
key: 'action_key_msg',
});
try {
await deleteProcessListener(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_key_msg',
});
onRefresh();
} catch {
hideLoading();
}
}
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -53,57 +83,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
} as VxeTableGridOptions<BpmProcessListenerApi.ProcessListenerVO>, } as VxeTableGridOptions<BpmProcessListenerApi.ProcessListenerVO>,
}); });
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmProcessListenerApi.ProcessListenerVO>) {
switch (code) {
case 'delete': {
onDelete(row);
break;
}
case 'edit': {
onEdit(row);
break;
}
}
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
/** 创建流程监听器 */
function onCreate() {
formModalApi.setData(null).open();
}
/** 编辑流程监听器 */
function onEdit(row: BpmProcessListenerApi.ProcessListenerVO) {
formModalApi.setData(row).open();
}
/** 删除流程监听器 */
async function onDelete(row: BpmProcessListenerApi.ProcessListenerVO) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteProcessListener(row.id as number);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
key: 'action_process_msg',
});
onRefresh();
} catch {
hideLoading();
}
}
</script> </script>
<template> <template>
@ -117,14 +96,41 @@ async function onDelete(row: BpmProcessListenerApi.ProcessListenerVO) {
<FormModal @success="onRefresh" /> <FormModal @success="onRefresh" />
<Grid table-title=""> <Grid table-title="">
<template #toolbar-tools> <template #toolbar-tools>
<Button <TableAction
type="primary" :actions="[
@click="onCreate" {
v-access:code="['bpm:process-listener:create']" label: $t('ui.actionTitle.create', ['流程监听器']),
> type: 'primary',
<Plus class="size-5" /> icon: ACTION_ICON.ADD,
{{ $t('ui.actionTitle.create', ['流程监听器']) }} auth: ['bpm:process-listener:create'],
</Button> onClick: handleCreate,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['bpm:process-listener:update'],
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['bpm:process-listener:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template> </template>
</Grid> </Grid>
</Page> </Page>

View File

@ -15,7 +15,7 @@ import {
} from '#/api/bpm/processListener'; } from '#/api/bpm/processListener';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { EVENT_EXECUTION_OPTIONS, EVENT_OPTIONS, useFormSchema } from '../data'; import { useFormSchema } from '../data';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const formData = ref<BpmProcessListenerApi.ProcessListenerVO>(); const formData = ref<BpmProcessListenerApi.ProcessListenerVO>();
@ -31,7 +31,7 @@ const [Form, formApi] = useVbenForm({
class: 'w-full', class: 'w-full',
}, },
formItemClass: 'col-span-2', formItemClass: 'col-span-2',
labelWidth: 110, labelWidth: 100,
}, },
layout: 'horizontal', layout: 'horizontal',
schema: useFormSchema(), schema: useFormSchema(),
@ -67,55 +67,11 @@ const [Modal, modalApi] = useVbenModal({
formData.value = undefined; formData.value = undefined;
return; return;
} }
//
formApi.updateSchema([
{
fieldName: 'type',
componentProps: {
onChange: (value: string) => {
formApi.setFieldValue('event', undefined);
formApi.updateSchema([
{
fieldName: 'event',
componentProps: {
options:
value === 'execution'
? EVENT_EXECUTION_OPTIONS
: EVENT_OPTIONS,
},
},
]);
},
},
},
{
fieldName: 'valueType',
componentProps: {
onChange: (value: string) => {
formApi.setFieldValue('value', undefined);
formApi.updateSchema([
{
fieldName: 'value',
label: value === 'class' ? '类路径' : '表达式',
componentProps: {
placeholder:
value === 'class' ? '请输入类路径' : '请输入表达式',
},
},
]);
},
},
},
]);
// //
const data = modalApi.getData<BpmProcessListenerApi.ProcessListenerVO>(); const data = modalApi.getData<BpmProcessListenerApi.ProcessListenerVO>();
if (!data || !data.id) { if (!data || !data.id) {
return; return;
} }
modalApi.lock(); modalApi.lock();
try { try {
formData.value = await getProcessListener(data.id as number); formData.value = await getProcessListener(data.id as number);
@ -129,7 +85,7 @@ const [Modal, modalApi] = useVbenModal({
</script> </script>
<template> <template>
<Modal :title="getTitle" class="w-[600px]"> <Modal :title="getTitle" class="w-[40%]">
<Form class="mx-4" /> <Form class="mx-4" />
</Modal> </Modal>
</template> </template>

View File

@ -1,13 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
import { useAccess } from '@vben/access';
import { getRangePickerDefaultProps } from '#/utils'; import { getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */ /** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] { export function useGridFormSchema(): VbenFormSchema[] {
return [ return [
@ -32,9 +27,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = BpmTaskApi.TaskVO>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'processInstanceName', field: 'processInstanceName',
@ -46,8 +39,12 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
field: 'summary', field: 'summary',
title: '摘要', title: '摘要',
minWidth: 200, minWidth: 200,
slots: { formatter: ({ cellValue }) => {
default: 'slot-summary', return cellValue && cellValue.length > 0
? cellValue
.map((item: any) => `${item.key} : ${item.value}`)
.join('\n')
: '-';
}, },
}, },
{ {
@ -70,8 +67,8 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
field: 'createUser.nickname', field: 'createUser.nickname',
title: '抄送人', title: '抄送人',
minWidth: 180, minWidth: 180,
slots: { formatter: ({ cellValue }) => {
default: 'slot-createUser', return cellValue || '-';
}, },
}, },
{ {
@ -86,26 +83,10 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 120, width: 120,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
attrs: {
nameField: 'name',
nameTitle: '流程名称',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'detail',
text: '详情',
show: hasAccessByCodes(['bpm:task:query']),
},
],
},
}, },
]; ];
} }

View File

@ -1,13 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance'; import type { BpmProcessInstanceApi } from '#/api/bpm/processInstance';
import { Page } from '@vben/common-ui'; import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getProcessInstanceCopyPage } from '#/api/bpm/processInstance'; import { getProcessInstanceCopyPage } from '#/api/bpm/processInstance';
import { DocAlert } from '#/components/doc-alert'; import { DocAlert } from '#/components/doc-alert';
import { router } from '#/router'; import { router } from '#/router';
@ -16,12 +13,24 @@ import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmCopyTask' }); defineOptions({ name: 'BpmCopyTask' });
const [Grid, gridApi] = useVbenVxeGrid({ /** 任务详情 */
function handleDetail(row: BpmProcessInstanceApi.CopyVO) {
const query = {
id: row.processInstanceId,
...(row.activityId && { activityId: row.activityId }),
};
router.push({
name: 'BpmProcessInstanceDetail',
query,
});
}
const [Grid] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -47,36 +56,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
} as VxeTableGridOptions<BpmProcessInstanceApi.CopyVO>, } as VxeTableGridOptions<BpmProcessInstanceApi.CopyVO>,
}); });
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmProcessInstanceApi.CopyVO>) {
switch (code) {
case 'detail': {
onDetail(row);
break;
}
}
}
/** 任务详情 */
function onDetail(row: BpmProcessInstanceApi.CopyVO) {
const query = {
id: row.processInstanceId,
...(row.activityId && { activityId: row.activityId }),
};
router.push({
name: 'BpmProcessInstanceDetail',
query,
});
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
</script> </script>
<template> <template>
@ -88,27 +67,19 @@ function onRefresh() {
/> />
</template> </template>
<FormModal @success="onRefresh" />
<Grid table-title=""> <Grid table-title="">
<!-- 摘要 --> <template #actions="{ row }">
<template #slot-summary="{ row }"> <TableAction
<div :actions="[
class="flex flex-col py-2" {
v-if="row.summary && row.summary.length > 0" label: $t('common.detail'),
> type: 'link',
<div v-for="(item, index) in row.summary" :key="index"> icon: ACTION_ICON.VIEW,
<span class="text-gray-500"> auth: ['bpm:task:query'],
{{ item.key }} : {{ item.value }} onClick: handleDetail.bind(null, row),
</span> },
</div> ]"
</div> />
<div v-else>-</div>
</template>
<!-- 抄送人 -->
<template #slot-createUser="{ row }">
<span class="text-gray-500">
{{ row.createUser.nickname || '系统' }}
</span>
</template> </template>
</Grid> </Grid>
</Page> </Page>

View File

@ -1,14 +1,8 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
import { getCategorySimpleList } from '#/api/bpm/category'; import { getCategorySimpleList } from '#/api/bpm/category';
import { import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
DICT_TYPE,
formatPast2,
getDictOptions,
getRangePickerDefaultProps,
} from '#/utils';
/** 列表的搜索表单 */ /** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] { export function useGridFormSchema(): VbenFormSchema[] {
@ -69,9 +63,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = BpmTaskApi.TaskVO>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'processInstance.name', field: 'processInstance.name',
@ -83,8 +75,12 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
field: 'processInstance.summary', field: 'processInstance.summary',
title: '摘要', title: '摘要',
minWidth: 200, minWidth: 200,
slots: { formatter: ({ cellValue }) => {
default: 'slot-summary', return cellValue && cellValue.length > 0
? cellValue
.map((item: any) => `${item.key} : ${item.value}`)
.join('\n')
: '-';
}, },
}, },
{ {
@ -92,12 +88,6 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
title: '发起人', title: '发起人',
minWidth: 120, minWidth: 120,
}, },
{
field: 'createTime',
title: '发起时间',
minWidth: 180,
formatter: 'formatDateTime',
},
{ {
field: 'name', field: 'name',
title: '当前任务', title: '当前任务',
@ -133,9 +123,7 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
field: 'durationInMillis', field: 'durationInMillis',
title: '耗时', title: '耗时',
minWidth: 180, minWidth: 180,
formatter: ({ cellValue }) => { formatter: 'formatPast2',
return `${formatPast2(cellValue)}`;
},
}, },
{ {
field: 'processInstanceId', field: 'processInstanceId',
@ -148,25 +136,10 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
minWidth: 280, minWidth: 280,
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 120, width: 120,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
attrs: {
nameField: 'name',
nameTitle: '流程名称',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'history',
text: '历史',
},
],
},
}, },
]; ];
} }

View File

@ -1,13 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task'; import type { BpmTaskApi } from '#/api/bpm/task';
import { Page } from '@vben/common-ui'; import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getTaskDonePage } from '#/api/bpm/task'; import { getTaskDonePage } from '#/api/bpm/task';
import { DocAlert } from '#/components/doc-alert'; import { DocAlert } from '#/components/doc-alert';
import { router } from '#/router'; import { router } from '#/router';
@ -16,12 +13,23 @@ import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmDoneTask' }); defineOptions({ name: 'BpmDoneTask' });
const [Grid, gridApi] = useVbenVxeGrid({ /** 查看历史 */
function handleHistory(row: BpmTaskApi.TaskManagerVO) {
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstance.id,
taskId: row.id,
},
});
}
const [Grid] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -47,33 +55,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
} as VxeTableGridOptions<BpmTaskApi.TaskVO>, } as VxeTableGridOptions<BpmTaskApi.TaskVO>,
}); });
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<BpmTaskApi.TaskVO>) {
switch (code) {
case 'history': {
onHistory(row);
break;
}
}
}
/** 查看历史 */
function onHistory(row: BpmTaskApi.TaskVO) {
console.warn(row);
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstance.id,
taskId: row.id,
},
});
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
</script> </script>
<template> <template>
@ -92,25 +73,17 @@ function onRefresh() {
</template> </template>
<Grid table-title=""> <Grid table-title="">
<!-- 摘要 --> <template #actions="{ row }">
<template #slot-summary="{ row }"> <TableAction
<div :actions="[
class="flex flex-col py-2" {
v-if=" label: '历史',
row.processInstance.summary && type: 'link',
row.processInstance.summary.length > 0 icon: ACTION_ICON.VIEW,
" onClick: handleHistory.bind(null, row),
> },
<div ]"
v-for="(item, index) in row.processInstance.summary" />
:key="index"
>
<span class="text-gray-500">
{{ item.key }} : {{ item.value }}
</span>
</div>
</div>
<div v-else>-</div>
</template> </template>
</Grid> </Grid>
</Page> </Page>

View File

@ -1,8 +1,7 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
import { DICT_TYPE, formatPast2, getRangePickerDefaultProps } from '#/utils'; import { DICT_TYPE, getRangePickerDefaultProps } from '#/utils';
/** 列表的搜索表单 */ /** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] { export function useGridFormSchema(): VbenFormSchema[] {
@ -28,9 +27,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = BpmTaskApi.TaskVO>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'processInstance.name', field: 'processInstance.name',
@ -89,9 +86,7 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
field: 'durationInMillis', field: 'durationInMillis',
title: '耗时', title: '耗时',
minWidth: 180, minWidth: 180,
formatter: ({ cellValue }) => { formatter: 'formatPast2',
return `${formatPast2(cellValue)}`;
},
}, },
{ {
field: 'processInstanceId', field: 'processInstanceId',
@ -104,25 +99,10 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
minWidth: 280, minWidth: 280,
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 120, width: 120,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
attrs: {
nameField: 'name',
nameTitle: '流程名称',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'history',
text: '历史',
},
],
},
}, },
]; ];
} }

View File

@ -1,13 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task'; import type { BpmTaskApi } from '#/api/bpm/task';
import { Page } from '@vben/common-ui'; import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getTaskManagerPage } from '#/api/bpm/task'; import { getTaskManagerPage } from '#/api/bpm/task';
import { DocAlert } from '#/components/doc-alert'; import { DocAlert } from '#/components/doc-alert';
import { router } from '#/router'; import { router } from '#/router';
@ -16,12 +13,22 @@ import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmManagerTask' }); defineOptions({ name: 'BpmManagerTask' });
/** 查看历史 */
function handleHistory(row: BpmTaskApi.TaskManagerVO) {
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstance.id,
},
});
}
const [Grid] = useVbenVxeGrid({ const [Grid] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -47,30 +54,6 @@ const [Grid] = useVbenVxeGrid({
}, },
} as VxeTableGridOptions<BpmTaskApi.TaskManagerVO>, } as VxeTableGridOptions<BpmTaskApi.TaskManagerVO>,
}); });
/** 表格操作按钮的回调函数 */
function onActionClick({
code,
row,
}: OnActionClickParams<BpmTaskApi.TaskManagerVO>) {
switch (code) {
case 'history': {
onHistory(row);
break;
}
}
}
/** 查看历史 */
function onHistory(row: BpmTaskApi.TaskManagerVO) {
console.warn(row);
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstance.id,
},
});
}
</script> </script>
<template> <template>
@ -78,6 +61,20 @@ function onHistory(row: BpmTaskApi.TaskManagerVO) {
<template #doc> <template #doc>
<DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm/" /> <DocAlert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
</template> </template>
<Grid table-title="" /> <Grid table-title="">
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '历史',
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:task:query'],
onClick: handleHistory.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page> </Page>
</template> </template>

View File

@ -1,16 +1,9 @@
import type { VbenFormSchema } from '#/adapter/form'; import type { VbenFormSchema } from '#/adapter/form';
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task';
import { h } from 'vue';
import { useAccess } from '@vben/access';
import { getCategorySimpleList } from '#/api/bpm/category'; import { getCategorySimpleList } from '#/api/bpm/category';
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils'; import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
const { hasAccessByCodes } = useAccess();
/** 列表的搜索表单 */ /** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] { export function useGridFormSchema(): VbenFormSchema[] {
return [ return [
@ -69,9 +62,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
} }
/** 列表的字段 */ /** 列表的字段 */
export function useGridColumns<T = BpmTaskApi.TaskVO>( export function useGridColumns(): VxeTableGridOptions['columns'] {
onActionClick: OnActionClickFn<T>,
): VxeTableGridOptions['columns'] {
return [ return [
{ {
field: 'processInstance.name', field: 'processInstance.name',
@ -83,29 +74,12 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
field: 'processInstance.summary', field: 'processInstance.summary',
title: '摘要', title: '摘要',
minWidth: 200, minWidth: 200,
slots: { formatter: ({ cellValue }) => {
default: ({ row }) => { return cellValue && cellValue.length > 0
const summary = row?.processInstance?.summary; ? cellValue
.map((item: any) => `${item.key} : ${item.value}`)
if (!summary || summary.length === 0) { .join('\n')
return '-'; : '-';
}
return summary.map((item: any) => {
return h(
'div',
{
key: item.key,
},
h(
'span',
{
class: 'text-gray-500',
},
`${item.key} : ${item.value}`,
),
);
});
},
}, },
}, },
{ {
@ -113,12 +87,6 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
title: '发起人', title: '发起人',
minWidth: 120, minWidth: 120,
}, },
{
field: 'createTime',
title: '发起时间',
minWidth: 180,
formatter: 'formatDateTime',
},
{ {
field: 'name', field: 'name',
title: '当前任务', title: '当前任务',
@ -141,26 +109,10 @@ export function useGridColumns<T = BpmTaskApi.TaskVO>(
minWidth: 280, minWidth: 280,
}, },
{ {
field: 'operation',
title: '操作', title: '操作',
minWidth: 120, width: 120,
align: 'center',
fixed: 'right', fixed: 'right',
cellRender: { slots: { default: 'actions' },
attrs: {
nameField: 'name',
nameTitle: '流程名称',
onClick: onActionClick,
},
name: 'CellOperation',
options: [
{
code: 'audit',
text: '办理',
show: hasAccessByCodes(['bpm:task:query']),
},
],
},
}, },
]; ];
} }

View File

@ -1,13 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { import type { VxeTableGridOptions } from '#/adapter/vxe-table';
OnActionClickParams,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmTaskApi } from '#/api/bpm/task'; import type { BpmTaskApi } from '#/api/bpm/task';
import { Page } from '@vben/common-ui'; import { Page } from '@vben/common-ui';
import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getTaskTodoPage } from '#/api/bpm/task'; import { getTaskTodoPage } from '#/api/bpm/task';
import { DocAlert } from '#/components/doc-alert'; import { DocAlert } from '#/components/doc-alert';
import { router } from '#/router'; import { router } from '#/router';
@ -16,12 +13,24 @@ import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'BpmTodoTask' }); defineOptions({ name: 'BpmTodoTask' });
const [Grid, gridApi] = useVbenVxeGrid({ /** 办理任务 */
function handleAudit(row: BpmTaskApi.TaskVO) {
console.warn(row);
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstance.id,
taskId: row.id,
},
});
}
const [Grid] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(onActionClick), columns: useGridColumns(),
height: 'auto', height: 'auto',
keepSource: true, keepSource: true,
proxyConfig: { proxyConfig: {
@ -47,33 +56,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
}, },
} as VxeTableGridOptions<BpmTaskApi.TaskVO>, } as VxeTableGridOptions<BpmTaskApi.TaskVO>,
}); });
/** 表格操作按钮的回调函数 */
function onActionClick({ code, row }: OnActionClickParams<BpmTaskApi.TaskVO>) {
switch (code) {
case 'audit': {
onAudit(row);
break;
}
}
}
/** 办理任务 */
function onAudit(row: BpmTaskApi.TaskVO) {
console.warn(row);
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstance.id,
taskId: row.id,
},
});
}
/** 刷新表格 */
function onRefresh() {
gridApi.query();
}
</script> </script>
<template> <template>
@ -90,6 +72,20 @@ function onRefresh() {
/> />
<DocAlert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" /> <DocAlert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
</template> </template>
<Grid table-title="" /> <Grid table-title="">
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '办理',
type: 'link',
icon: ACTION_ICON.VIEW,
auth: ['bpm:task:query'],
onClick: handleAudit.bind(null, row),
},
]"
/>
</template>
</Grid>
</Page> </Page>
</template> </template>

View File

@ -58,7 +58,6 @@ async function handleDelete(row: SystemTenantPackageApi.TenantPackage) {
const [Grid, gridApi] = useVbenVxeGrid({ const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { formOptions: {
schema: useGridFormSchema(), schema: useGridFormSchema(),
// TODO @
}, },
gridOptions: { gridOptions: {
columns: useGridColumns(), columns: useGridColumns(),