refactor: 重构 bpmnProcessDesigner 组件 ele => antd

This commit is contained in:
puhui999
2025-09-14 19:53:25 +08:00
parent 6ffd3dbd67
commit 9f1c3831fa
4 changed files with 217 additions and 398 deletions

View File

@@ -46,18 +46,18 @@
"@vueuse/core": "catalog:",
"@vueuse/integrations": "catalog:",
"ant-design-vue": "catalog:",
"bpmn-js": "^17.11.1",
"bpmn-js-properties-panel": "5.23.0",
"bpmn-js-token-simulation": "^0.36.3",
"camunda-bpmn-moddle": "^7.0.1",
"bpmn-js": "catalog:",
"bpmn-js-properties-panel": "catalog:",
"bpmn-js-token-simulation": "catalog:",
"camunda-bpmn-moddle": "catalog:",
"cropperjs": "catalog:",
"dayjs": "catalog:",
"diagram-js": "^12.8.1",
"fast-xml-parser": "^4.5.3",
"diagram-js": "catalog:",
"fast-xml-parser": "catalog:",
"highlight.js": "catalog:",
"min-dash": "^4.2.3",
"min-dash": "catalog:",
"pinia": "catalog:",
"steady-xml": "^0.1.0",
"steady-xml": "catalog:",
"tinymce": "catalog:",
"vue": "catalog:",
"vue-dompurify-html": "catalog:",

View File

