From 3f779091be7d58de8b5b910c4383a361d1775a9c Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 20 Jun 2026 19:43:55 -0700 Subject: [PATCH] =?UTF-8?q?fix(ts):=20=E4=BF=AE=E5=A4=8D=20BPMN=20?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E5=99=A8=E7=B1=BB=E5=9E=8B=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 设计器/预览补 DOM 空值保护与局部 service 类型(canvas/elementRegistry) - UserTaskCustomConfig/ElementForm/ElementMultiInstance 等补 ref/数组/element 类型 - ProcessListener/Expression Dialog 补声明实际已 emit 的 select 事件 - UserTask 删除 return 后不可达旧代码 - ElementMultiInstance:loopCharacteristics 按需写入,不影响 ApproveMethod 扩展保存 - 运行时:keyboard 配置 { bindTo: document } → { bind: true },适配 diagram-js 15 --- .../package/designer/ProcessDesigner.vue | 9 ++-- .../package/designer/ProcessViewer.vue | 29 ++++++++---- .../components/BoundaryEventTimer.vue | 2 +- .../components/UserTaskCustomConfig.vue | 45 +++++++++++++------ .../package/penal/form/ElementForm.vue | 6 +-- .../penal/listeners/ProcessListenerDialog.vue | 4 +- .../multi-instance/ElementMultiInstance.vue | 17 ++++--- .../ProcessExpressionDialog.vue | 4 +- .../penal/task/task-components/UserTask.vue | 15 +++---- 9 files changed, 85 insertions(+), 46 deletions(-) diff --git a/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue b/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue index c0e39efc7..a7a53af9b 100644 --- a/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue +++ b/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue @@ -405,7 +405,10 @@ console.log(additionalModules, 'additionalModules()') console.log(moddleExtensions, 'moddleExtensions()') const initBpmnModeler = () => { if (bpmnModeler) return - let data = document.getElementById('bpmnCanvas') + const data = document.getElementById('bpmnCanvas') + if (!data) { + return + } console.log(data, 'data') console.log(props.keyboard, 'props.keyboard') console.log(additionalModules, 'additionalModules()') @@ -422,9 +425,9 @@ const initBpmnModeler = () => { // propertiesPanel: { // parent: '#js-properties-panel' // }, - keyboard: props.keyboard ? { bindTo: document } : null, + keyboard: props.keyboard ? { bind: true } : null, // additionalModules: additionalModules.value, - additionalModules: additionalModules.value, + additionalModules: additionalModules.value as any[], moddleExtensions: moddleExtensions.value // additionalModules: [ diff --git a/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue b/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue index e78d5c701..a5e4e21ee 100644 --- a/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue +++ b/src/components/bpmnProcessDesigner/package/designer/ProcessViewer.vue @@ -140,6 +140,8 @@ import '../theme/index.scss' import BpmnViewer from 'bpmn-js/lib/Viewer' import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas' +import type Canvas from 'diagram-js/lib/core/Canvas' +import type ElementRegistry from 'diagram-js/lib/core/ElementRegistry' import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue' import { DICT_TYPE } from '@/utils/dict' import { dateFormatter, formatPast2 } from '@/utils/formatTime' @@ -170,10 +172,18 @@ const dialogTitle = ref(undefined) // 弹窗标题 const selectActivityType = ref(undefined) // 选中 Task 的活动编号 const selectTasks = ref([]) // 选中的任务数组 +type BpmnCanvas = Omit & { + _svg?: SVGSVGElement + zoom: (newScale?: number | 'fit-viewport', center?: 'auto' | { x: number; y: number }) => number +} + +const getCanvas = () => bpmnViewer.value?.get('canvas') +const getElementRegistry = () => bpmnViewer.value?.get('elementRegistry') + /** Zoom:恢复 */ const processReZoom = () => { defaultZoom.value = 1 - bpmnViewer.value?.get('canvas').zoom('fit-viewport', 'auto') + getCanvas()?.zoom('fit-viewport', 'auto') } let resizeObserver: ResizeObserver | null = null @@ -218,7 +228,7 @@ const processZoomIn = (zoomStep = 0.1) => { throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4') } defaultZoom.value = newZoom - bpmnViewer.value?.get('canvas').zoom(defaultZoom.value) + getCanvas()?.zoom(defaultZoom.value) } /** Zoom:缩小 */ @@ -228,7 +238,7 @@ const processZoomOut = (zoomStep = 0.1) => { throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2') } defaultZoom.value = newZoom - bpmnViewer.value?.get('canvas').zoom(defaultZoom.value) + getCanvas()?.zoom(defaultZoom.value) } /** 流程图预览清空 */ @@ -249,9 +259,9 @@ const addCustomDefs = () => { if (!bpmnViewer.value) { return } - const canvas = bpmnViewer.value?.get('canvas') + const canvas = getCanvas() const svg = canvas?._svg - svg.appendChild(customDefs.value) + svg?.appendChild(customDefs.value) } /** 节点选中 */ @@ -340,8 +350,11 @@ const setProcessStatus = (view: any) => { finishedSequenceFlowActivityIds, rejectedTaskActivityIds } = view - const canvas = bpmnViewer.value.get('canvas') - const elementRegistry = bpmnViewer.value.get('elementRegistry') + const canvas = getCanvas() + const elementRegistry = getElementRegistry() + if (!canvas || !elementRegistry) { + return + } // 已完成节点 if (Array.isArray(finishedSequenceFlowActivityIds)) { @@ -349,7 +362,7 @@ const setProcessStatus = (view: any) => { if (item != null) { canvas.addMarker(item, 'success') const element = elementRegistry.get(item) - const conditionExpression = element.businessObject.conditionExpression + const conditionExpression = element?.businessObject.conditionExpression if (conditionExpression) { canvas.addMarker(item, 'condition-expression') } diff --git a/src/components/bpmnProcessDesigner/package/penal/custom-config/components/BoundaryEventTimer.vue b/src/components/bpmnProcessDesigner/package/penal/custom-config/components/BoundaryEventTimer.vue index 77948ce0e..c8c82d2d3 100644 --- a/src/components/bpmnProcessDesigner/package/penal/custom-config/components/BoundaryEventTimer.vue +++ b/src/components/bpmnProcessDesigner/package/penal/custom-config/components/BoundaryEventTimer.vue @@ -101,7 +101,7 @@ const maxRemindCount = ref(1) const elExtensionElements = ref() const otherExtensions = ref() -const configExtensions = ref([]) +const configExtensions = ref([]) const eventDefinition = ref() const resetElement = () => { diff --git a/src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue b/src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue index 3901a99c2..e17f691cd 100644 --- a/src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue +++ b/src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue @@ -215,6 +215,18 @@ import * as UserApi from '@/api/system/user' import { useFormFieldsPermission } from '@/components/SimpleProcessDesignerV2/src/node' import { BpmModelFormType } from '@/utils/constants' +type BpmnElement = { + id: string + type: string + businessObject: Record + source?: BpmnElement + target?: BpmnElement +} +type ReturnTask = Record & { + id: string + name?: string +} + defineOptions({ name: 'ElementCustomConfig4UserTask' }) const props = defineProps({ id: String, @@ -231,16 +243,16 @@ const rejectHandlerTypeEl = ref() const rejectHandlerType = ref() const returnNodeIdEl = ref() const returnNodeId = ref() -const returnTaskList = ref([]) +const returnTaskList = ref([]) // 审批人为空时 const assignEmptyHandlerTypeEl = ref() const assignEmptyHandlerType = ref() const assignEmptyUserIdsEl = ref() -const assignEmptyUserIds = ref() +const assignEmptyUserIds = ref>([]) // 操作按钮 -const buttonsSettingEl = ref() +const buttonsSettingEl = ref([]) const { btnDisplayNameEdit, changeBtnDisplayName } = useButtonsSetting() const btnDisplayNameBlurEvent = (index: number) => { btnDisplayNameEdit.value[index] = false @@ -250,7 +262,7 @@ const btnDisplayNameBlurEvent = (index: number) => { } // 字段权限 -const fieldsPermissionEl = ref([]) +const fieldsPermissionEl = ref([]) const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFieldsPermission( FieldPermissionType.READ ) @@ -451,18 +463,20 @@ watch( { immediate: true } ) -function findAllPredecessorsExcludingStart(elementId, modeler) { +function findAllPredecessorsExcludingStart(elementId: string, modeler: any) { const elementRegistry = modeler.get('elementRegistry') - const allConnections = elementRegistry.filter((element) => element.type === 'bpmn:SequenceFlow') - const predecessors = new Set() // 使用 Set 来避免重复节点 - const visited = new Set() // 用于记录已访问的节点 + const allConnections = elementRegistry.filter( + (element: BpmnElement) => element.type === 'bpmn:SequenceFlow' + ) + const predecessors = new Set>() // 使用 Set 来避免重复节点 + const visited = new Set() // 用于记录已访问的节点 // 检查是否是开始事件节点 - function isStartEvent(element) { + function isStartEvent(element: BpmnElement) { return element.type === 'bpmn:StartEvent' } - function findPredecessorsRecursively(element) { + function findPredecessorsRecursively(element: BpmnElement) { // 如果该节点已经访问过,直接返回,避免循环 if (visited.has(element)) { return @@ -472,10 +486,15 @@ function findAllPredecessorsExcludingStart(elementId, modeler) { visited.add(element) // 获取与当前节点相连的所有连接 - const incomingConnections = allConnections.filter((connection) => connection.target === element) + const incomingConnections = allConnections.filter( + (connection: BpmnElement) => connection.target === element + ) - incomingConnections.forEach((connection) => { + incomingConnections.forEach((connection: BpmnElement) => { const source = connection.source // 获取前置节点 + if (!source) { + return + } // 只添加不是开始事件的前置节点 if (!isStartEvent(source)) { @@ -491,7 +510,7 @@ function findAllPredecessorsExcludingStart(elementId, modeler) { findPredecessorsRecursively(targetElement) } - return Array.from(predecessors) // 返回前置节点数组 + return Array.from(predecessors) as ReturnTask[] // 返回前置节点数组 } function useButtonsSetting() { diff --git a/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue b/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue index 0ec2ad853..6a2c94166 100644 --- a/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue +++ b/src/components/bpmnProcessDesigner/package/penal/form/ElementForm.vue @@ -236,7 +236,7 @@ const props = defineProps({ }) const prefix = inject('prefix') -const formKey = ref(undefined) +const formKey = ref() const bpmnELement = ref() const elExtensionElements = ref() const formData = ref() @@ -282,10 +282,10 @@ const updateElementExtensions = () => { }) } -const formList = ref([]) // 流程表单的下拉框的数据 +const formList = ref>([]) // 流程表单的下拉框的数据 onMounted(async () => { formList.value = await FormApi.getFormSimpleList() - formKey.value = parseInt(formKey.value) + formKey.value = formKey.value != null ? Number(formKey.value) : undefined }) watch( diff --git a/src/components/bpmnProcessDesigner/package/penal/listeners/ProcessListenerDialog.vue b/src/components/bpmnProcessDesigner/package/penal/listeners/ProcessListenerDialog.vue index 21088abfa..4e10e2e6e 100644 --- a/src/components/bpmnProcessDesigner/package/penal/listeners/ProcessListenerDialog.vue +++ b/src/components/bpmnProcessDesigner/package/penal/listeners/ProcessListenerDialog.vue @@ -76,8 +76,8 @@ const getList = async () => { } /** 提交表单 */ -const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 -const select = async (row) => { +const emit = defineEmits(['success', 'select']) // 定义 success/select 事件,用于操作成功后的回调 +const select = async (row: ProcessListenerVO) => { dialogVisible.value = false // 发送操作成功的事件 emit('select', row) diff --git a/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue b/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue index 0e7097b5f..67206cf69 100644 --- a/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue +++ b/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue @@ -117,8 +117,8 @@ const props = defineProps({ const prefix = inject('prefix') const loopCharacteristics = ref('') const loopInstanceForm = ref({}) -const bpmnElement = ref(null) -const multiLoopInstance = ref(null) +const bpmnElement = ref(null) +const multiLoopInstance = ref(null) const bpmnInstances = () => (window as any)?.bpmnInstances const changeLoopCharacteristicsType = (type) => { @@ -261,7 +261,7 @@ const approveMethod = ref() const approveRatio = ref(100) const otherExtensions = ref() const getElementLoopNew = () => { - if (props.type === 'UserTask') { + if (props.type === 'UserTask' && bpmnElement.value) { const extensionElements = bpmnElement.value.businessObject?.extensionElements ?? bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] }) @@ -286,6 +286,9 @@ const onApproveRatioChange = () => { updateLoopCharacteristics() } const updateLoopCharacteristics = () => { + if (!bpmnElement.value) { + return + } // 根据ApproveMethod生成multiInstanceLoopCharacteristics节点 if (approveMethod.value === ApproveMethodType.RANDOM_SELECT_ONE_APPROVE) { bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { @@ -334,9 +337,11 @@ const updateLoopCharacteristics = () => { } ) } - bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { - loopCharacteristics: toRaw(multiLoopInstance.value) - }) + if (multiLoopInstance.value) { + bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { + loopCharacteristics: toRaw(multiLoopInstance.value) + }) + } } // 添加ApproveMethod到ExtensionElements diff --git a/src/components/bpmnProcessDesigner/package/penal/task/task-components/ProcessExpressionDialog.vue b/src/components/bpmnProcessDesigner/package/penal/task/task-components/ProcessExpressionDialog.vue index a038e69bc..1bc526a6c 100644 --- a/src/components/bpmnProcessDesigner/package/penal/task/task-components/ProcessExpressionDialog.vue +++ b/src/components/bpmnProcessDesigner/package/penal/task/task-components/ProcessExpressionDialog.vue @@ -61,8 +61,8 @@ const getList = async () => { } /** 提交表单 */ -const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 -const select = async (row) => { +const emit = defineEmits(['success', 'select']) // 定义 success/select 事件,用于操作成功后的回调 +const select = async (row: ProcessExpressionVO) => { dialogVisible.value = false // 发送操作成功的事件 emit('select', row) diff --git a/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue b/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue index 1de79a277..f8ebde065 100644 --- a/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue +++ b/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue @@ -233,7 +233,13 @@ const props = defineProps({ type: String }) const prefix = inject('prefix') -const userTaskForm = ref({ +type CandidateParam = Array | string | number +type UserTaskForm = { + candidateStrategy?: CandidateStrategy + candidateParam: CandidateParam + skipExpression: string +} +const userTaskForm = ref({ candidateStrategy: undefined, // 分配规则 candidateParam: [], // 分配选项 skipExpression: '' // 跳过表达式 @@ -405,13 +411,6 @@ const updateElementTask = () => { bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { extensionElements: extensions }) - - // 改用通过extensionElements来存储数据 - return - bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), { - candidateStrategy: userTaskForm.value.candidateStrategy, - candidateParam: userTaskForm.value.candidateParam.join(',') - }) } const updateSkipExpression = () => {