fix: [bpm][antd] BPM 设计器任务监听器问题修复

This commit is contained in:
jason
2025-12-03 23:55:28 +08:00
parent b94513dee4
commit 943a8e0cee

View File

@@ -1,23 +1,21 @@
<script lang="ts" setup> <script lang="ts" setup>
import { inject, nextTick, ref, watch } from 'vue'; import { inject, nextTick, ref, watch } from 'vue';
import { confirm, useVbenDrawer, useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons'; import { IconifyIcon } from '@vben/icons';
import { cloneDeep } from '@vben/utils'; import { cloneDeep } from '@vben/utils';
import { import {
Button, Button,
Divider, Divider,
Drawer,
Form, Form,
FormItem, FormItem,
Input, Input,
Modal,
Select, Select,
SelectOption, SelectOption,
Table,
TableColumn,
} from 'ant-design-vue'; } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import ProcessListenerDialog from '#/views/bpm/components/bpmn-process-designer/package/penal/listeners/ProcessListenerDialog.vue'; import ProcessListenerDialog from '#/views/bpm/components/bpmn-process-designer/package/penal/listeners/ProcessListenerDialog.vue';
import { createListenerObject, updateElementExtensions } from '../../utils'; import { createListenerObject, updateElementExtensions } from '../../utils';
@@ -40,47 +38,30 @@ interface Props {
} }
const prefix = inject<string>('prefix'); const prefix = inject<string>('prefix');
const width = inject<number>('width');
const elementListenersList = ref<any[]>([]); const elementListenersList = ref<any[]>([]);
const listenerEventTypeObject = ref(eventType); const listenerEventTypeObject = ref(eventType);
const listenerTypeObject = ref(listenerType); const listenerTypeObject = ref(listenerType);
const listenerFormModelVisible = ref(false);
const listenerForm = ref<any>({}); const listenerForm = ref<any>({});
const fieldTypeObject = ref(fieldType); const fieldTypeObject = ref(fieldType);
const fieldsListOfListener = ref<any[]>([]); const fieldsListOfListener = ref<any[]>([]);
const listenerFieldFormModelVisible = ref(false); // 监听器 注入字段表单弹窗 显示状态 const editingListenerIndex = ref(-1);
const editingListenerIndex = ref(-1); // 监听器所在下标,-1 为新增 const editingListenerFieldIndex = ref<any>(-1);
const editingListenerFieldIndex = ref<any>(-1); // 字段所在下标,-1 为新增 const listenerFieldForm = ref<any>({});
const listenerFieldForm = ref<any>({}); // 监听器 注入字段 详情表单
const bpmnElement = ref<any>(); const bpmnElement = ref<any>();
const bpmnElementListeners = ref<any[]>([]); const bpmnElementListeners = ref<any[]>([]);
const otherExtensionList = ref<any[]>([]); const otherExtensionList = ref<any[]>([]);
const listenerFormRef = ref<any>({}); const listenerFormRef = ref<any>({});
const listenerFieldFormRef = ref<any>({}); const listenerFieldFormRef = ref<any>({});
interface BpmnInstances { const bpmnInstances = () => (window as any)?.bpmnInstances;
bpmnElement: any;
[key: string]: any;
}
declare global {
interface Window {
bpmnInstances?: BpmnInstances;
}
}
const bpmnInstances = () => window.bpmnInstances;
const resetListenersList = () => { const resetListenersList = () => {
// console.log(
// bpmnInstances().bpmnElement,
// 'window.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElementwindow.bpmnInstances.bpmnElement',
// );
bpmnElement.value = bpmnInstances()?.bpmnElement; bpmnElement.value = bpmnInstances()?.bpmnElement;
otherExtensionList.value = otherExtensionList.value =
bpmnElement.value.businessObject?.extensionElements?.values?.filter( bpmnElement.value.businessObject?.extensionElements?.values?.filter(
(ex: any) => ex.$type !== `${prefix}:TaskListener`, (ex: any) => ex.$type !== `${prefix}:TaskListener`,
) ?? []; // 保留非监听器类型的扩展属性避免移除监听器时清空其他配置如审批人等。相关案例https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues/ICMSYC ) ?? [];
bpmnElementListeners.value = bpmnElementListeners.value =
bpmnElement.value.businessObject?.extensionElements?.values?.filter( bpmnElement.value.businessObject?.extensionElements?.values?.filter(
(ex: any) => ex.$type === `${prefix}:TaskListener`, (ex: any) => ex.$type === `${prefix}:TaskListener`,
@@ -89,13 +70,14 @@ const resetListenersList = () => {
initListenerType(listener), initListenerType(listener),
); );
}; };
const openListenerForm = (listener: any, index?: number) => { const openListenerForm = (listener: any, index?: number) => {
if (listener) { if (listener) {
listenerForm.value = initListenerForm(listener); listenerForm.value = initListenerForm(listener);
editingListenerIndex.value = index || -1; editingListenerIndex.value = index || -1;
} else { } else {
listenerForm.value = {}; listenerForm.value = {};
editingListenerIndex.value = -1; // 标记为新增 editingListenerIndex.value = -1;
} }
if (listener && listener.fields) { if (listener && listener.fields) {
fieldsListOfListener.value = listener.fields.map((field: any) => ({ fieldsListOfListener.value = listener.fields.map((field: any) => ({
@@ -106,37 +88,32 @@ const openListenerForm = (listener: any, index?: number) => {
fieldsListOfListener.value = []; fieldsListOfListener.value = [];
listenerForm.value.fields = []; listenerForm.value.fields = [];
} }
// 打开侧边栏并清楚验证状态 listenerDrawerApi.open();
listenerFormModelVisible.value = true;
nextTick(() => { nextTick(() => {
if (listenerFormRef.value) listenerFormRef.value.clearValidate(); if (listenerFormRef.value) listenerFormRef.value.clearValidate();
}); });
}; };
// 移除监听器
const removeListener = (_: any, index: number) => { const removeListener = (_: any, index: number) => {
// console.log(listener, 'listener'); confirm({
Modal.confirm({
title: '提示', title: '提示',
content: '确认移除该监听器吗?', content: '确认移除该监听器吗?',
okText: '确 认', }).then(() => {
cancelText: '取 消',
onOk() {
bpmnElementListeners.value.splice(index, 1); bpmnElementListeners.value.splice(index, 1);
elementListenersList.value.splice(index, 1); elementListenersList.value.splice(index, 1);
updateElementExtensions(bpmnElement.value, [ updateElementExtensions(bpmnElement.value, [
...otherExtensionList.value, ...otherExtensionList.value,
...bpmnElementListeners.value, ...bpmnElementListeners.value,
]); ]);
},
onCancel() {
// console.info('操作取消');
},
}); });
}; };
// 保存监听器
const saveListenerConfig = async () => { async function saveListenerConfig() {
const validateStatus = await listenerFormRef.value.validate(); try {
if (!validateStatus) return; // 验证不通过直接返回 await listenerFormRef.value.validate();
} catch {
return;
}
const listenerObject = createListenerObject(listenerForm.value, true, prefix); const listenerObject = createListenerObject(listenerForm.value, true, prefix);
if (editingListenerIndex.value === -1) { if (editingListenerIndex.value === -1) {
bpmnElementListeners.value.push(listenerObject); bpmnElementListeners.value.push(listenerObject);
@@ -153,7 +130,6 @@ const saveListenerConfig = async () => {
listenerForm.value, listenerForm.value,
); );
} }
// 保存其他配置
otherExtensionList.value = otherExtensionList.value =
bpmnElement.value.businessObject?.extensionElements?.values?.filter( bpmnElement.value.businessObject?.extensionElements?.values?.filter(
(ex: any) => ex.$type !== `${prefix}:TaskListener`, (ex: any) => ex.$type !== `${prefix}:TaskListener`,
@@ -162,63 +138,92 @@ const saveListenerConfig = async () => {
...otherExtensionList.value, ...otherExtensionList.value,
...bpmnElementListeners.value, ...bpmnElementListeners.value,
]); ]);
// 4. 隐藏侧边栏 listenerDrawerApi.close();
listenerFormModelVisible.value = false;
listenerForm.value = {}; listenerForm.value = {};
}; }
// 打开监听器字段编辑弹窗
const openListenerFieldForm = (field: any, index?: number) => { const openListenerFieldForm = (field: any, index?: number) => {
listenerFieldForm.value = field ? cloneDeep(field) : {}; listenerFieldForm.value = field ? cloneDeep(field) : {};
editingListenerFieldIndex.value = field ? index : -1; editingListenerFieldIndex.value = field ? index : -1;
listenerFieldFormModelVisible.value = true; fieldModalApi.open();
nextTick(() => { nextTick(() => {
if (listenerFieldFormRef.value) listenerFieldFormRef.value.clearValidate(); if (listenerFieldFormRef.value) listenerFieldFormRef.value.clearValidate();
}); });
}; };
// 保存监听器注入字段
const saveListenerFiled = async () => { const [ListenerGrid, listenerGridApi] = useVbenVxeGrid({
const validateStatus = await listenerFieldFormRef.value.validate(); gridOptions: {
if (!validateStatus) return; // 验证不通过直接返回 columns: [
{ type: 'seq', width: 50, title: '序号' },
{
field: 'event',
title: '事件类型',
minWidth: 80,
formatter: ({ cellValue }: { cellValue: string }) =>
(listenerEventTypeObject.value as Record<string, any>)[cellValue],
},
{ field: 'id', title: '事件id', minWidth: 80, showOverflow: true },
{
field: 'listenerType',
title: '监听器类型',
minWidth: 80,
formatter: ({ cellValue }: { cellValue: string }) =>
(listenerTypeObject.value as Record<string, any>)[cellValue],
},
{
title: '操作',
width: 120,
slots: { default: 'action' },
fixed: 'right',
},
],
border: true,
showOverflow: true,
height: 'auto',
toolbarConfig: {
enabled: false,
},
pagerConfig: {
enabled: false,
},
},
});
async function saveListenerField() {
try {
await listenerFieldFormRef.value.validate();
if (editingListenerFieldIndex.value === -1) { if (editingListenerFieldIndex.value === -1) {
fieldsListOfListener.value.push(listenerFieldForm.value); fieldsListOfListener.value.push(cloneDeep(listenerFieldForm.value));
listenerForm.value.fields.push(listenerFieldForm.value); listenerForm.value.fields.push(cloneDeep(listenerFieldForm.value));
} else { } else {
fieldsListOfListener.value.splice( fieldsListOfListener.value.splice(
editingListenerFieldIndex.value, editingListenerFieldIndex.value,
1, 1,
listenerFieldForm.value, cloneDeep(listenerFieldForm.value),
); );
listenerForm.value.fields.splice( listenerForm.value.fields.splice(
editingListenerFieldIndex.value, editingListenerFieldIndex.value,
1, 1,
listenerFieldForm.value, cloneDeep(listenerFieldForm.value),
); );
} }
listenerFieldFormModelVisible.value = false; fieldModalApi.close();
nextTick(() => {
listenerFieldForm.value = {}; listenerFieldForm.value = {};
}); } catch (error) {
}; console.error(error);
// 移除监听器字段 }
}
const removeListenerField = (_: any, index: number) => { const removeListenerField = (_: any, index: number) => {
// console.log(field, 'field'); confirm({
Modal.confirm({
title: '提示', title: '提示',
content: '确认移除该字段吗?', content: '确认移除该字段吗?',
okText: '确 认', }).then(() => {
cancelText: '取 消',
onOk() {
fieldsListOfListener.value.splice(index, 1); fieldsListOfListener.value.splice(index, 1);
listenerForm.value.fields.splice(index, 1); listenerForm.value.fields.splice(index, 1);
},
onCancel() {
// console.info('操作取消');
},
}); });
}; };
// 打开监听器弹窗
const processListenerDialogRef = ref<any>(); const processListenerDialogRef = ref<any>();
const openProcessListenerDialog = async () => { const openProcessListenerDialog = async () => {
processListenerDialogRef.value.open('task'); processListenerDialogRef.value.open('task');
@@ -229,7 +234,6 @@ const selectProcessListener = (listener: any) => {
bpmnElementListeners.value.push(listenerObject); bpmnElementListeners.value.push(listenerObject);
elementListenersList.value.push(listenerForm); elementListenersList.value.push(listenerForm);
// 保存其他配置
otherExtensionList.value = otherExtensionList.value =
bpmnElement.value.businessObject?.extensionElements?.values?.filter( bpmnElement.value.businessObject?.extensionElements?.values?.filter(
(ex: any) => ex.$type !== `${prefix}:TaskListener`, (ex: any) => ex.$type !== `${prefix}:TaskListener`,
@@ -240,6 +244,69 @@ const selectProcessListener = (listener: any) => {
); );
}; };
const [ListenerDrawer, listenerDrawerApi] = useVbenDrawer({
title: '任务监听器',
destroyOnClose: true,
onConfirm: saveListenerConfig,
});
const [FieldModal, fieldModalApi] = useVbenModal({
title: '字段配置',
onConfirm: saveListenerField,
});
const [FieldsGrid, fieldsGridApi] = useVbenVxeGrid({
gridOptions: {
columns: [
{ type: 'seq', width: 50, title: '序号' },
{ field: 'name', title: '字段名称', minWidth: 100 },
{
field: 'fieldType',
title: '字段类型',
width: 80,
formatter: ({ cellValue }: { cellValue: string }) =>
fieldTypeObject.value[cellValue as keyof typeof fieldType],
},
{
title: '字段值/表达式',
width: 100,
formatter: ({ row }: { row: any }) => row.string || row.expression,
},
{
title: '操作',
width: 120,
slots: { default: 'action' },
fixed: 'right',
},
],
border: true,
showOverflow: true,
minHeight: 200,
toolbarConfig: {
enabled: false,
},
pagerConfig: {
enabled: false,
},
},
});
watch(
elementListenersList,
(val) => {
listenerGridApi.setGridOptions({ data: val });
},
{ deep: true },
);
watch(
fieldsListOfListener,
(val) => {
fieldsGridApi.setGridOptions({ data: val });
},
{ deep: true },
);
watch( watch(
() => props.id, () => props.id,
(val) => { (val) => {
@@ -253,39 +320,13 @@ watch(
); );
</script> </script>
<template> <template>
<div class="panel-tab__content"> <div class="-mx-2">
<Table :data="elementListenersList" size="small" bordered> <ListenerGrid>
<TableColumn title="序号" width="50px" type="index" /> <template #action="{ row, rowIndex }">
<TableColumn
title="事件类型"
width="80px"
:ellipsis="{ showTitle: true }"
:custom-render="
({ record }: any) =>
listenerEventTypeObject[record.event as keyof typeof eventType]
"
/>
<TableColumn
title="事件id"
width="80px"
data-index="id"
:ellipsis="{ showTitle: true }"
/>
<TableColumn
title="监听器类型"
width="80px"
:ellipsis="{ showTitle: true }"
:custom-render="
({ record }: any) =>
listenerTypeObject[record.listenerType as keyof typeof listenerType]
"
/>
<TableColumn title="操作" width="90px">
<template #default="{ record, index }">
<Button <Button
size="small" size="small"
type="link" type="link"
@click="openListenerForm(record, index)" @click="openListenerForm(row, rowIndex)"
> >
编辑 编辑
</Button> </Button>
@@ -294,32 +335,41 @@ watch(
size="small" size="small"
type="link" type="link"
danger danger
@click="removeListener(record, index)" @click="removeListener(row, rowIndex)"
> >
移除 移除
</Button> </Button>
</template> </template>
</TableColumn> </ListenerGrid>
</Table> <div class="mt-1 flex w-full items-center justify-center gap-2 px-2">
<div class="element-drawer__button"> <Button
<Button size="small" type="primary" @click="openListenerForm(null)"> class="flex flex-1 items-center justify-center"
size="small"
type="primary"
@click="openListenerForm(null)"
>
<template #icon> <IconifyIcon icon="ep:plus" /></template> <template #icon> <IconifyIcon icon="ep:plus" /></template>
添加监听器 添加监听器
</Button> </Button>
<Button size="small" @click="openProcessListenerDialog"> <Button
class="flex flex-1 items-center justify-center"
size="small"
@click="openProcessListenerDialog"
>
<template #icon> <IconifyIcon icon="ep:select" /></template> <template #icon> <IconifyIcon icon="ep:select" /></template>
选择监听器 选择监听器
</Button> </Button>
</div> </div>
<!-- 监听器 编辑/创建 部分 --> <!-- 监听器 编辑/创建 部分 -->
<Drawer <ListenerDrawer class="w-2/5">
v-model:open="listenerFormModelVisible" <template #default>
title="任务监听器" <Form
:width="width" :label-col="{ span: 6 }"
:destroy-on-close="true" :model="listenerForm"
:wrapper-col="{ span: 18 }"
ref="listenerFormRef"
> >
<Form :model="listenerForm" ref="listenerFormRef">
<FormItem <FormItem
label="事件类型" label="事件类型"
name="event" name="event"
@@ -382,7 +432,10 @@ watch(
key="listener-delegate" key="listener-delegate"
:rules="[{ required: true, message: '请输入代理表达式' }]" :rules="[{ required: true, message: '请输入代理表达式' }]"
> >
<Input v-model:value="listenerForm.delegateExpression" allow-clear /> <Input
v-model:value="listenerForm.delegateExpression"
allow-clear
/>
</FormItem> </FormItem>
<template v-if="listenerForm.listenerType === 'scriptListener'"> <template v-if="listenerForm.listenerType === 'scriptListener'">
<FormItem <FormItem
@@ -462,48 +515,23 @@ watch(
注入字段 注入字段
</span> </span>
<Button <Button
type="primary" class="flex items-center"
title="添加字段" size="small"
type="link"
@click="openListenerFieldForm(null)" @click="openListenerFieldForm(null)"
> >
<template #icon> <template #icon>
<IconifyIcon icon="ep:plus" /> <IconifyIcon class="size-4" icon="ep:plus" />
</template> </template>
添加字段 添加字段
</Button> </Button>
</div> </div>
<Table <FieldsGrid>
:data="fieldsListOfListener" <template #action="{ row, rowIndex }">
size="small"
:scroll="{ y: 240 }"
bordered
style="flex: none"
>
<TableColumn title="序号" width="50px" type="index" />
<TableColumn title="字段名称" width="100px" data-index="name" />
<TableColumn
title="字段类型"
width="80px"
:ellipsis="{ showTitle: true }"
:custom-render="
({ record }: any) =>
fieldTypeObject[record.fieldType as keyof typeof fieldType]
"
/>
<TableColumn
title="字段值/表达式"
width="100px"
:ellipsis="{ showTitle: true }"
:custom-render="
({ record }: any) => record.string || record.expression
"
/>
<TableColumn title="操作" width="100px">
<template #default="{ record, index }">
<Button <Button
size="small" size="small"
type="link" type="link"
@click="openListenerFieldForm(record, index)" @click="openListenerFieldForm(row, rowIndex)"
> >
编辑 编辑
</Button> </Button>
@@ -512,32 +540,23 @@ watch(
size="small" size="small"
type="link" type="link"
danger danger
@click="removeListenerField(record, index)" @click="removeListenerField(row, rowIndex)"
> >
移除 移除
</Button> </Button>
</template> </template>
</TableColumn> </FieldsGrid>
</Table> </template>
</ListenerDrawer>
<div class="element-drawer__button">
<Button size="small" @click="listenerFormModelVisible = false">
取 消
</Button>
<Button size="small" type="primary" @click="saveListenerConfig">
保 存
</Button>
</div>
</Drawer>
<!-- 注入字段 编辑/创建 部分 --> <!-- 注入字段 编辑/创建 部分 -->
<Modal <FieldModal class="w-3/5">
title="字段配置" <Form
v-model:open="listenerFieldFormModelVisible" :label-col="{ span: 4 }"
:width="600" :wrapper-col="{ span: 18 }"
:destroy-on-close="true" :model="listenerFieldForm"
ref="listenerFieldFormRef"
> >
<Form :model="listenerFieldForm" ref="listenerFieldFormRef">
<FormItem <FormItem
label="字段名称:" label="字段名称:"
name="name" name="name"
@@ -579,15 +598,7 @@ watch(
<Input v-model:value="listenerFieldForm.expression" allow-clear /> <Input v-model:value="listenerFieldForm.expression" allow-clear />
</FormItem> </FormItem>
</Form> </Form>
<template #footer> </FieldModal>
<Button size="small" @click="listenerFieldFormModelVisible = false">
取 消
</Button>
<Button size="small" type="primary" @click="saveListenerFiled">
确 定
</Button>
</template>
</Modal>
</div> </div>
<!-- 选择弹窗 --> <!-- 选择弹窗 -->