@@ -1,9 +1,12 @@
<script lang="ts" setup>
import { nextTick, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue';
import { Icon } from '@vben/icons';
import { IconifyIcon } from '@vben/icons';
import { Collapse, CollapsePanel } from 'ant-design-vue';
import { Collapse } from 'ant-design-vue';
import ElementCustomConfig from '#/components/bpmnProcessDesigner/package/penal/custom-config/ElementCustomConfig.vue';
import ElementForm from '#/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue';
import ElementBaseInfo from './base/ElementBaseInfo.vue';
import FlowCondition from './flow-condition/FlowCondition.vue';
@@ -29,7 +32,7 @@ defineOptions({ name: 'MyPropertiesPanel' });
const props = defineProps({
bpmnModeler: {
type: Object,
default: () => {},
default: () => ({}),
},
prefix: {
type: String,
@@ -43,12 +46,21 @@ const props = defineProps({
type: Boolean,
default: false,
},
model: Object, // 流程模型的数据
businessObject: {
type: Object,
default: () => ({}),
},
model: {
type: Object,
default: () => ({}),
}, // 流程模型的数据
});
const CollapsePanel = Collapse.Panel;
const activeTab = ref('base');
const elementId = ref('');
const elementType = ref('');
const elementType = ref<any>('');
const elementBusinessObject = ref<any>({}); // 元素 businessObject 镜像,提供给需要做判断的组件使用
const conditionFormVisible = ref(false); // 流转条件设置
const formVisible = ref(false); // 表单配置
@@ -77,9 +89,7 @@ const initBpmnInstances = () => {
};
// 检查所有实例是否都存在
const allInstancesExist = Object.values(instances).every(
Boolean,
);
const allInstancesExist = Object.values(instances).every(Boolean);
if (allInstancesExist) {
const w = window as any;
w.bpmnInstances = instances;
@@ -95,12 +105,12 @@ const initBpmnInstances = () => {
const bpmnInstances = () => (window as any)?.bpmnInstances;
// 监听 props.bpmnModeler 然后 initModels
const unwatchBpmn = watch(
watch(
() => props.bpmnModeler,
async () => {
// 避免加载时 流程图 并未加载完成
if (!props.bpmnModeler) {
console.log('缺少props.bpmnModeler');
// console.log('缺少props.bpmnModeler');
return;
}
@@ -128,15 +138,18 @@ const getActiveElement = () => {
// 初始第一个选中元素 bpmn:Process
initFormOnChanged(null);
props.bpmnModeler.on('import.done', (e) => {
console.log(e, 'eeeee');
props.bpmnModeler.on('import.done', (_: any) => {
// console.log(e, 'eeeee');
initFormOnChanged(null);
});
// 监听选择事件,修改当前激活的元素以及表单
props.bpmnModeler.on('selection.changed', ({ newSelection }) => {
initFormOnChanged(newSelection[0] || null);
});
props.bpmnModeler.on('element.changed', ({ element }) => {
props.bpmnModeler.on(
'selection.changed',
({ newSelection }: { newSelection: any }) => {
initFormOnChanged(newSelection[0] || null);
},
);
props.bpmnModeler.on('element.changed', ({ element }: { element: any }) => {
// 保证 修改 "默认流转路径" 类似需要修改多个元素的事件发生的时候,更新表单的元素与原选中元素不一致。
if (element && element.id === elementId.value) {
initFormOnChanged(element);
@@ -145,42 +158,41 @@ const getActiveElement = () => {
};
// 初始化数据
const initFormOnChanged = (element) => {
const initFormOnChanged = (element: any) => {
if (!isReady.value || !bpmnInstances()) return;
let activatedElement = element;
if (!activatedElement) {
activatedElement =
bpmnInstances().elementRegistry.find(
(el) => el.type === 'bpmn:Process',
(el: any) => el.type === 'bpmn:Process',
) ??
bpmnInstances().elementRegistry.find(
(el) => el.type === 'bpmn:Collaboration',
(el: any) => el.type === 'bpmn:Collaboration',
);
}
if (!activatedElement) return;
try {
console.log(`
----------
select element changed:
id: ${activatedElement.id}
type: ${activatedElement.businessObject.$type}
----------
`);
console.log('businessObject:', activatedElement.businessObject);
// console.log(`
// ----------
// select element changed:
// id: ${activatedElement.id}
// type: ${activatedElement.businessObject.$type}
// ----------
// `);
// console.log('businessObject:', activatedElement.businessObject);
bpmnInstances().bpmnElement = activatedElement;
bpmnElement.value = activatedElement;
elementId.value = activatedElement.id;
elementType.value = activatedElement.type.split(':')[1] || '';
elementBusinessObject.value = JSON.parse(
JSON.stringify(activatedElement.businessObject),
elementBusinessObject.value = structuredClone(
activatedElement.businessObject,
);
conditionFormVisible.value = !!(
conditionFormVisible.value =
elementType.value === 'SequenceFlow' &&
activatedElement.source &&
!activatedElement.source.type.indexOf('StartEvent') === -1
);
(activatedElement.source.type as string).includes('StartEvent');
formVisible.value =
elementType.value === 'UserTask' || elementType.value === 'StartEvent';
} catch (error) {
@@ -200,46 +212,46 @@ watch(
activeTab.value = 'base';
},
);
function updateNode() {
const moddle = window.bpmnInstances?.moddle;
const modeling = window.bpmnInstances?.modeling;
const elementRegistry = window.bpmnInstances?.elementRegistry;
if (!moddle || !modeling || !elementRegistry) return;
const element = elementRegistry.get(props.businessObject.id);
if (!element) return;
const timerDef = moddle.create('bpmn:TimerEventDefinition', {});
switch (type.value) {
case 'cycle': {
timerDef.timeCycle = moddle.create('bpmn:FormalExpression', {
body: condition.value,
});
break;
}
case 'duration': {
timerDef.timeDuration = moddle.create('bpmn:FormalExpression', {
body: condition.value,
});
break;
}
case 'time': {
timerDef.timeDate = moddle.create('bpmn:FormalExpression', {
body: condition.value,
});
break;
}
// No default
}
modeling.updateModdleProperties(element, element.businessObject, {
eventDefinitions: [timerDef],
});
}
//
// function updateNode() {
// const moddle = window.bpmnInstances?.moddle;
// const modeling = window.bpmnInstances?.modeling;
// const elementRegistry = window.bpmnInstances?.elementRegistry;
// if (!moddle || !modeling || !elementRegistry) return;
//
// const element = elementRegistry.get(props.businessObject.id);
// if (!element) return;
//
// const timerDef = moddle.create('bpmn:TimerEventDefinition', {});
// switch (type.value) {
// case 'cycle': {
// timerDef.timeCycle = moddle.create('bpmn:FormalExpression', {
// body: condition.value,
// });
//
// break;
// }
// case 'duration': {
// timerDef.timeDuration = moddle.create('bpmn:FormalExpression', {
// body: condition.value,
// });
//
// break;
// }
// case 'time': {
// timerDef.timeDate = moddle.create('bpmn:FormalExpression', {
// body: condition.value,
// });
//
// break;
// }
// // No default
// }
//
// modeling.updateModdleProperties(element, element.businessObject, {
// eventDefinitions: [timerDef],
// });
// }
// 初始化和监听
function syncFromBusinessObject() {
@@ -269,9 +281,8 @@ watch(() => props.businessObject, syncFromBusinessObject, { deep: true });
>
<Collapse v-model:active-key="activeTab" v-if="isReady">
<CollapsePanel key="base" header="常规">
<template #title>
<Icon icon="ant-design:info-circle-filled" />
常规
<template #extra>
<IconifyIcon icon="ep:info-filled" />
</template>
<ElementBaseInfo
:id-edit-disabled="idEditDisabled"
@@ -282,51 +293,50 @@ watch(() => props.businessObject, syncFromBusinessObject, { deep: true });
</CollapsePanel>
<CollapsePanel
key="message"
header="消息与信号"
v-if="elementType === 'Process'"
>
<template #title>
<Icon icon="ant-design:message-filled" />
消息与信号
<template #extra>
<IconifyIcon icon="ep:comment" />
</template>
<SignalAndMassage />
</CollapsePanel>
<CollapsePanel
key="condition"
header="流转条件"
v-if="conditionFormVisible"
>
<template #title>
<Icon icon="ant-design:swap-right" />
流转条件
<template #extra>
<IconifyIcon icon="ep:promotion" />
</template>
<FlowCondition
:business-object="elementBusinessObject"
:type="elementType"
/>
</CollapsePanel>
<CollapsePanel key="form" v-if="formVisible">
<template #title>
<Icon icon="ant-design:unordered-list-outlined" />
表单
<CollapsePanel key="form" header="表单" v-if="formVisible">
<template #extra>
<IconifyIcon icon="ep:list" />
</template>
<element-form :id="elementId" :type="elementType" />
<ElementForm :id="elementId" :type="elementType" />
</CollapsePanel>
<CollapsePanel
key="task"
:header="getTaskCollapseItemName(elementType)"
v-if="isTaskCollapseItemShow(elementType)"
>
<template #title>
<Icon icon="ant-design:check-circle-filled" />
{{ getTaskCollapseItemName(elementType) }}
<template #extra>
<IconifyIcon icon="ep:checked" />
</template>
<ElementTask :id="elementId" :type="elementType" />
</CollapsePanel>
<CollapsePanel
key="multiInstance"
header="多人审批方式"
v-if="elementType.includes('Task')"
>
<template #title>
<Icon icon="ant-design:question-circle-filled" />
多人审批方式
<template #extra>
<IconifyIcon icon="ep:help-filled" />
</template>
<ElementMultiInstance
:id="elementId"
@@ -334,43 +344,39 @@ watch(() => props.businessObject, syncFromBusinessObject, { deep: true });
:type="elementType"
/>
</CollapsePanel>
<CollapsePanel key="listeners">
<template #title>
<Icon icon="ant-design:bell-filled" />
执行监听器
<CollapsePanel key="listeners" header="执行监听器">
<template #extra>
<IconifyIcon icon="ep:bell-filled" />
</template>
<ElementListeners :id="elementId" :type="elementType" />
</CollapsePanel>
<CollapsePanel
key="taskListeners"
header="任务监听器"
v-if="elementType === 'UserTask'"
>
<template #title>
<Icon icon="ant-design:bell-filled" />
任务监听器
<template #extra>
<IconifyIcon icon="ep:bell-filled" />
</template>
<UserTaskListeners :id="elementId" :type="elementType" />
</CollapsePanel>
<CollapsePanel key="extensions">
<template #title>
<Icon icon="ant-design:plus-circle-filled" />
扩展属性
<CollapsePanel key="extensions" header="扩展属性">
<template #extra>
<IconifyIcon icon="ep:circle-plus-filled" />
</template>
<ElementProperties :id="elementId" :type="elementType" />
</CollapsePanel>
<CollapsePanel key="other">
<template #title>
<Icon icon="ant-design:swap-right" />
其他
<CollapsePanel key="other" header="其他">
<template #extra>
<IconifyIcon icon="ep:promotion" />
</template>
<ElementOtherConfig :id="elementId" />
</CollapsePanel>
<CollapsePanel key="customConfig">
<template #title>
<Icon icon="ant-design:tool-filled" />
自定义配置
<CollapsePanel key="customConfig" header="自定义配置">
<template #extra>
<IconifyIcon icon="ep:tools" />
</template>
<element-custom-config
<ElementCustomConfig
:id="elementId"
:type="elementType"
:business-object="elementBusinessObject"
@@ -379,212 +385,11 @@ watch(() => props.businessObject, syncFromBusinessObject, { deep: true });
<!-- 新增的时间事件配置项 -->
<CollapsePanel
key="timeEvent"
header="时间事件"
v-if="elementType === 'IntermediateCatchEvent'"
>
<template #title>
<Icon icon="ant-design:clock-circle-filled" />
时间事件
</template>
<TimeEventConfig
:business-object="bpmnElement.value?.businessObject"
:key="elementId"
/>
</CollapsePanel>
</Collapse>
</div>
</template>.includes('StartEvent')
);
formVisible.value =
elementType.value === 'UserTask' || elementType.value === 'StartEvent';
} catch (error) {
console.error('初始化表单数据失败:', error);
}
};
onBeforeUnmount(() => {
const w = window as any;
w.bpmnInstances = null;
isReady.value = false;
});
watch(
() => elementId.value,
() => {
activeTab.value = 'base';
},
);
function updateNode() {
const moddle = window.bpmnInstances?.moddle;
const modeling = window.bpmnInstances?.modeling;
const elementRegistry = window.bpmnInstances?.elementRegistry;
if (!moddle || !modeling || !elementRegistry) return;
const element = elementRegistry.get(props.businessObject.id);
if (!element) return;
const timerDef = moddle.create('bpmn:TimerEventDefinition', {});
switch (type.value) {
case 'time': {
timerDef.timeDate = moddle.create('bpmn:FormalExpression', {
body: condition.value,
});
break;
}
case 'duration': {
timerDef.timeDuration = moddle.create('bpmn:FormalExpression', {
body: condition.value,
});
break;
}
case 'cycle': {
timerDef.timeCycle = moddle.create('bpmn:FormalExpression', {
body: condition.value,
});
break;
}
// No default
}
modeling.updateModdleProperties(element, element.businessObject, {
eventDefinitions: [timerDef],
});
}
// 初始化和监听
function syncFromBusinessObject() {
if (props.businessObject) {
const timerDef = (props.businessObject.eventDefinitions || [])[0];
if (timerDef) {
if (timerDef.timeDate) {
type.value = 'time';
condition.value = timerDef.timeDate.body;
} else if (timerDef.timeDuration) {
type.value = 'duration';
condition.value = timerDef.timeDuration.body;
} else if (timerDef.timeCycle) {
type.value = 'cycle';
condition.value = timerDef.timeCycle.body;
}
}
}
}
onMounted(syncFromBusinessObject);
watch(() => props.businessObject, syncFromBusinessObject, { deep: true });
</script>
<template>
<div
class="process-panel__container"
:style="{ width: `${width}px`, maxHeight: '600px' }"
>
<Collapse v-model:active-key="activeTab" v-if="isReady">
<CollapsePanel key="base" header="常规">
<template #title>
<Icon icon="ant-design:info-circle-filled" />
常规
</template>
<ElementBaseInfo
:id-edit-disabled="idEditDisabled"
:business-object="elementBusinessObject"
:type="elementType"
:model="model"
/>
</CollapsePanel>
<CollapsePanel key="message" v-if="elementType === 'Process'">
<template #title>
<Icon icon="ant-design:message-filled" />
消息与信号
</template>
<SignalAndMassage />
</CollapsePanel>
<CollapsePanel key="condition" v-if="conditionFormVisible">
<template #title>
<Icon icon="ant-design:swap-right" />
流转条件
</template>
<FlowCondition
:business-object="elementBusinessObject"
:type="elementType"
/>
</CollapsePanel>
<CollapsePanel key="form" v-if="formVisible">
<template #title>
<Icon icon="ant-design:unordered-list-outlined" />
表单
</template>
<element-form :id="elementId" :type="elementType" />
</CollapsePanel>
<CollapsePanel key="task" v-if="isTaskCollapseItemShow(elementType)">
<template #title>
<Icon icon="ant-design:check-circle-filled" />
{{ getTaskCollapseItemName(elementType) }}
</template>
<ElementTask :id="elementId" :type="elementType" />
</CollapsePanel>
<CollapsePanel
key="multiInstance"
v-if="elementType.includes('Task')"
>
<template #title>
<Icon icon="ant-design:question-circle-filled" />
多人审批方式
</template>
<ElementMultiInstance
:id="elementId"
:business-object="elementBusinessObject"
:type="elementType"
/>
</CollapsePanel>
<CollapsePanel key="listeners">
<template #title>
<Icon icon="ant-design:bell-filled" />
执行监听器
</template>
<ElementListeners :id="elementId" :type="elementType" />
</CollapsePanel>
<CollapsePanel key="taskListeners" v-if="elementType === 'UserTask'">
<template #title>
<Icon icon="ant-design:bell-filled" />
任务监听器
</template>
<UserTaskListeners :id="elementId" :type="elementType" />
</CollapsePanel>
<CollapsePanel key="extensions">
<template #title>
<Icon icon="ant-design:plus-circle-filled" />
扩展属性
</template>
<ElementProperties :id="elementId" :type="elementType" />
</CollapsePanel>
<CollapsePanel key="other">
<template #title>
<Icon icon="ant-design:swap-right" />
其他
</template>
<ElementOtherConfig :id="elementId" />
</CollapsePanel>
<CollapsePanel key="customConfig">
<template #title>
<Icon icon="ant-design:tool-filled" />
自定义配置
</template>
<element-custom-config
:id="elementId"
:type="elementType"
:business-object="elementBusinessObject"
/>
</CollapsePanel>
<!-- 新增的时间事件配置项 -->
<CollapsePanel
key="timeEvent"
v-if="elementType === 'IntermediateCatchEvent'"
>
<template #title>
<Icon icon="ant-design:clock-circle-filled" />
时间事件
<template #extra>
<IconifyIcon icon="ep:timer" />
</template>
<TimeEventConfig
:business-object="bpmnElement.value?.businessObject"

View File

@@ -1,15 +1,21 @@
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { Form } from 'ant-design-vue';
import { Checkbox, Form, FormItem } from 'ant-design-vue';
import { installedComponent } from './data';
defineOptions({ name: 'ElementTaskConfig' });
const props = defineProps({
id: String,
type: String,
id: {
type: String,
default: '',
},
type: {
type: String,
default: '',
},
});
const taskConfigForm = ref({
asyncAfter: false,
@@ -47,6 +53,7 @@ watch(
() => props.type,
() => {
if (props.type) {
// @ts-ignore
witchTaskComponent.value = installedComponent[props.type].component;
}
},
@@ -58,7 +65,7 @@ watch(
<div class="panel-tab__content">
<Form :label-col="{ span: 9 }" :wrapper-col="{ span: 15 }">
<!-- add by 芋艿由于异步延续暂时用不到所以这里 display none -->
<Form.Item label="异步延续" style="display: none">
<FormItem label="异步延续" style="display: none">
<Checkbox
v-model:checked="taskConfigForm.asyncBefore"
@change="changeTaskAsync"
@@ -78,7 +85,7 @@ watch(
>
排除
</Checkbox>
</Form.Item>
</FormItem>
<component :is="witchTaskComponent" v-bind="$props" />
</Form>
</div>