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 }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -155,33 +45,17 @@
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/TriggerNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/TriggerNodeConfig.vue
new file mode 100644
index 00000000..ce5e82e1
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/TriggerNodeConfig.vue
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 确 定
+ 取 消
+
+
+
+
+
+
+
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue
index fb5e780e..ced8c8fd 100644
--- a/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/UserTaskNodeConfig.vue
@@ -3,7 +3,7 @@
:append-to-body="true"
v-model="settingVisible"
:show-close="false"
- :size="550"
+ :size="580"
:before-close="saveConfig"
class="justify-start"
>
@@ -19,7 +19,8 @@
:placeholder="nodeName"
/>
- {{ nodeName }}
+ {{ nodeName }}
+
@@ -46,14 +47,13 @@
v-model="configForm.candidateStrategy"
@change="changeCandidateStrategy"
>
-
- {{ dict.label }}
-
+
+
+
+ {{ dict.label }}
+
+
+
@@ -163,7 +163,7 @@
:key="idx"
:label="item.title"
:value="item.field"
- :disabled ="!item.required"
+ :disabled="!item.required"
/>
@@ -356,6 +356,16 @@
+
+ 是否需要签名
+
+
+
+
+ 审批意见
+
+
+
@@ -435,6 +445,9 @@
+
+
+
@@ -484,6 +497,7 @@ import {
import { defaultProps } from '@/utils/tree'
import { cloneDeep } from 'lodash-es'
import { convertTimeUnit, getApproveTypeText } from '../utils'
+import UserTaskListener from './components/UserTaskListener.vue'
defineOptions({
name: 'UserTaskNodeConfig'
})
@@ -609,9 +623,11 @@ const {
cTimeoutMaxRemindCount
} = useTimeoutHandler()
+const userTaskListenerRef = ref()
+
// 保存配置
const saveConfig = async () => {
- activeTabName.value = 'user'
+ // activeTabName.value = 'user'
// 设置审批节点名称
currentNode.value.name = nodeName.value!
// 设置审批类型
@@ -624,7 +640,8 @@ const saveConfig = async () => {
}
if (!formRef) return false
- const valid = await formRef.value.validate()
+ if (!userTaskListenerRef) return false
+ const valid = (await formRef.value.validate()) && (await userTaskListenerRef.value.validate())
if (!valid) return false
const showText = getShowText()
if (!showText) return false
@@ -663,6 +680,31 @@ const saveConfig = async () => {
currentNode.value.fieldsPermission = fieldsPermissionConfig.value
// 设置按钮权限
currentNode.value.buttonsSetting = buttonsSetting.value
+ // 创建任务监听器
+ currentNode.value.taskCreateListener = {
+ enable: configForm.value.taskCreateListenerEnable ?? false,
+ path: configForm.value.taskCreateListenerPath,
+ header: configForm.value.taskCreateListener?.header,
+ body: configForm.value.taskCreateListener?.body
+ }
+ // 指派任务监听器
+ currentNode.value.taskAssignListener = {
+ enable: configForm.value.taskAssignListenerEnable ?? false,
+ path: configForm.value.taskAssignListenerPath,
+ header: configForm.value.taskAssignListener?.header,
+ body: configForm.value.taskAssignListener?.body
+ }
+ // 完成任务监听器
+ currentNode.value.taskCompleteListener = {
+ enable: configForm.value.taskCompleteListenerEnable ?? false,
+ path: configForm.value.taskCompleteListenerPath,
+ header: configForm.value.taskCompleteListener?.header,
+ body: configForm.value.taskCompleteListener?.body
+ }
+ // 签名
+ currentNode.value.signEnable = configForm.value.signEnable
+ // 审批意见
+ currentNode.value.reasonRequire = configForm.value.reasonRequire
currentNode.value.showText = showText
settingVisible.value = false
@@ -714,6 +756,32 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
buttonsSetting.value = cloneDeep(node.buttonsSetting) || DEFAULT_BUTTON_SETTING
// 4. 表单字段权限配置
getNodeConfigFormFields(node.fieldsPermission)
+ // 5. 监听器
+ // 5.1 创建任务
+ configForm.value.taskCreateListenerEnable = node.taskCreateListener!.enable
+ configForm.value.taskCreateListenerPath = node.taskCreateListener!.path
+ configForm.value.taskCreateListener = {
+ header: node.taskCreateListener?.header ?? [],
+ body: node.taskCreateListener?.body ?? []
+ }
+ // 5.2 指派任务
+ configForm.value.taskAssignListenerEnable = node.taskAssignListener!.enable
+ configForm.value.taskAssignListenerPath = node.taskAssignListener!.path
+ configForm.value.taskAssignListener = {
+ header: node.taskAssignListener?.header ?? [],
+ body: node.taskAssignListener?.body ?? []
+ }
+ // 5.3 完成任务
+ configForm.value.taskCompleteListenerEnable = node.taskCompleteListener!.enable
+ configForm.value.taskCompleteListenerPath = node.taskCompleteListener!.path
+ configForm.value.taskCompleteListener = {
+ header: node.taskCompleteListener?.header ?? [],
+ body: node.taskCompleteListener?.body ?? []
+ }
+ // 6. 签名
+ configForm.value.signEnable = node?.signEnable ?? false
+ // 7. 审批意见
+ configForm.value.reasonRequire = node?.reasonRequire ?? false
}
defineExpose({ openDrawer, showUserTaskNodeConfig }) // 暴露方法给父组件
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/components/Condition.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/components/Condition.vue
new file mode 100644
index 00000000..e86ac2da
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/components/Condition.vue
@@ -0,0 +1,272 @@
+
+
+
+
+
+ {{ dict.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestParamSetting.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestParamSetting.vue
new file mode 100644
index 00000000..4a21af05
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/components/HttpRequestParamSetting.vue
@@ -0,0 +1,181 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 添加一行
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 添加一行
+
+
+
+
+
+
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes-config/components/UserTaskListener.vue b/src/components/SimpleProcessDesignerV2/src/nodes-config/components/UserTaskListener.vue
new file mode 100644
index 00000000..728f5684
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes-config/components/UserTaskListener.vue
@@ -0,0 +1,88 @@
+
+
+
+
+ {{ listener.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/DelayTimerNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/DelayTimerNode.vue
index 94f9c413..ad6795aa 100644
--- a/src/components/SimpleProcessDesignerV2/src/nodes/DelayTimerNode.vue
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/DelayTimerNode.vue
@@ -9,8 +9,7 @@
]"
>
-
-
+
('readonly')
-const processInstance = inject
[>('processInstance')
+const processInstance = inject][>('processInstance', ref({}))
// 审批信息的弹窗显示,用于只读模式
const dialogVisible = ref(false) // 弹窗可见性
const processInstanceInfos = ref([]) // 流程的审批信息
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue
index adeae77b..381e4701 100644
--- a/src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/ExclusiveNode.vue
@@ -108,7 +108,7 @@
+
+
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/StartUserNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/StartUserNode.vue
index 89a57d04..4abe38f0 100644
--- a/src/components/SimpleProcessDesignerV2/src/nodes/StartUserNode.vue
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/StartUserNode.vue
@@ -13,7 +13,7 @@
>]
('readonly') // 是否只读
-const tasks = inject[>('tasks')
+const tasks = inject][>('tasks', ref([]))
// 定义事件,更新父组件。
const emits = defineEmits<{
'update:modelValue': [node: SimpleFlowNode | undefined]
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/TriggerNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/TriggerNode.vue
new file mode 100644
index 00000000..00f1c829
--- /dev/null
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/TriggerNode.vue
@@ -0,0 +1,97 @@
+
+ ]
+
+
+
+
+
+
+
+
+ {{ currentNode.name }}
+
+
+
+
+ {{ currentNode.showText }}
+
+
+ {{ NODE_DEFAULT_TEXT.get(NodeType.TRIGGER_NODE) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue b/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue
index 761a6743..47ef540c 100644
--- a/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue
+++ b/src/components/SimpleProcessDesignerV2/src/nodes/UserTaskNode.vue
@@ -131,7 +131,7 @@ const emits = defineEmits<{
// 是否只读
const readonly = inject('readonly')
-const tasks = inject[>('tasks')
+const tasks = inject][>('tasks', ref([]))
// 监控节点变化
const currentNode = useWatchNode(props)
// 节点名称编辑
diff --git a/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf b/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf
index bb85b35f..58f31c36 100644
Binary files a/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf and b/src/components/SimpleProcessDesignerV2/theme/iconfont.ttf differ
diff --git a/src/components/SimpleProcessDesignerV2/theme/iconfont.woff b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff
index 94befbd1..f4b4f3de 100644
Binary files a/src/components/SimpleProcessDesignerV2/theme/iconfont.woff and b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff differ
diff --git a/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2 b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2
index e8f95c8c..d66f9685 100644
Binary files a/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2 and b/src/components/SimpleProcessDesignerV2/theme/iconfont.woff2 differ
diff --git a/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss b/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
index 8cf2681d..f3d8b443 100644
--- a/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
+++ b/src/components/SimpleProcessDesignerV2/theme/simple-process-designer.scss
@@ -113,18 +113,21 @@
// 节点连线气泡卡片样式
.handler-item-wrapper {
+ width: 320px;
display: flex;
+ flex-wrap: wrap;
cursor: pointer;
.handler-item {
display: flex;
flex-direction: column;
align-items: center;
+ margin-top: 12px;
}
.handler-item-icon {
- width: 60px;
- height: 60px;
+ width: 50px;
+ height: 50px;
background: #fff;
border: 1px solid #e2e2e2;
border-radius: 50%;
@@ -138,13 +141,14 @@
.icon-size {
font-size: 25px;
- line-height: 60px;
+ line-height: 50px;
}
}
.approve {
color: #ff943e;
}
+
.copy {
color: #3296fa;
}
@@ -161,6 +165,18 @@
color: #345da2;
}
+ .delay {
+ color: #e47470;
+ }
+
+ .trigger {
+ color: #3373d2;
+ }
+
+ .router {
+ color: #ca3a31
+ }
+
.handler-item-text {
margin-top: 4px;
width: 80px;
@@ -266,6 +282,18 @@
&.start-user {
color: #676565;
}
+
+ &.delay-node {
+ color: #e47470;
+ }
+
+ &.trigger-node {
+ color: #3373d2;
+ }
+
+ &.router-node {
+ color: #ca3a31
+ }
}
.node-title {
@@ -711,45 +739,56 @@
// iconfont 样式
@font-face {
- font-family: 'iconfont'; /* Project id 4495938 */
- src:
- url('iconfont.woff2?t=1724339470412') format('woff2'),
- url('iconfont.woff?t=1724339470412') format('woff'),
- url('iconfont.ttf?t=1724339470412') format('truetype');
+ font-family: "iconfont"; /* Project id 4495938 */
+ src: url('iconfont.woff2?t=1737639517142') format('woff2'),
+ url('iconfont.woff?t=1737639517142') format('woff'),
+ url('iconfont.ttf?t=1737639517142') format('truetype');
}
.iconfont {
- font-family: 'iconfont' !important;
+ font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
+.icon-trigger:before {
+ content: "\e6d3";
+}
+
+.icon-router:before {
+ content: "\e6b2";
+}
+
+.icon-delay:before {
+ content: "\e600";
+}
+
.icon-start-user:before {
- content: '\e679';
+ content: "\e679";
}
.icon-inclusive:before {
- content: '\e602';
+ content: "\e602";
}
.icon-copy:before {
- content: '\e7eb';
+ content: "\e7eb";
}
.icon-handle:before {
- content: '\e61c';
+ content: "\e61c";
}
.icon-exclusive:before {
- content: '\e717';
+ content: "\e717";
}
.icon-approve:before {
- content: '\e715';
+ content: "\e715";
}
.icon-parallel:before {
- content: '\e688';
+ content: "\e688";
}
diff --git a/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue b/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue
index 9d2fa5ba..83d40fbf 100644
--- a/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue
+++ b/src/components/bpmnProcessDesigner/package/designer/ProcessDesigner.vue
@@ -308,28 +308,6 @@ const props = defineProps({
}
})
-// 监听value变化,重新加载流程图
-watch(
- () => props.value,
- (newValue) => {
- if (newValue && bpmnModeler) {
- createNewDiagram(newValue)
- }
- },
- { immediate: true }
-)
-
-// 监听processId和processName变化
-watch(
- [() => props.processId, () => props.processName],
- ([newId, newName]) => {
- if (newId && newName && !props.value) {
- createNewDiagram(null)
- }
- },
- { immediate: true }
-)
-
provide('configGlobal', props)
let bpmnModeler: any = null
const defaultZoom = ref(1)
@@ -480,6 +458,7 @@ const initModelListeners = () => {
emit('commandStack-changed', event)
emit('input', xml)
emit('change', xml)
+ emit('save', xml)
} catch (e: any) {
console.error(`[Process Designer Warn]: ${e.message || e}`)
}
@@ -568,6 +547,7 @@ const importLocalFile = () => {
reader.onload = function () {
let xmlStr = this.result
createNewDiagram(xmlStr)
+ emit('save', xmlStr)
}
}
/* ------------------------------------------------ refs methods ------------------------------------------------------ */
diff --git a/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json b/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json
index 7fe1fa79..130b5941 100644
--- a/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json
+++ b/src/components/bpmnProcessDesigner/package/designer/plugins/descriptor/flowableDescriptor.json
@@ -1438,6 +1438,45 @@
"isBody": true
}
]
+ },
+ {
+ "name": "SignEnable",
+ "superClass": ["Element"],
+ "meta": {
+ "allowedIn": ["bpmn:UserTask"]
+ },
+ "properties": [
+ {
+ "name": "value",
+ "type": "Boolean",
+ "isBody": true
+ }
+ ]
+ },
+ {
+ "name": "SkipExpression",
+ "extends": ["bpmn:UserTask"],
+ "properties": [
+ {
+ "name": "skipExpression",
+ "isAttr": true,
+ "type": "String"
+ }
+ ]
+ },
+ {
+ "name": "ReasonRequire",
+ "superClass": ["Element"],
+ "meta": {
+ "allowedIn": ["bpmn:UserTask"]
+ },
+ "properties": [
+ {
+ "name": "value",
+ "type": "Boolean",
+ "isBody": true
+ }
+ ]
}
],
"emumerations": []
diff --git a/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue b/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
index e53ad994..ff08dd33 100644
--- a/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/PropertiesPanel.vue
@@ -1,5 +1,5 @@
- ]
+
diff --git a/src/components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue b/src/components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue
index 70ad4f8b..3172338d 100644
--- a/src/components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/base/ElementBaseInfo.vue
@@ -152,6 +152,9 @@ watch(
handleKeyUpdate(props.model.key)
handleNameUpdate(props.model.name)
}
+ },
+ {
+ immediate: true
}
)
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 ba385145..aab130d0 100644
--- a/src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/custom-config/components/UserTaskCustomConfig.vue
@@ -5,6 +5,7 @@
4. 操作按钮
5. 字段权限
6. 审批类型
+ 7. 是否需要签名
-->
@@ -161,6 +162,16 @@
+
+
是否需要签名
+
+
+
+
+
审批意见
+
+
+
@@ -218,6 +229,12 @@ const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFie
// 审批类型
const approveType = ref({ value: ApproveType.USER })
+// 是否需要签名
+const signEnable = ref({ value: false })
+
+// 审批意见
+const reasonRequire = ref({ value: false })
+
const elExtensionElements = ref()
const otherExtensions = ref()
const bpmnElement = ref()
@@ -311,6 +328,16 @@ const resetCustomConfigList = () => {
})
}
+ // 是否需要签名
+ signEnable.value =
+ elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:SignEnable`)?.[0] ||
+ bpmnInstances().moddle.create(`${prefix}:SignEnable`, { value: false })
+
+ // 审批意见
+ reasonRequire.value =
+ elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:ReasonRequire`)?.[0] ||
+ bpmnInstances().moddle.create(`${prefix}:ReasonRequire`, { value: false })
+
// 保留剩余扩展元素,便于后面更新该元素对应属性
otherExtensions.value =
elExtensionElements.value.values?.filter(
@@ -322,7 +349,9 @@ const resetCustomConfigList = () => {
ex.$type !== `${prefix}:AssignEmptyUserIds` &&
ex.$type !== `${prefix}:ButtonsSetting` &&
ex.$type !== `${prefix}:FieldsPermission` &&
- ex.$type !== `${prefix}:ApproveType`
+ ex.$type !== `${prefix}:ApproveType` &&
+ ex.$type !== `${prefix}:SignEnable` &&
+ ex.$type !== `${prefix}:ReasonRequire`
) ?? []
// 更新元素扩展属性,避免后续报错
@@ -373,7 +402,9 @@ const updateElementExtensions = () => {
assignEmptyUserIdsEl.value,
approveType.value,
...buttonsSettingEl.value,
- ...fieldsPermissionEl.value
+ ...fieldsPermissionEl.value,
+ signEnable.value,
+ reasonRequire.value
]
})
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
diff --git a/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue b/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue
index de2fb0d7..99ee6f88 100644
--- a/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/multi-instance/ElementMultiInstance.vue
@@ -1,6 +1,10 @@
-
+
+
+ 除了UserTask以外节点的多实例待实现
+
@@ -301,19 +308,21 @@ const approveMethod = ref()
const approveRatio = ref(100)
const otherExtensions = ref()
const getElementLoopNew = () => {
- const extensionElements =
- bpmnElement.value.businessObject?.extensionElements ??
- bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
- approveMethod.value = extensionElements.values.filter(
- (ex) => ex.$type === `${prefix}:ApproveMethod`
- )?.[0]?.value
+ if (props.type === 'UserTask') {
+ const extensionElements =
+ bpmnElement.value.businessObject?.extensionElements ??
+ bpmnInstances().moddle.create('bpmn:ExtensionElements', { values: [] })
+ approveMethod.value = extensionElements.values.filter(
+ (ex) => ex.$type === `${prefix}:ApproveMethod`
+ )?.[0]?.value
- otherExtensions.value =
- extensionElements.values.filter((ex) => ex.$type !== `${prefix}:ApproveMethod`) ?? []
+ otherExtensions.value =
+ extensionElements.values.filter((ex) => ex.$type !== `${prefix}:ApproveMethod`) ?? []
- if (!approveMethod.value) {
- approveMethod.value = ApproveMethodType.SEQUENTIAL_APPROVE
- updateLoopCharacteristics()
+ if (!approveMethod.value) {
+ approveMethod.value = ApproveMethodType.SEQUENTIAL_APPROVE
+ updateLoopCharacteristics()
+ }
}
}
const onApproveMethodChange = () => {
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 e563bfd6..81088ccc 100644
--- a/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue
+++ b/src/components/bpmnProcessDesigner/package/penal/task/task-components/UserTask.vue
@@ -192,6 +192,16 @@
+
+
+
+
@@ -220,7 +230,8 @@ const props = defineProps({
const prefix = inject('prefix')
const userTaskForm = ref({
candidateStrategy: undefined, // 分配规则
- candidateParam: [] // 分配选项
+ candidateParam: [], // 分配选项
+ skipExpression: '' // 跳过表达式
})
const bpmnElement = ref()
const bpmnInstances = () => (window as any)?.bpmnInstances
@@ -311,6 +322,13 @@ const resetTaskForm = () => {
(ex) => ex.$type !== `${prefix}:CandidateStrategy` && ex.$type !== `${prefix}:CandidateParam`
) ?? []
+ // 跳过表达式
+ if (businessObject.skipExpression != undefined) {
+ userTaskForm.value.skipExpression = businessObject.skipExpression
+ } else {
+ userTaskForm.value.skipExpression = ''
+ }
+
// 改用通过extensionElements来存储数据
return
if (businessObject.candidateStrategy != undefined) {
@@ -390,6 +408,18 @@ const updateElementTask = () => {
})
}
+const updateSkipExpression = () => {
+ if (userTaskForm.value.skipExpression && userTaskForm.value.skipExpression !== '') {
+ bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+ skipExpression: userTaskForm.value.skipExpression
+ })
+ } else {
+ bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
+ skipExpression: null
+ })
+ }
+}
+
// 打开监听器弹窗
const processExpressionDialogRef = ref()
const openProcessExpressionDialog = async () => {
diff --git a/src/directives/permission/hasPermi.ts b/src/directives/permission/hasPermi.ts
index 02e2fbc7..90cd0250 100644
--- a/src/directives/permission/hasPermi.ts
+++ b/src/directives/permission/hasPermi.ts
@@ -1,8 +1,9 @@
import type { App } from 'vue'
-import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import { useUserStore } from '@/store/modules/user'
const { t } = useI18n() // 国际化
+/** 判断权限的指令 directive */
export function hasPermi(app: App) {
app.directive('hasPermi', (el, binding) => {
const { value } = binding
@@ -19,13 +20,12 @@ export function hasPermi(app: App) {
})
}
+/** 判断权限的方法 function */
+const userStore = useUserStore()
+const all_permission = '*:*:*'
export const hasPermission = (permission: string[]) => {
- const { wsCache } = useCache()
- const all_permission = '*:*:*'
- const userInfo = wsCache.get(CACHE_KEY.USER)
- const permissions = userInfo?.permissions || []
-
- return permissions.some((p: string) => {
- return all_permission === p || permission.includes(p)
- })
-}
\ No newline at end of file
+ return (
+ userStore.permissions.has(all_permission) ||
+ permission.some((permission) => userStore.permissions.has(permission))
+ )
+}
diff --git a/src/layout/components/TagsView/src/TagsView.vue b/src/layout/components/TagsView/src/TagsView.vue
index dcbb90fd..af492ba2 100644
--- a/src/layout/components/TagsView/src/TagsView.vue
+++ b/src/layout/components/TagsView/src/TagsView.vue
@@ -12,6 +12,10 @@ import { useDesign } from '@/hooks/web/useDesign'
import { useTemplateRefsList } from '@vueuse/core'
import { ElScrollbar } from 'element-plus'
import { useScrollTo } from '@/hooks/event/useScrollTo'
+import { useTagsView } from '@/hooks/web/useTagsView'
+import { cloneDeep } from 'lodash-es'
+
+defineOptions({ name: 'TagsView' })
const { getPrefixCls } = useDesign()
@@ -19,7 +23,9 @@ const prefixCls = getPrefixCls('tags-view')
const { t } = useI18n()
-const { currentRoute, push, replace } = useRouter()
+const { currentRoute, push } = useRouter()
+
+const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage } = useTagsView()
const permissionStore = usePermissionStore()
@@ -31,6 +37,10 @@ const visitedViews = computed(() => tagsViewStore.getVisitedViews)
const affixTagArr = ref([])
+const selectedTag = computed(() => tagsViewStore.getSelectedTag)
+
+const setSelectTag = tagsViewStore.setSelectedTag
+
const appStore = useAppStore()
const tagsViewImmerse = computed(() => appStore.getTagsViewImmerse)
@@ -45,66 +55,30 @@ const initTags = () => {
for (const tag of unref(affixTagArr)) {
// Must have tag name
if (tag.name) {
- tagsViewStore.addVisitedView(tag)
+ tagsViewStore.addVisitedView(cloneDeep(tag))
}
}
}
-const selectedTag = ref()
-
// 新增tag
const addTags = () => {
const { name } = unref(currentRoute)
if (name) {
- selectedTag.value = unref(currentRoute)
+ setSelectTag(unref(currentRoute))
tagsViewStore.addView(unref(currentRoute))
}
- return false
}
// 关闭选中的tag
const closeSelectedTag = (view: RouteLocationNormalizedLoaded) => {
- if (view?.meta?.affix) return
- tagsViewStore.delView(view)
- if (isActive(view)) {
- toLastView()
- }
-}
-
-// 关闭全部
-const closeAllTags = () => {
- tagsViewStore.delAllViews()
- toLastView()
-}
-
-// 关闭其它
-const closeOthersTags = () => {
- tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
-}
-
-// 重新加载
-const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
- if (!view) return
- tagsViewStore.delCachedView()
- const { path, query } = view
- await nextTick()
- replace({
- path: '/redirect' + path,
- query: query
+ closeCurrent(view, () => {
+ if (isActive(view)) {
+ toLastView()
+ }
})
}
-// 关闭左侧
-const closeLeftTags = () => {
- tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
-}
-
-// 关闭右侧
-const closeRightTags = () => {
- tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
-}
-
-// 跳转到最后一个
+// 去最后一个
const toLastView = () => {
const visitedViews = tagsViewStore.getVisitedViews
const latestView = visitedViews.slice(-1)[0]
@@ -118,11 +92,38 @@ const toLastView = () => {
addTags()
return
}
- // TODO: You can set another route
- push('/')
+ // You can set another route
+ push(permissionStore.getAddRouters[0].path)
}
}
+// 关闭全部
+const closeAllTags = () => {
+ closeAll(() => {
+ toLastView()
+ })
+}
+
+// 关闭其它
+const closeOthersTags = () => {
+ closeOther()
+}
+
+// 重新加载
+const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
+ refreshPage(view)
+}
+
+// 关闭左侧
+const closeLeftTags = () => {
+ closeLeft()
+}
+
+// 关闭右侧
+const closeRightTags = () => {
+ closeRight()
+}
+
// 滚动到选中的tag
const moveToCurrentTag = async () => {
await nextTick()
@@ -209,13 +210,14 @@ const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
// 所有右键菜单组件的元素
const itemRefs = useTemplateRefsList>()
-// 右键菜单装填改变的时候
+// 右键菜单状态改变的时候
const visibleChange = (visible: boolean, tagItem: RouteLocationNormalizedLoaded) => {
if (visible) {
for (const v of unref(itemRefs)) {
const elDropdownMenuRef = v.elDropdownMenuRef
if (tagItem.fullPath !== v.tagItem.fullPath) {
elDropdownMenuRef?.handleClose()
+ setSelectTag(tagItem)
}
}
}
@@ -243,7 +245,17 @@ const move = (to: number) => {
start()
}
-onMounted(() => {
+const canShowIcon = (item: RouteLocationNormalizedLoaded) => {
+ if (
+ (item?.matched?.[1]?.meta?.icon && unref(tagsViewIcon)) ||
+ (item?.meta?.affix && unref(tagsViewIcon) && item?.meta?.icon)
+ ) {
+ return true
+ }
+ return false
+}
+
+onBeforeMount(() => {
initTags()
addTags()
})
diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts
index 806f954d..4b177ae8 100644
--- a/src/router/modules/remaining.ts
+++ b/src/router/modules/remaining.ts
@@ -344,7 +344,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
}
},
{
- path: 'manager/model/update/:id',
+ // TODO @zws:1)建议,在加一个路由。然后标题是“复制流程”,这样体验会好点;2)复制出来的数据,在名字前面,加“副本 ”,和钉钉保持一致!
+ path: 'manager/model/:type/:id',
component: () => import('@/views/bpm/model/form/index.vue'),
name: 'BpmModelUpdate',
meta: {
diff --git a/src/store/modules/tagsView.ts b/src/store/modules/tagsView.ts
index 4368efe0..2b2d817f 100644
--- a/src/store/modules/tagsView.ts
+++ b/src/store/modules/tagsView.ts
@@ -4,16 +4,19 @@ import { getRawRoute } from '@/utils/routerHelper'
import { defineStore } from 'pinia'
import { store } from '../index'
import { findIndex } from '@/utils'
+import { useUserStoreWithOut } from './user'
export interface TagsViewState {
visitedViews: RouteLocationNormalizedLoaded[]
cachedViews: Set
+ selectedTag?: RouteLocationNormalizedLoaded
}
export const useTagsViewStore = defineStore('tagsView', {
state: (): TagsViewState => ({
visitedViews: [],
- cachedViews: new Set()
+ cachedViews: new Set(),
+ selectedTag: undefined
}),
getters: {
getVisitedViews(): RouteLocationNormalizedLoaded[] {
@@ -21,6 +24,9 @@ export const useTagsViewStore = defineStore('tagsView', {
},
getCachedViews(): string[] {
return Array.from(this.cachedViews)
+ },
+ getSelectedTag(): RouteLocationNormalizedLoaded | undefined {
+ return this.selectedTag
}
},
actions: {
@@ -98,8 +104,12 @@ export const useTagsViewStore = defineStore('tagsView', {
},
// 删除所有tag
delAllVisitedViews() {
+ const userStore = useUserStoreWithOut()
+
// const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
- this.visitedViews = []
+ this.visitedViews = userStore.getUser
+ ? this.visitedViews.filter((tag) => tag?.meta?.affix)
+ : []
},
// 删除其他
delOthersViews(view: RouteLocationNormalizedLoaded) {
@@ -145,6 +155,18 @@ export const useTagsViewStore = defineStore('tagsView', {
break
}
}
+ },
+ // 设置当前选中的 tag
+ setSelectedTag(tag: RouteLocationNormalizedLoaded) {
+ this.selectedTag = tag
+ },
+ setTitle(title: string, path?: string) {
+ for (const v of this.visitedViews) {
+ if (v.path === (path ?? this.selectedTag?.path)) {
+ v.meta.title = title
+ break
+ }
+ }
}
},
persist: false
diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts
index b3861809..c3920c71 100644
--- a/src/store/modules/user.ts
+++ b/src/store/modules/user.ts
@@ -15,7 +15,7 @@ interface UserVO {
interface UserInfoVO {
// USER 缓存
- permissions: string[]
+ permissions: Set
roles: string[]
isSetUser: boolean
user: UserVO
@@ -23,7 +23,7 @@ interface UserInfoVO {
export const useUserStore = defineStore('admin-user', {
state: (): UserInfoVO => ({
- permissions: [],
+ permissions: new Set(),
roles: [],
isSetUser: false,
user: {
@@ -34,7 +34,7 @@ export const useUserStore = defineStore('admin-user', {
}
}),
getters: {
- getPermissions(): string[] {
+ getPermissions(): Set {
return this.permissions
},
getRoles(): string[] {
@@ -57,7 +57,7 @@ export const useUserStore = defineStore('admin-user', {
if (!userInfo) {
userInfo = await getInfo()
}
- this.permissions = userInfo.permissions
+ this.permissions = new Set(userInfo.permissions)
this.roles = userInfo.roles
this.user = userInfo.user
this.isSetUser = true
@@ -85,7 +85,7 @@ export const useUserStore = defineStore('admin-user', {
this.resetState()
},
resetState() {
- this.permissions = []
+ this.permissions = new Set()
this.roles = []
this.isSetUser = false
this.user = {
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 692a45fb..f673bdca 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -457,3 +457,9 @@ export const BpmProcessInstanceStatus = {
REJECT: 3, // 审批不通过
CANCEL: 4 // 已取消
}
+
+export const BpmAutoApproveType = {
+ NONE: 0, // 不自动通过
+ APPROVE_ALL: 1, // 仅审批一次,后续重复的审批节点均自动通过
+ APPROVE_SEQUENT: 2, // 仅针对连续审批的节点自动通过
+}
diff --git a/src/utils/download.ts b/src/utils/download.ts
index 5bbfb9fe..32fc624b 100644
--- a/src/utils/download.ts
+++ b/src/utils/download.ts
@@ -33,6 +33,10 @@ const download = {
markdown: (data: Blob, fileName: string) => {
download0(data, fileName, 'text/markdown')
},
+ // 下载 Json 方法
+ json: (data: Blob, fileName: string) => {
+ download0(data, fileName, 'application/json')
+ },
// 下载图片(允许跨域)
image: ({
url,
@@ -65,6 +69,31 @@ const download = {
a.download = 'image.png'
a.click()
}
+ },
+ base64ToFile: (base64: any, fileName: string) => {
+ // 将base64按照 , 进行分割 将前缀 与后续内容分隔开
+ const data = base64.split(',')
+ // 利用正则表达式 从前缀中获取图片的类型信息(image/png、image/jpeg、image/webp等)
+ const type = data[0].match(/:(.*?);/)[1]
+ // 从图片的类型信息中 获取具体的文件格式后缀(png、jpeg、webp)
+ const suffix = type.split('/')[1]
+ // 使用atob()对base64数据进行解码 结果是一个文件数据流 以字符串的格式输出
+ const bstr = window.atob(data[1])
+ // 获取解码结果字符串的长度
+ let n = bstr.length
+ // 根据解码结果字符串的长度创建一个等长的整形数字数组
+ // 但在创建时 所有元素初始值都为 0
+ const u8arr = new Uint8Array(n)
+ // 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
+ while (n--) {
+ // charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元
+ u8arr[n] = bstr.charCodeAt(n)
+ }
+
+ // 将File文件对象返回给方法的调用者
+ return new File([u8arr], `${fileName}.${suffix}`, {
+ type: type
+ })
}
}
diff --git a/src/utils/permission.ts b/src/utils/permission.ts
index 3bf67b61..41381738 100644
--- a/src/utils/permission.ts
+++ b/src/utils/permission.ts
@@ -1,4 +1,6 @@
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
+import {hasPermission} from "@/directives/permission/hasPermi";
+
const { t } = useI18n() // 国际化
@@ -7,21 +9,8 @@ const { t } = useI18n() // 国际化
* @param {Array} value 校验值
* @returns {Boolean}
*/
-export function checkPermi(value: string[]) {
- if (value && value instanceof Array && value.length > 0) {
- const { wsCache } = useCache()
- const permissionDatas = value
- const all_permission = '*:*:*'
- const userInfo = wsCache.get(CACHE_KEY.USER)
- const permissions = userInfo?.permissions || []
- const hasPermission = permissions.some((permission: string) => {
- return all_permission === permission || permissionDatas.includes(permission)
- })
- return !!hasPermission
- } else {
- console.error(t('permission.hasPermission'))
- return false
- }
+export function checkPermi(permission: string[]) {
+ return hasPermission(permission)
}
/**
diff --git a/src/views/bpm/model/CategoryDraggableModel.vue b/src/views/bpm/model/CategoryDraggableModel.vue
index f3b5a422..bcbd47d2 100644
--- a/src/views/bpm/model/CategoryDraggableModel.vue
+++ b/src/views/bpm/model/CategoryDraggableModel.vue
@@ -1,5 +1,5 @@
-
+
@@ -13,7 +13,7 @@
({{ categoryInfo.modelList?.length || 0 }})
-
+
-
+
-
- {{ scope.row.name }}
+
+ {{ row.name }}
-
-
- 全部可见
-
-
- {{ scope.row.startUsers[0].nickname }}
+
+ 全部可见
+
+ {{ row.startUsers[0].nickname }}
- {{ scope.row.startUsers[0].nickname }}等 {{ scope.row.startUsers.length }} 人可见
+ {{ row.startUsers[0].nickname }}等 {{ row.startUsers.length }} 人可见
@@ -158,17 +157,26 @@
link
type="primary"
@click="openModelForm('update', scope.row.id)"
- v-hasPermi="['bpm:model:update']"
+ v-if="hasPermiUpdate"
:disabled="!isManagerUser(scope.row)"
>
修改
+
+ 复制
+
发布
@@ -176,28 +184,33 @@
handleModelCommand(command, scope.row)"
- v-hasPermi="['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete']"
+ v-if="hasPermiMore"
>
更多
-
+
历史
{{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
+
+ 清理
+
删除
@@ -226,16 +239,11 @@
-
-
-
diff --git a/src/views/bpm/model/ModelForm.vue b/src/views/bpm/model/ModelForm.vue
deleted file mode 100644
index 2b4bcc6d..00000000
--- a/src/views/bpm/model/ModelForm.vue
+++ /dev/null
@@ -1,440 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/views/bpm/model/editor/index.vue b/src/views/bpm/model/editor/index.vue
index 37eff739..50386487 100644
--- a/src/views/bpm/model/editor/index.vue
+++ b/src/views/bpm/model/editor/index.vue
@@ -12,10 +12,12 @@
:additionalModel="controlForm.additionalModel"
:model="model"
@save="save"
+ :process-id="modelKey"
+ :process-name="modelName"
/>
()
@@ -51,10 +53,13 @@ const formType = ref(20)
provide('formFields', formFields)
provide('formType', formType)
-const xmlString = ref('') // BPMN XML
+// 注入流程数据
+const xmlString = inject('processData') as Ref
+// 注入模型数据
+const modelData = inject('modelData') as Ref
+
const modeler = shallowRef() // BPMN Modeler
const processDesigner = ref()
-const isModelerReady = ref(false)
const controlForm = ref({
simulation: true,
labelEditing: false,
@@ -65,154 +70,26 @@ const controlForm = ref({
})
const model = ref() // 流程模型的信息
-// 初始化 bpmnInstances
-const initBpmnInstances = () => {
- if (!modeler.value) return false
- try {
- const instances = {
- modeler: modeler.value,
- modeling: modeler.value.get('modeling'),
- moddle: modeler.value.get('moddle'),
- eventBus: modeler.value.get('eventBus'),
- bpmnFactory: modeler.value.get('bpmnFactory'),
- elementFactory: modeler.value.get('elementFactory'),
- elementRegistry: modeler.value.get('elementRegistry'),
- replace: modeler.value.get('replace'),
- selection: modeler.value.get('selection')
- }
-
- // 检查所有实例是否都存在
- return Object.values(instances).every((instance) => instance)
- } catch (error) {
- console.error('初始化 bpmnInstances 失败:', error)
- return false
- }
-}
-
/** 初始化 modeler */
-const initModeler = async (item) => {
- try {
- modeler.value = item
- // 等待 modeler 初始化完成
- await nextTick()
-
- // 确保 modeler 的所有实例都已经准备好
- if (initBpmnInstances()) {
- isModelerReady.value = true
- emit('init-finished')
-
- // 初始化完成后,设置初始值
- if (props.modelId) {
- // 编辑模式
- const data = await ModelApi.getModel(props.modelId)
- model.value = {
- ...data,
- bpmnXml: undefined // 清空 bpmnXml 属性
- }
- xmlString.value = data.bpmnXml || getDefaultBpmnXml(data.key, data.name)
- } else if (props.modelKey && props.modelName) {
- // 新建模式
- xmlString.value = props.value || getDefaultBpmnXml(props.modelKey, props.modelName)
- model.value = {
- key: props.modelKey,
- name: props.modelName
- } as ModelApi.ModelVO
- }
-
- // 导入XML并刷新视图
- await nextTick()
- try {
- await modeler.value.importXML(xmlString.value)
- if (processDesigner.value?.refresh) {
- processDesigner.value.refresh()
- }
- } catch (error) {
- console.error('导入XML失败:', error)
- }
- } else {
- console.error('modeler 实例未完全初始化')
- }
- } catch (error) {
- console.error('初始化 modeler 失败:', error)
- }
-}
-
-/** 获取默认的BPMN XML */
-const getDefaultBpmnXml = (key: string, name: string) => {
- return `
-
-
-
-
-
-`
+const initModeler = async (item: any) => {
+ //先初始化模型数据
+ model.value = modelData.value
+ modeler.value = item
}
/** 添加/修改模型 */
const save = async (bpmnXml: string) => {
try {
xmlString.value = bpmnXml
- if (props.modelId) {
- // 编辑模式
- const data = {
- ...model.value,
- bpmnXml: bpmnXml
- } as unknown as ModelApi.ModelVO
- await ModelApi.updateModelBpmn(data)
- emit('success')
- } else {
- // 新建模式,直接返回XML
- emit('success', bpmnXml)
- }
+ emit('success', bpmnXml)
} catch (error) {
console.error('保存失败:', error)
message.error('保存失败')
}
}
-// 监听 key、name 和 value 的变化
-watch(
- [() => props.modelKey, () => props.modelName, () => props.value],
- async ([newKey, newName, newValue]) => {
- if (!props.modelId && isModelerReady.value) {
- let shouldRefresh = false
-
- if (newKey && newName) {
- const newXml = newValue || getDefaultBpmnXml(newKey, newName)
- if (newXml !== xmlString.value) {
- xmlString.value = newXml
- shouldRefresh = true
- }
- model.value = {
- ...model.value,
- key: newKey,
- name: newName
- } as ModelApi.ModelVO
- } else if (newValue && newValue !== xmlString.value) {
- xmlString.value = newValue
- shouldRefresh = true
- }
-
- if (shouldRefresh) {
- // 确保更新后重新渲染
- await nextTick()
- if (processDesigner.value?.refresh) {
- try {
- await modeler.value?.importXML(xmlString.value)
- processDesigner.value.refresh()
- } catch (error) {
- console.error('导入XML失败:', error)
- }
- }
- }
- }
- },
- { deep: true }
-)
-
// 在组件卸载时清理
onBeforeUnmount(() => {
- isModelerReady.value = false
modeler.value = null
// 清理全局实例
const w = window as any
@@ -220,55 +97,6 @@ onBeforeUnmount(() => {
w.bpmnInstances = null
}
})
-
-/** 获取 XML 字符串 */
-const saveXML = async () => {
- if (!modeler.value) {
- return { xml: xmlString.value }
- }
- try {
- const result = await modeler.value.saveXML({ format: true })
- xmlString.value = result.xml
- return result
- } catch (error) {
- console.error('获取XML失败:', error)
- return { xml: xmlString.value }
- }
-}
-
-/** 获取SVG字符串 */
-const saveSVG = async () => {
- if (!modeler.value) {
- return { svg: undefined }
- }
- try {
- return await modeler.value.saveSVG()
- } catch (error) {
- console.error('获取SVG失败:', error)
- return { svg: undefined }
- }
-}
-
-/** 刷新视图 */
-const refresh = async () => {
- if (processDesigner.value?.refresh && modeler.value) {
- try {
- await modeler.value.importXML(xmlString.value)
- processDesigner.value.refresh()
- } catch (error) {
- console.error('刷新视图失败:', error)
- }
- }
-}
-
-// 暴露必要的属性和方法给父组件
-defineExpose({
- modeler,
- isModelerReady,
- saveXML,
- saveSVG,
- refresh
-})
diff --git a/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue b/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
index e24316cb..fcd5ec89 100644
--- a/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
+++ b/src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
@@ -123,6 +123,17 @@
>
审批意见:{{ task.reason }}
+
+ 签名:
+
+
diff --git a/src/views/bpm/processInstance/detail/SignDialog.vue b/src/views/bpm/processInstance/detail/SignDialog.vue
new file mode 100644
index 00000000..744a3556
--- /dev/null
+++ b/src/views/bpm/processInstance/detail/SignDialog.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+ 清除
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/bpm/processInstance/index.vue b/src/views/bpm/processInstance/index.vue
index a47b8bf7..dc071996 100644
--- a/src/views/bpm/processInstance/index.vue
+++ b/src/views/bpm/processInstance/index.vue
@@ -130,6 +130,15 @@
+
+
+
+
+ {{ item.key }} : {{ item.value }}
+
+
+
+
@@ -19,137 +17,22 @@ defineOptions({
name: 'SimpleModelDesign'
})
-const props = defineProps<{
+defineProps<{
modelId?: string
modelKey?: string
modelName?: string
- value?: string
startUserIds?: number[]
}>()
-const emit = defineEmits(['success', 'init-finished'])
+const emit = defineEmits(['success'])
const designerRef = ref()
-const isInitialized = ref(false)
-const currentValue = ref('')
-
-// 初始化或更新当前值
-const initOrUpdateValue = async () => {
- console.log('initOrUpdateValue', props.value)
- if (props.value) {
- currentValue.value = props.value
- // 如果设计器已经初始化,立即加载数据
- if (isInitialized.value && designerRef.value) {
- try {
- await designerRef.value.loadProcessData(props.value)
- await nextTick()
- if (designerRef.value.refresh) {
- await designerRef.value.refresh()
- }
- } catch (error) {
- console.error('加载流程数据失败:', error)
- }
- }
- }
-}
-
-// 监听属性变化
-watch(
- [() => props.modelKey, () => props.modelName, () => props.value],
- async ([newKey, newName, newValue], [oldKey, oldName, oldValue]) => {
- if (designerRef.value && isInitialized.value) {
- try {
- if (newKey && newName && (newKey !== oldKey || newName !== oldName)) {
- await designerRef.value.updateModel(newKey, newName)
- }
- if (newValue && newValue !== oldValue) {
- currentValue.value = newValue
- await designerRef.value.loadProcessData(newValue)
- await nextTick()
- if (designerRef.value.refresh) {
- await designerRef.value.refresh()
- }
- }
- } catch (error) {
- console.error('更新流程数据失败:', error)
- }
- }
- },
- { deep: true, immediate: true }
-)
-
-// 初始化完成回调
-const handleInit = async () => {
- try {
- isInitialized.value = true
- emit('init-finished')
-
- // 等待下一个tick,确保设计器已经准备好
- await nextTick()
-
- // 初始化完成后,设置初始值
- if (props.modelKey && props.modelName) {
- await designerRef.value.updateModel(props.modelKey, props.modelName)
- }
- if (props.value) {
- currentValue.value = props.value
- await designerRef.value.loadProcessData(props.value)
- // 再次刷新确保数据正确加载
- await nextTick()
- if (designerRef.value.refresh) {
- await designerRef.value.refresh()
- }
- }
- } catch (error) {
- console.error('初始化流程数据失败:', error)
- }
-}
// 修改成功回调
const handleSuccess = (data?: any) => {
- console.warn('handleSuccess', data)
- if (data && data !== currentValue.value) {
- currentValue.value = data
+ console.info('handleSuccess', data)
+ if (data) {
emit('success', data)
}
}
-
-/** 获取当前流程数据 */
-const getCurrentFlowData = async () => {
- try {
- if (designerRef.value) {
- const data = await designerRef.value.getCurrentFlowData()
- if (data) {
- currentValue.value = data
- }
- return data
- }
- return currentValue.value || undefined
- } catch (error) {
- console.error('获取流程数据失败:', error)
- return currentValue.value || undefined
- }
-}
-
-// 组件创建时初始化数据
-onMounted(() => {
- initOrUpdateValue()
-})
-
-// 组件卸载前保存数据
-onBeforeUnmount(async () => {
- try {
- const data = await getCurrentFlowData()
- if (data) {
- emit('success', data)
- }
- } catch (error) {
- console.error('保存数据失败:', error)
- }
-})
-
-defineExpose({
- getCurrentFlowData,
- refresh: () => designerRef.value?.refresh?.()
-})
diff --git a/src/views/bpm/task/copy/index.vue b/src/views/bpm/task/copy/index.vue
index f32a52a5..889028af 100644
--- a/src/views/bpm/task/copy/index.vue
+++ b/src/views/bpm/task/copy/index.vue
@@ -44,6 +44,7 @@
+
-
+
@@ -77,9 +77,9 @@
>
- 高级筛选
+ 高级筛选
-
+
-
+
+
+
+
+
+ {{ item.key }} : {{ item.value }}
+
+
+
+
- 高级筛选
+ 高级筛选
-
+
-
+
+
+
+
+
+ {{ item.key }} : {{ item.value }}
+
+
+
+