diff --git a/package.json b/package.json index ff94abd7..89477d4c 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "vue-i18n": "9.10.2", "vue-router": "4.4.5", "vue-types": "^5.1.1", + "vue3-signature": "^0.2.4", "vuedraggable": "^4.1.0", "web-storage-cache": "^1.1.1", "xml-js": "^1.6.11" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 50e9bf96..ec016eb1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ dependencies: specifier: ^1.1.5 version: 1.1.5 bpmn-js-token-simulation: - specifier: ^0.10.0 - version: 0.10.0 + specifier: ^0.36.0 + version: 0.36.0 camunda-bpmn-moddle: specifier: ^7.0.1 version: 7.0.1 @@ -149,6 +149,9 @@ dependencies: vue-types: specifier: ^5.1.1 version: 5.1.3(vue@3.5.12) + vue3-signature: + specifier: ^0.2.4 + version: 0.2.4(vue@3.5.12) vuedraggable: specifier: ^4.1.0 version: 4.1.0(vue@3.5.12) @@ -4581,12 +4584,14 @@ packages: min-dom: 4.2.1 dev: true - /bpmn-js-token-simulation@0.10.0: - resolution: {integrity: sha512-QuZQ/KVXKt9Vl+XENyOBoTW2Aw+uKjuBlKdCJL6El7AyM7DkJ5bZkSYURshId1SkBDdYg2mJ1flSmsrhGuSfwg==, tarball: https://registry.npmmirror.com/bpmn-js-token-simulation/-/bpmn-js-token-simulation-0.10.0.tgz} + /bpmn-js-token-simulation@0.36.0: + resolution: {integrity: sha512-vz+RHlbZCev/6dzk6FhJRz8M0aZ1GL7Xrza0ecWqeg4tHbgPozgyOm3tXTz75XdtOwRVVBzmCjcciXQX7A55wQ==, tarball: https://registry.npmmirror.com/bpmn-js-token-simulation/-/bpmn-js-token-simulation-0.36.0.tgz} + engines: {node: '>= 16'} dependencies: - min-dash: 3.8.1 - min-dom: 0.2.0 - svg.js: 2.7.1 + inherits-browser: 0.1.0 + min-dash: 4.2.2 + min-dom: 4.2.1 + randomcolor: 0.6.2 dev: false /bpmn-js@17.11.1: @@ -4927,51 +4932,13 @@ packages: dot-prop: 5.3.0 dev: true - /component-classes@1.2.6: - resolution: {integrity: sha512-hPFGULxdwugu1QWW3SvVOCUHLzO34+a2J6Wqy0c5ASQkfi9/8nZcBB0ZohaEbXOQlCflMAEMmEWk7u7BVs4koA==, tarball: https://registry.npmmirror.com/component-classes/-/component-classes-1.2.6.tgz} - dependencies: - component-indexof: 0.0.3 - dev: false - - /component-closest@0.1.4: - resolution: {integrity: sha512-NF9hMj6JKGM5sb6wP/dg7GdJOttaIH9PcTsUNdWcrvu7Kw/5R5swQAFpgaYEHlARrNMyn4Wf7O1PlRej+pt76Q==, tarball: https://registry.npmmirror.com/component-closest/-/component-closest-0.1.4.tgz} - dependencies: - component-matches-selector: 0.1.7 - dev: false - - /component-delegate@0.2.4: - resolution: {integrity: sha512-OlpcB/6Fi+kXQPh/TfXnSvvmrU04ghz7vcJh/jgLF0Ni+I+E3WGlKJQbBGDa5X+kVUG8WxOgjP+8iWbz902fPg==, tarball: https://registry.npmmirror.com/component-delegate/-/component-delegate-0.2.4.tgz} - dependencies: - component-closest: 0.1.4 - component-event: 0.1.4 - dev: false - /component-emitter@1.3.1: resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==, tarball: https://registry.npmmirror.com/component-emitter/-/component-emitter-1.3.1.tgz} dev: true - /component-event@0.1.4: - resolution: {integrity: sha512-GMwOG8MnUHP1l8DZx1ztFO0SJTFnIzZnBDkXAj8RM2ntV2A6ALlDxgbMY1Fvxlg6WPQ+5IM/a6vg4PEYbjg/Rw==, tarball: https://registry.npmmirror.com/component-event/-/component-event-0.1.4.tgz} - dev: false - /component-event@0.2.1: resolution: {integrity: sha512-wGA++isMqiDq1jPYeyv2as/Bt/u+3iLW0rEa+8NQ82jAv3TgqMiCM+B2SaBdn2DfLilLjjq736YcezihRYhfxw==, tarball: https://registry.npmmirror.com/component-event/-/component-event-0.2.1.tgz} - /component-indexof@0.0.3: - resolution: {integrity: sha512-puDQKvx/64HZXb4hBwIcvQLaLgux8o1CbWl39s41hrIIZDl1lJiD5jc22gj3RBeGK0ovxALDYpIbyjqDUUl0rw==, tarball: https://registry.npmmirror.com/component-indexof/-/component-indexof-0.0.3.tgz} - dev: false - - /component-matches-selector@0.1.7: - resolution: {integrity: sha512-Yb2+pVBvrqkQVpPaDBF0DYXRreBveXJNrpJs9FnFu8PF6/5IIcz5oDZqiH9nB5hbD2/TmFVN5ZCxBzqu7yFFYQ==, tarball: https://registry.npmmirror.com/component-matches-selector/-/component-matches-selector-0.1.7.tgz} - dependencies: - component-query: 0.0.3 - global-object: 1.0.0 - dev: false - - /component-query@0.0.3: - resolution: {integrity: sha512-VgebQseT1hz1Ps7vVp2uaSg+N/gsI5ts3AZUSnN6GMA2M82JH7o+qYifWhmVE/e8w/H48SJuA3nA9uX8zRe95Q==, tarball: https://registry.npmmirror.com/component-query/-/component-query-0.0.3.tgz} - dev: false - /compute-scroll-into-view@1.0.20: resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==, tarball: https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz} dev: false @@ -5521,6 +5488,10 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, tarball: https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz} dev: true + /default-passive-events@2.0.0: + resolution: {integrity: sha512-eMtt76GpDVngZQ3ocgvRcNCklUMwID1PaNbCNxfpDXuiOXttSh0HzBbda1HU9SIUsDc02vb7g9+3I5tlqe/qMQ==, tarball: https://registry.npmmirror.com/default-passive-events/-/default-passive-events-2.0.0.tgz} + dev: false + /define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, tarball: https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz} engines: {node: '>= 0.4'} @@ -6674,10 +6645,6 @@ packages: global-prefix: 3.0.0 dev: true - /global-object@1.0.0: - resolution: {integrity: sha512-mSPSkY6UsHv6hgW0V2dfWBWTS8TnPnLx3ECVNoWp6rBI2Bg66VYoqGoTFlH/l7XhAZ/l+StYlntXlt87BEeCcg==, tarball: https://registry.npmmirror.com/global-object/-/global-object-1.0.0.tgz} - dev: false - /global-prefix@3.0.0: resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==, tarball: https://registry.npmmirror.com/global-prefix/-/global-prefix-3.0.0.tgz} engines: {node: '>=6'} @@ -7899,10 +7866,6 @@ packages: engines: {node: '>=18'} dev: true - /min-dash@3.8.1: - resolution: {integrity: sha512-evumdlmIlg9mbRVPbC4F5FuRhNmcMS5pvuBUbqb1G9v09Ro0ImPEgz5n3khir83lFok1inKqVDjnKEg3GpDxQg==, tarball: https://registry.npmmirror.com/min-dash/-/min-dash-3.8.1.tgz} - dev: false - /min-dash@4.2.2: resolution: {integrity: sha512-qbhSYUxk6mBaF096B3JOQSumXbKWHenmT97cSpdNzgkWwGjhjhE/KZODCoDNhI2I4C9Cb6R/Q13S4BYkUSXoXQ==, tarball: https://registry.npmmirror.com/min-dash/-/min-dash-4.2.2.tgz} @@ -7912,18 +7875,6 @@ packages: dom-walk: 0.1.2 dev: false - /min-dom@0.2.0: - resolution: {integrity: sha512-VmxugbnAcVZGqvepjhOA4d4apmrpX8mMaRS+/jo0dI5Yorzrr4Ru9zc9KVALlY/+XakVCb8iQ+PYXljihQcsNw==, tarball: https://registry.npmmirror.com/min-dom/-/min-dom-0.2.0.tgz} - dependencies: - component-classes: 1.2.6 - component-closest: 0.1.4 - component-delegate: 0.2.4 - component-event: 0.1.4 - component-matches-selector: 0.1.7 - component-query: 0.0.3 - domify: 1.4.2 - dev: false - /min-dom@4.2.1: resolution: {integrity: sha512-TMoL8SEEIhUWYgkj7XMSgxmwSyGI+4fP2KFFGnN3FbHfbGHVdsLYSz8LoIsgPhz4dWRmLvxWWSMgzZMJW5sZuA==, tarball: https://registry.npmmirror.com/min-dom/-/min-dom-4.2.1.tgz} dependencies: @@ -8714,6 +8665,10 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, tarball: https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz} dev: true + /randomcolor@0.6.2: + resolution: {integrity: sha512-Mn6TbyYpFgwFuQ8KJKqf3bqqY9O1y37/0jgSK/61PUxV4QfIMv0+K2ioq8DfOjkBslcjwSzRfIDEXfzA9aCx7A==, tarball: https://registry.npmmirror.com/randomcolor/-/randomcolor-0.6.2.tgz} + dev: false + /rd@2.0.1: resolution: {integrity: sha512-/XdKU4UazUZTXFmI0dpABt8jSXPWcEyaGdk340KdHnsEOdkTctlX23aAK7ChQDn39YGNlAJr1M5uvaKt4QnpNw==, tarball: https://registry.npmmirror.com/rd/-/rd-2.0.1.tgz} dependencies: @@ -9128,6 +9083,10 @@ packages: engines: {node: '>=14'} dev: true + /signature_pad@3.0.0-beta.4: + resolution: {integrity: sha512-cOf2NhVuTiuNqe2X/ycEmizvCDXk0DoemhsEpnkcGnA4kS5iJYTCqZ9As7tFBbsch45Q1EdX61833+6sjJ8rrw==, tarball: https://registry.npmmirror.com/signature_pad/-/signature_pad-3.0.0-beta.4.tgz} + dev: false + /sirv@2.0.4: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==, tarball: https://registry.npmmirror.com/sirv/-/sirv-2.0.4.tgz} engines: {node: '>= 10'} @@ -9561,10 +9520,6 @@ packages: resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==, tarball: https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz} dev: true - /svg.js@2.7.1: - resolution: {integrity: sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==, tarball: https://registry.npmmirror.com/svg.js/-/svg.js-2.7.1.tgz} - dev: false - /svgo@2.8.0: resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==, tarball: https://registry.npmmirror.com/svgo/-/svgo-2.8.0.tgz} engines: {node: '>=10.13.0'} @@ -10324,6 +10279,16 @@ packages: vue: 3.5.12(typescript@5.3.3) dev: false + /vue3-signature@0.2.4(vue@3.5.12): + resolution: {integrity: sha512-XFwwFVK9OG3F085pKIq2SlNVqx32WdFH+TXbGEWc5FfEKpx8oMmZuAwZZ50K/pH2FgmJSE8IRwU9DDhrLpd6iA==, tarball: https://registry.npmmirror.com/vue3-signature/-/vue3-signature-0.2.4.tgz} + peerDependencies: + vue: ^3.2.0 + dependencies: + default-passive-events: 2.0.0 + signature_pad: 3.0.0-beta.4 + vue: 3.5.12(typescript@5.3.3) + dev: false + /vue@3.5.12(typescript@5.3.3): resolution: {integrity: sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==, tarball: https://registry.npmmirror.com/vue/-/vue-3.5.12.tgz} peerDependencies: diff --git a/src/api/bpm/model/index.ts b/src/api/bpm/model/index.ts index 0c499dba..63b6af6a 100644 --- a/src/api/bpm/model/index.ts +++ b/src/api/bpm/model/index.ts @@ -72,3 +72,7 @@ export const deleteModel = async (id: number) => { export const deployModel = async (id: number) => { return await request.post({ url: '/bpm/model/deploy?id=' + id }) } + +export const cleanModel = async (id: number) => { + return await request.delete({ url: '/bpm/model/clean?id=' + id }) +} diff --git a/src/api/bpm/processInstance/index.ts b/src/api/bpm/processInstance/index.ts index f97270f9..9a99a91e 100644 --- a/src/api/bpm/processInstance/index.ts +++ b/src/api/bpm/processInstance/index.ts @@ -36,6 +36,7 @@ export type ApprovalTaskInfo = { assigneeUser: User status: number reason: string + signPicUrl: string } // 审批节点信息 @@ -89,7 +90,7 @@ export const getProcessInstanceCopyPage = async (params: any) => { // 获取审批详情 export const getApprovalDetail = async (params: any) => { - return await request.get({ url: 'bpm/process-instance/get-approval-detail' , params }) + return await request.get({ url: 'bpm/process-instance/get-approval-detail', params }) } // 获取表单字段权限 diff --git a/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue b/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue index 4dfd51ad..b3f62340 100644 --- a/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue +++ b/src/components/SimpleProcessDesignerV2/src/NodeHandler.vue @@ -40,13 +40,24 @@
包容分支
- -
- +
+
延迟器
-
+
+
+ +
+
路由分支
+
+
+
+ +
+
触发器
+
+ @@ -60,12 +71,14 @@ import { ApproveMethodType, AssignEmptyHandlerType, AssignStartUserHandlerType, + ConditionType, NODE_DEFAULT_NAME, NodeType, RejectHandlerType, - SimpleFlowNode + SimpleFlowNode, + DEFAULT_CONDITION_GROUP_VALUE } from './consts' -import { generateUUID } from '@/utils' +import {generateUUID} from '@/utils' defineOptions({ name: 'NodeHandler' @@ -120,7 +133,16 @@ const addNode = (type: number) => { type: AssignEmptyHandlerType.APPROVE }, assignStartUserHandlerType: AssignStartUserHandlerType.START_USER_AUDIT, - childNode: props.childNode + childNode: props.childNode, + taskCreateListener: { + enable: false + }, + taskAssignListener: { + enable: false + }, + taskCompleteListener: { + enable: false + } } emits('update:childNode', data) } @@ -147,8 +169,11 @@ const addNode = (type: number) => { showText: '', type: NodeType.CONDITION_NODE, childNode: undefined, - conditionType: 1, - defaultFlow: false + conditionSetting: { + defaultFlow: false, + conditionType: ConditionType.RULE, + conditionGroups: DEFAULT_CONDITION_GROUP_VALUE + } }, { id: 'Flow_' + generateUUID(), @@ -156,8 +181,9 @@ const addNode = (type: number) => { showText: '未满足其它条件时,将进入此分支', type: NodeType.CONDITION_NODE, childNode: undefined, - conditionType: undefined, - defaultFlow: true + conditionSetting: { + defaultFlow: true + } } ] } @@ -201,7 +227,11 @@ const addNode = (type: number) => { showText: '', type: NodeType.CONDITION_NODE, childNode: undefined, - defaultFlow: false + conditionSetting: { + defaultFlow: false, + conditionType: ConditionType.RULE, + conditionGroups: DEFAULT_CONDITION_GROUP_VALUE + } }, { id: 'Flow_' + generateUUID(), @@ -209,7 +239,9 @@ const addNode = (type: number) => { showText: '未满足其它条件时,将进入此分支', type: NodeType.CONDITION_NODE, childNode: undefined, - defaultFlow: true + conditionSetting: { + defaultFlow: true + } } ] } @@ -225,6 +257,26 @@ const addNode = (type: number) => { } emits('update:childNode', data) } + if (type === NodeType.ROUTER_BRANCH_NODE) { + const data: SimpleFlowNode = { + id: 'GateWay_' + generateUUID(), + name: NODE_DEFAULT_NAME.get(NodeType.ROUTER_BRANCH_NODE) as string, + showText: '', + type: NodeType.ROUTER_BRANCH_NODE, + childNode: props.childNode + } + emits('update:childNode', data) + } + if (type === NodeType.TRIGGER_NODE) { + const data: SimpleFlowNode = { + id: 'Activity_' + generateUUID(), + name: NODE_DEFAULT_NAME.get(NodeType.TRIGGER_NODE) as string, + showText: '', + type: NodeType.TRIGGER_NODE, + childNode: props.childNode + } + emits('update:childNode', data) + } } diff --git a/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue b/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue index 419501ac..048764c1 100644 --- a/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue +++ b/src/components/SimpleProcessDesignerV2/src/ProcessNodeTree.vue @@ -44,6 +44,18 @@ :flow-node="currentNode" @update:flow-node="handleModelValueUpdate" /> + + + + ([]) const formType = ref(20) @@ -76,9 +73,6 @@ const deptOptions = ref([]) // 部门列表 const deptTreeOptions = ref() const userGroupOptions = ref([]) // 用户组列表 -// 添加当前值的引用 -const currentValue = ref() - provide('formFields', formFields) provide('formType', formType) provide('roleList', roleOptions) @@ -88,9 +82,11 @@ provide('deptList', deptOptions) provide('userGroupList', userGroupOptions) provide('deptTree', deptTreeOptions) provide('startUserIds', props.startUserIds) - +provide('tasks', []) +provide('processInstance', {}) const message = useMessage() // 国际化 const processNodeTree = ref() +provide('processNodeTree', processNodeTree) const errorDialogVisible = ref(false) let errorNodes: SimpleFlowNode[] = [] @@ -112,70 +108,13 @@ const updateModel = () => { } } -// 加载流程数据 -const loadProcessData = async (data: any) => { - try { - if (data) { - const parsedData = typeof data === 'string' ? JSON.parse(data) : data - processNodeTree.value = parsedData - currentValue.value = parsedData - // 确保数据加载后刷新视图 - await nextTick() - if (simpleProcessModelRef.value?.refresh) { - await simpleProcessModelRef.value.refresh() - } - } - } catch (error) { - console.error('加载流程数据失败:', error) - } -} - -// 监听属性变化 -watch( - () => props.value, - async (newValue, oldValue) => { - if (newValue && newValue !== oldValue) { - await loadProcessData(newValue) - } - }, - { immediate: true, deep: true } -) - -// 监听流程节点树变化,自动保存 -watch( - () => processNodeTree.value, - async (newValue, oldValue) => { - if (newValue && oldValue && JSON.stringify(newValue) !== JSON.stringify(oldValue)) { - await saveSimpleFlowModel(newValue) - } - }, - { deep: true } -) - const saveSimpleFlowModel = async (simpleModelNode: SimpleFlowNode) => { if (!simpleModelNode) { return } - // 校验节点 - errorNodes = [] - validateNode(simpleModelNode, errorNodes) - if (errorNodes.length > 0) { - errorDialogVisible.value = true - return - } - try { - if (props.modelId) { - // 编辑模式 - const data = { - id: props.modelId, - simpleModel: simpleModelNode - } - await updateBpmSimpleModel(data) - } - // 无论是编辑还是新建模式,都更新当前值并触发事件 - currentValue.value = simpleModelNode + processData.value = simpleModelNode emits('success', simpleModelNode) } catch (error) { console.error('保存失败:', error) @@ -246,61 +185,18 @@ onMounted(async () => { deptTreeOptions.value = handleTree(deptOptions.value as DeptApi.DeptVO[], 'id') // 获取用户组列表 userGroupOptions.value = await UserGroupApi.getUserGroupSimpleList() - // 加载流程数据 - if (props.modelId) { - // 获取 SIMPLE 设计器模型 - const result = await getBpmSimpleModel(props.modelId) - if (result) { - await loadProcessData(result) - } else { - updateModel() - } - } else if (props.value) { - await loadProcessData(props.value) + if (processData.value) { + processNodeTree.value = processData?.value } else { updateModel() } } finally { loading.value = false - emits('init-finished') } }) const simpleProcessModelRef = ref() -/** 获取当前流程数据 */ -const getCurrentFlowData = async () => { - try { - if (simpleProcessModelRef.value) { - const data = await simpleProcessModelRef.value.getCurrentFlowData() - if (data) { - currentValue.value = data - return data - } - } - return currentValue.value - } catch (error) { - console.error('获取流程数据失败:', error) - return currentValue.value - } -} - -// 刷新方法 -const refresh = async () => { - try { - if (currentValue.value) { - await loadProcessData(currentValue.value) - } - } catch (error) { - console.error('刷新失败:', error) - } -} - -defineExpose({ - getCurrentFlowData, - updateModel, - loadProcessData, - refresh -}) +defineExpose({}) diff --git a/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue b/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue index ccd1f10d..8f0a2916 100644 --- a/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue +++ b/src/components/SimpleProcessDesignerV2/src/SimpleProcessModel.vue @@ -3,6 +3,22 @@
+ + 导出 + + + 导入 + + + {{ scaleValue }}% @@ -34,6 +50,8 @@ import ProcessNodeTree from './ProcessNodeTree.vue' import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from './consts' import { useWatchNode } from './node' import { ZoomOut, ZoomIn, ScaleToOriginal } from '@element-plus/icons-vue' +import { isString } from '@/utils/is' +import download from '@/utils/download' defineOptions({ name: 'SimpleProcessModel' @@ -52,7 +70,7 @@ const props = defineProps({ }) const emits = defineEmits<{ - 'save': [node: SimpleFlowNode | undefined] + save: [node: SimpleFlowNode | undefined] }>() const processNodeTree = useWatchNode(props) @@ -85,6 +103,16 @@ const processReZoom = () => { const errorDialogVisible = ref(false) let errorNodes: SimpleFlowNode[] = [] +const saveSimpleFlowModel = async () => { + errorNodes = [] + validateNode(processNodeTree.value, errorNodes) + if (errorNodes.length > 0) { + errorDialogVisible.value = true + return + } + emits('save', processNodeTree.value) +} + // 校验节点设置。 暂时以 showText 为空 未节点错误配置 const validateNode = (node: SimpleFlowNode | undefined, errorNodes: SimpleFlowNode[]) => { if (node) { @@ -143,6 +171,28 @@ const getCurrentFlowData = async () => { defineExpose({ getCurrentFlowData }) + +/** 导出 JSON */ +const exportJson = () => { + download.json(new Blob([JSON.stringify(processNodeTree.value)]), 'model.json') +} + +/** 导入 JSON */ +const refFile = ref() +const importJson = () => { + refFile.value.click() +} +const importLocalFile = () => { + const file = refFile.value.files[0] + const reader = new FileReader() + reader.readAsText(file) + reader.onload = function () { + if (isString(this.result)) { + processNodeTree.value = JSON.parse(this.result) + emits('save', processNodeTree.value) + } + } +} diff --git a/src/components/SimpleProcessDesignerV2/src/consts.ts b/src/components/SimpleProcessDesignerV2/src/consts.ts index 10d8a21b..d4e59155 100644 --- a/src/components/SimpleProcessDesignerV2/src/consts.ts +++ b/src/components/SimpleProcessDesignerV2/src/consts.ts @@ -28,6 +28,11 @@ export enum NodeType { */ DELAY_TIMER_NODE = 14, + /** + * 触发器节点 + */ + TRIGGER_NODE = 15, + /** * 条件节点 */ @@ -44,7 +49,11 @@ export enum NodeType { /** * 包容分支节点 (对应包容网关) */ - INCLUSIVE_BRANCH_NODE = 53 + INCLUSIVE_BRANCH_NODE = 53, + /** + * 路由分支节点 + */ + ROUTER_BRANCH_NODE = 54 } export enum NodeId { @@ -93,18 +102,27 @@ export interface SimpleFlowNode { assignEmptyHandler?: AssignEmptyHandler // 审批节点的审批人与发起人相同时,对应的处理类型 assignStartUserHandlerType?: number - // 条件类型 - conditionType?: ConditionType - // 条件表达式 - conditionExpression?: string - // 条件组 - conditionGroups?: ConditionGroup - // 是否默认的条件 - defaultFlow?: boolean + // 创建任务监听器 + taskCreateListener?: ListenerHandler + // 创建任务监听器 + taskAssignListener?: ListenerHandler + // 创建任务监听器 + taskCompleteListener?: ListenerHandler + // 条件设置 + conditionSetting?: ConditionSetting // 活动的状态,用于前端节点状态展示 activityStatus?: TaskStatusEnum // 延迟设置 delaySetting?: DelaySetting + // 路由分支 + routerGroups?: RouterSetting[] + defaultFlowId?: string + // 签名 + signEnable?: boolean + // 审批意见 + reasonRequire?: boolean + // 触发器设置 + triggerSetting?: TriggerSetting } // 候选人策略枚举 ( 用于审批节点。抄送节点 ) export enum CandidateStrategy { @@ -222,6 +240,41 @@ export type AssignEmptyHandler = { userIds?: number[] } +/** + * 监听器的结构定义 + */ +export type ListenerHandler = { + enable: boolean + path?: string + header?: ListenerParam[] + body?: ListenerParam[] +} +export type ListenerParam = { + key: string + type: number + value: string +} +export enum ListenerParamTypeEnum { + /** + * 固定值 + */ + FIXED_VALUE = 1, + /** + * 表单 + */ + FROM_FORM = 2 +} +export const LISTENER_MAP_TYPES = [ + { + value: 1, + label: '固定值' + }, + { + value: 2, + label: '表单' + } +] + // 审批拒绝类型枚举 export enum RejectHandlerType { /** @@ -315,6 +368,20 @@ export enum TimeUnitType { DAY = 3 } +/** + * 条件节点设置结构定义,用于条件节点 + */ +export type ConditionSetting = { + // 条件类型 + conditionType?: ConditionType, + // 条件表达式 + conditionExpression?: string, + // 条件组 + conditionGroups?: ConditionGroup, + // 是否默认的条件 + defaultFlow?: boolean +} + // 条件配置类型 ( 用于条件节点配置 ) export enum ConditionType { /** @@ -389,8 +456,6 @@ export enum OperationButtonType { * 条件规则结构定义 */ export type ConditionRule = { - type: number - opName: string opCode: string leftSide: string rightSide: string @@ -405,6 +470,24 @@ export type ConditionGroup = { // 条件数组 conditions: Condition[] } +/** + * 条件组默认值 + */ +export const DEFAULT_CONDITION_GROUP_VALUE = { + and: true, + conditions: [ + { + and: true, + rules: [ + { + opCode: '==', + leftSide: '', + rightSide: '' + } + ] + } + ] +} /** * 条件结构定义 @@ -421,6 +504,8 @@ NODE_DEFAULT_TEXT.set(NodeType.COPY_TASK_NODE, '请配置抄送人') NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件') NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人') NODE_DEFAULT_TEXT.set(NodeType.DELAY_TIMER_NODE, '请设置延迟器') +NODE_DEFAULT_TEXT.set(NodeType.ROUTER_BRANCH_NODE, '请设置路由节点') +NODE_DEFAULT_TEXT.set(NodeType.TRIGGER_NODE, '请设置触发器') export const NODE_DEFAULT_NAME = new Map() NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人') @@ -428,6 +513,8 @@ NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人') NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件') NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人') NODE_DEFAULT_NAME.set(NodeType.DELAY_TIMER_NODE, '延迟器') +NODE_DEFAULT_NAME.set(NodeType.ROUTER_BRANCH_NODE, '路由分支') +NODE_DEFAULT_NAME.set(NodeType.TRIGGER_NODE, '触发器') // 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序 export const CANDIDATE_STRATEGY: DictDataVO[] = [ @@ -460,8 +547,8 @@ export const APPROVE_METHODS: DictDataVO[] = [ ] export const CONDITION_CONFIG_TYPES: DictDataVO[] = [ - { label: '条件表达式', value: ConditionType.EXPRESSION }, - { label: '条件规则', value: ConditionType.RULE } + { label: '条件规则', value: ConditionType.RULE }, + { label: '条件表达式', value: ConditionType.EXPRESSION } ] // 时间单位类型 @@ -575,7 +662,15 @@ export enum ProcessVariableEnum { /** * 发起用户 ID */ - START_USER_ID = 'PROCESS_START_USER_ID' + START_USER_ID = 'PROCESS_START_USER_ID', + /** + * 发起时间 + */ + START_TIME = 'PROCESS_START_TIME', + /** + * 流程定义名称 + */ + PROCESS_DEFINITION_NAME = 'PROCESS_DEFINITION_NAME' } /** @@ -604,3 +699,48 @@ export const DELAY_TYPE = [ { label: '固定时长', value: DelayTypeEnum.FIXED_TIME_DURATION }, { label: '固定日期', value: DelayTypeEnum.FIXED_DATE_TIME } ] + +/** + * 路由分支结构定义 + */ +export type RouterSetting = { + nodeId: string + conditionType: ConditionType + conditionExpression: string + conditionGroups: ConditionGroup +} + +// ==================== 触发器相关定义 ==================== +/** + * 触发器节点结构定义 + */ +export type TriggerSetting = { + type: TriggerTypeEnum + httpRequestSetting: HttpRequestTriggerSetting +} + +/** + * 触发器类型枚举 + */ +export enum TriggerTypeEnum { + /** + * 发送 HTTP 请求触发器 + */ + HTTP_REQUEST = 1, +} + +/** + * HTTP 请求触发器结构定义 + */ +export type HttpRequestTriggerSetting = { + // 请求 URL + url: string + // 请求头参数设置 + header?: ListenerParam[] // TODO 需要重命名一下 + // 请求体参数设置 + body?: ListenerParam[] +} + +export const TRIGGER_TYPES: DictDataVO[] = [ + { label: 'HTTP 请求', value: TriggerTypeEnum.HTTP_REQUEST } +] diff --git a/src/components/SimpleProcessDesignerV2/src/node.ts b/src/components/SimpleProcessDesignerV2/src/node.ts index b3cfc972..eba4c7ef 100644 --- a/src/components/SimpleProcessDesignerV2/src/node.ts +++ b/src/components/SimpleProcessDesignerV2/src/node.ts @@ -1,4 +1,3 @@ -import { cloneDeep } from 'lodash-es' import { TaskStatusEnum } from '@/api/bpm/task' import * as RoleApi from '@/api/system/role' import * as DeptApi from '@/api/system/dept' @@ -14,9 +13,11 @@ import { NODE_DEFAULT_NAME, AssignStartUserHandlerType, AssignEmptyHandlerType, - FieldPermissionType + FieldPermissionType, + ListenerParam } from './consts' -import { parseFormFields } from '@/components/FormCreate/src/utils/index' +import { parseFormFields } from '@/components/FormCreate/src/utils' + export function useWatchNode(props: { flowNode: SimpleFlowNode }): Ref { const node = ref(props.flowNode) watch( @@ -46,9 +47,9 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType) // 字段权限配置. 需要有 field, title, permissioin 属性 const fieldsPermissionConfig = ref>>([]) - const formType = inject>('formType') // 表单类型 + const formType = inject>('formType', ref()) // 表单类型 - const formFields = inject>('formFields') // 流程表单字段 + const formFields = inject>('formFields', ref([])) // 流程表单字段 const getNodeConfigFormFields = (nodeFormFields?: Array>) => { nodeFormFields = toRaw(nodeFormFields) @@ -108,12 +109,11 @@ export function useFormFieldsPermission(defaultPermission: FieldPermissionType) * @description 获取表单的字段 */ export function useFormFields() { - const formFields = inject>('formFields') // 流程表单字段 + const formFields = inject>('formFields', ref([])) // 流程表单字段 return parseFormCreateFields(unref(formFields)) } export type UserTaskFormType = { - //candidateParamArray: any[] candidateStrategy: CandidateStrategy approveMethod: ApproveMethodType roleIds?: number[] // 角色 @@ -136,10 +136,29 @@ export type UserTaskFormType = { timeDuration?: number maxRemindCount?: number buttonsSetting: any[] + taskCreateListenerEnable?: boolean + taskCreateListenerPath?: string + taskCreateListener?: { + header: ListenerParam[], + body: ListenerParam[] + } + taskAssignListenerEnable?: boolean + taskAssignListenerPath?: string + taskAssignListener?: { + header: ListenerParam[], + body: ListenerParam[] + } + taskCompleteListenerEnable?: boolean + taskCompleteListenerPath?: string + taskCompleteListener?:{ + header: ListenerParam[], + body: ListenerParam[] + } + signEnable: boolean + reasonRequire: boolean } export type CopyTaskFormType = { - // candidateParamArray: any[] candidateStrategy: CandidateStrategy roleIds?: number[] // 角色 deptIds?: number[] // 部门 @@ -156,13 +175,13 @@ export type CopyTaskFormType = { * @description 节点表单数据。 用于审批节点、抄送节点 */ export function useNodeForm(nodeType: NodeType) { - const roleOptions = inject>('roleList') // 角色列表 - const postOptions = inject>('postList') // 岗位列表 - const userOptions = inject>('userList') // 用户列表 - const deptOptions = inject>('deptList') // 部门列表 - const userGroupOptions = inject>('userGroupList') // 用户组列表 - const deptTreeOptions = inject('deptTree') // 部门树 - const formFields = inject>('formFields') // 流程表单字段 + const roleOptions = inject>('roleList', ref([])) // 角色列表 + const postOptions = inject>('postList', ref([])) // 岗位列表 + const userOptions = inject>('userList', ref([])) // 用户列表 + const deptOptions = inject>('deptList', ref([])) // 部门列表 + const userGroupOptions = inject>('userGroupList', ref([])) // 用户组列表 + const deptTreeOptions = inject('deptTree', ref()) // 部门树 + const formFields = inject>('formFields', ref([])) // 流程表单字段 const configForm = ref() if (nodeType === NodeType.USER_TASK_NODE) { configForm.value = { diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue index ae931724..93e3795a 100644 --- a/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue +++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/ConditionNodeConfig.vue @@ -26,121 +26,11 @@
-
未满足其它条件时,将进入此分支(该分支不可编辑和删除)
- - - - - {{ dict.label }} - - - - - - - - -
-
-
条件组关系
- -
-
- - -
- -
- - -
-
- - - -
-
- - - -
-
- -
-
- -
-
- -
-
-
-
-
- -
-
-
+