@lambo-design-mobile/workflow-approve 1.0.0-beta.3 → 1.0.0-beta.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +177 -2
- package/api.js +128 -15
- package/package.json +4 -3
- package/src/ApprovalNodeCell.vue +126 -0
- package/src/FlowApproval.vue +906 -198
- package/src/FlowApproval1.vue +1186 -0
- package/src/FlowBaseList.vue +97 -28
- package/src/FlowNodeCell.vue +116 -0
- package/src/SelectHandle.vue +125 -38
- package/src/SelectHandleCard.vue +5 -1
- package/src/SelectOrganize.vue +46 -18
- package/src/TodoListCard.vue +150 -12
- package/src/WorkflowDiagram.vue +505 -152
- package/src/assets/icon/iconfont.css +11 -3
- package/src/assets/icon/iconfont.js +1 -1
- package/src/assets/icon/iconfont.json +14 -0
- package/src/assets/icon/iconfont.ttf +0 -0
- package/src/assets/icon/iconfont.woff +0 -0
- package/src/assets/icon/iconfont.woff2 +0 -0
- package/src/js/global.js +33 -0
- package/src/utils/const.js +36 -0
package/src/WorkflowDiagram.vue
CHANGED
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
<div class="containers">
|
|
11
11
|
<div ref="canvas" class="canvas"></div>
|
|
12
12
|
</div>
|
|
13
|
+
<div v-if="predictButtonEnabled" class="predict-toggle">
|
|
14
|
+
<van-button
|
|
15
|
+
type="primary"
|
|
16
|
+
size="small"
|
|
17
|
+
:loading="loadingFutureNodes"
|
|
18
|
+
@click="toggleNodeDisplay"
|
|
19
|
+
>
|
|
20
|
+
{{ showAllNodes ? '显示预测路线' : '显示设计路线' }}
|
|
21
|
+
</van-button>
|
|
22
|
+
</div>
|
|
13
23
|
|
|
14
24
|
<!--审批流程追踪-->
|
|
15
25
|
<div class="title-info">
|
|
@@ -18,75 +28,70 @@
|
|
|
18
28
|
</div>
|
|
19
29
|
<div v-if="auditData.length > 0">
|
|
20
30
|
<div v-for="(item,index) in auditData" :key="index" class="record-item">
|
|
21
|
-
<
|
|
22
|
-
<template v-slot:icon>
|
|
23
|
-
<div
|
|
24
|
-
v-if="['通过', '跳转指定节点'].includes(getAuditStatus(item.auditResult).text)"
|
|
25
|
-
class="completed-icon">
|
|
26
|
-
<van-icon class="iconfont" class-prefix='icon' :name=getAuditStatus(item.auditResult).icon
|
|
27
|
-
size="30px" color="#fff"/>
|
|
28
|
-
</div>
|
|
29
|
-
<div v-else class="pending-icon">
|
|
30
|
-
<van-icon class="iconfont" class-prefix='icon' :name=getAuditStatus(item.auditResult).icon
|
|
31
|
-
size="30px" color="#fff"/>
|
|
32
|
-
</div>
|
|
33
|
-
</template>
|
|
34
|
-
<template v-slot:title>
|
|
35
|
-
<div :style="{ color: getAuditStatus(item.auditResult).color }"
|
|
36
|
-
style="font-size: 16px;font-weight: bold;padding:5px 0;">
|
|
37
|
-
{{ item.auditName }}
|
|
38
|
-
</div>
|
|
39
|
-
<div>{{ item.taskName }}
|
|
40
|
-
<span v-if="item.auditComment" @click="showAuditDetail(item.auditComment)">
|
|
41
|
-
| 审批意见 <van-icon name="comment-circle-o"/>
|
|
42
|
-
</span>
|
|
43
|
-
</div>
|
|
44
|
-
</template>
|
|
45
|
-
<template v-slot:label>
|
|
46
|
-
<div>{{ item.auditDate }}</div>
|
|
47
|
-
</template>
|
|
48
|
-
<template v-slot:default>
|
|
49
|
-
<span :style="{ color: getAuditStatus(item.auditResult).color }">{{
|
|
50
|
-
getAuditStatus(item.auditResult).text
|
|
51
|
-
}}</span>
|
|
52
|
-
</template>
|
|
53
|
-
</van-cell>
|
|
31
|
+
<flow-node-cell :node-detail="item"/>
|
|
54
32
|
</div>
|
|
55
33
|
</div>
|
|
56
34
|
<div v-else>
|
|
57
35
|
<van-empty description="暂无审批记录"/>
|
|
58
36
|
</div>
|
|
59
|
-
|
|
60
37
|
<!--审批详情弹出框-->
|
|
61
|
-
<van-dialog
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
38
|
+
<van-dialog
|
|
39
|
+
v-model="showTaskNodeDetail"
|
|
40
|
+
:title="nodeDetailTitle"
|
|
41
|
+
:close-on-popstate="true"
|
|
42
|
+
>
|
|
43
|
+
<!-- 当前记录内容 -->
|
|
44
|
+
<van-cell-group inset v-if="currentRecord">
|
|
45
|
+
<van-field readonly input-align="right" label="审批节点:" :value="currentRecord.taskName" />
|
|
46
|
+
<van-field readonly input-align="right" label="审批人:" :value="currentRecord.auditName" />
|
|
47
|
+
<van-field readonly input-align="right" label="申请人:"
|
|
48
|
+
:value="currentRecord.applyUser ? currentRecord.applyUser.split(':')[1] : '未定义'" />
|
|
49
|
+
<van-field readonly input-align="right"
|
|
50
|
+
:label="currentRecord.auditDate ? '审批时间:' : '发起时间:'"
|
|
51
|
+
:value="currentRecord.auditDate || currentRecord.createTime" />
|
|
69
52
|
<van-cell title="审批结果:" class="custom-cell" :border="false">
|
|
70
|
-
<template
|
|
71
|
-
<van-tag :type="getAuditStatus(
|
|
72
|
-
{{ getAuditStatus(
|
|
53
|
+
<template #default>
|
|
54
|
+
<van-tag :type="getAuditStatus(currentRecord.auditResult).type">
|
|
55
|
+
{{ getAuditStatus(currentRecord.auditResult).text }}
|
|
73
56
|
</van-tag>
|
|
74
57
|
</template>
|
|
75
58
|
</van-cell>
|
|
76
|
-
<van-cell v-if="
|
|
77
|
-
|
|
78
|
-
:value="nodeDetail.auditComment"></van-cell>
|
|
59
|
+
<van-cell v-if="currentRecord.auditComment" class="custom-cell" :border="false"
|
|
60
|
+
title="审批意见:" :value="currentRecord.auditComment" />
|
|
79
61
|
</van-cell-group>
|
|
62
|
+
|
|
63
|
+
<!-- 包含多条审批意见时可切换展示 -->
|
|
64
|
+
<div v-show="nodeDetailsList.length > 1" class="dialog-footer-arrows">
|
|
65
|
+
<van-button
|
|
66
|
+
round
|
|
67
|
+
size="small"
|
|
68
|
+
:disabled="currentIndex <= 0"
|
|
69
|
+
@click="prevRecord"
|
|
70
|
+
icon="arrow-left"
|
|
71
|
+
/>
|
|
72
|
+
<span class="record-counter">{{ currentIndex + 1 }} / {{ nodeDetailsList.length }}</span>
|
|
73
|
+
<van-button
|
|
74
|
+
round
|
|
75
|
+
size="small"
|
|
76
|
+
:disabled="currentIndex >= nodeDetailsList.length - 1"
|
|
77
|
+
@click="nextRecord"
|
|
78
|
+
icon="arrow"
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
80
81
|
</van-dialog>
|
|
81
82
|
</div>
|
|
82
83
|
</div>
|
|
83
84
|
</template>
|
|
84
85
|
<script>
|
|
85
86
|
import Viewer from "bpmn-js/lib/Viewer";
|
|
86
|
-
import {
|
|
87
|
-
import {
|
|
87
|
+
import { Toast } from "vant";
|
|
88
|
+
import {getHisAudit, getPrintData, printData, renderFutureNode, getNodeData} from "../api";
|
|
89
|
+
import ApprovalNodeCell from "./ApprovalNodeCell.vue";
|
|
90
|
+
import { getAuditStatus } from './js/global';
|
|
91
|
+
import FlowNodeCell from "./FlowNodeCell.vue"; // 根据路径修改
|
|
88
92
|
|
|
89
93
|
export default {
|
|
94
|
+
components: {FlowNodeCell, ApprovalNodeCell},
|
|
90
95
|
props: {
|
|
91
96
|
procId: {
|
|
92
97
|
type: String,
|
|
@@ -96,6 +101,11 @@ export default {
|
|
|
96
101
|
type: String,
|
|
97
102
|
required: false,
|
|
98
103
|
},
|
|
104
|
+
taskId: {
|
|
105
|
+
type: String,
|
|
106
|
+
required: false,
|
|
107
|
+
default: '',
|
|
108
|
+
},
|
|
99
109
|
taskNode: {
|
|
100
110
|
type: String,
|
|
101
111
|
required: false,
|
|
@@ -104,24 +114,74 @@ export default {
|
|
|
104
114
|
type: String,
|
|
105
115
|
required: true,
|
|
106
116
|
},
|
|
117
|
+
predictButtonEnabled: {
|
|
118
|
+
type: Boolean,
|
|
119
|
+
required: false,
|
|
120
|
+
},
|
|
107
121
|
},
|
|
108
122
|
mounted() {
|
|
123
|
+
if (this.predictButtonEnabled == null && this.taskNode){
|
|
124
|
+
getNodeData(this.procId, this.taskNode).then(resp => {
|
|
125
|
+
if (resp.data.code === '200') {
|
|
126
|
+
const data = resp.data.data[0]
|
|
127
|
+
this.predictButtonEnabled = data.handleButtons && data.handleButtons.includes("predictiveRoute")
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
109
131
|
this.getPrintData()
|
|
110
|
-
this.getHisAudit()
|
|
132
|
+
// this.getHisAudit()
|
|
111
133
|
this.onTrack()
|
|
112
134
|
},
|
|
135
|
+
beforeDestroy() {
|
|
136
|
+
this.removeTouchGesture();
|
|
137
|
+
if (this.bpmnViewer) {
|
|
138
|
+
this.bpmnViewer.destroy();
|
|
139
|
+
this.bpmnViewer = null;
|
|
140
|
+
}
|
|
141
|
+
},
|
|
113
142
|
data() {
|
|
114
143
|
return {
|
|
115
144
|
showTaskNodeDetail: false,
|
|
116
|
-
|
|
145
|
+
nodeDetailsList: [],
|
|
146
|
+
currentIndex: 0,
|
|
117
147
|
auditData: [],
|
|
148
|
+
tableData: [],
|
|
149
|
+
|
|
150
|
+
bpmnViewer: null,
|
|
151
|
+
diagramReady: false,
|
|
152
|
+
|
|
153
|
+
// 预测路线相关状态
|
|
154
|
+
showAllNodes: true,
|
|
155
|
+
loadingFutureNodes: false,
|
|
156
|
+
futureNodeData: null,
|
|
157
|
+
|
|
158
|
+
// 已办/待办集合(用于预测路线显示时保留已执行路径)
|
|
159
|
+
doneLightSet: new Set(),
|
|
160
|
+
doneTaskSet: new Set(),
|
|
161
|
+
donePointIdSet: new Set(),
|
|
162
|
+
doneNodeIdSet: new Set(),
|
|
163
|
+
todoPointIdSet: new Set(),
|
|
164
|
+
|
|
165
|
+
// 触摸手势事件句柄(用于重复初始化时清理)
|
|
166
|
+
touchGestureHandlers: null,
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
computed: {
|
|
170
|
+
currentRecord() {
|
|
171
|
+
return this.nodeDetailsList[this.currentIndex];
|
|
172
|
+
},
|
|
173
|
+
nodeDetailTitle() {
|
|
174
|
+
return this.nodeDetailsList.length > 1 ? `审批详情( ${this.currentIndex + 1}/ ${this.nodeDetailsList.length})` : '审批详情'
|
|
118
175
|
}
|
|
119
176
|
},
|
|
120
177
|
methods: {
|
|
178
|
+
getAuditStatus,
|
|
121
179
|
getPrintData() {
|
|
122
180
|
getPrintData(this.applyId, this.procId).then(resp => {
|
|
123
181
|
if (resp.data.code === '200') {
|
|
124
|
-
|
|
182
|
+
const data = Array.isArray(resp.data.data) ? resp.data.data : [];
|
|
183
|
+
this.tableData = data;
|
|
184
|
+
this.auditData = data;
|
|
125
185
|
}
|
|
126
186
|
})
|
|
127
187
|
},
|
|
@@ -132,16 +192,317 @@ export default {
|
|
|
132
192
|
}
|
|
133
193
|
})
|
|
134
194
|
},
|
|
135
|
-
|
|
195
|
+
|
|
196
|
+
// 切换“设计路线 / 预测路线”显示
|
|
197
|
+
async toggleNodeDisplay() {
|
|
198
|
+
if (this.showAllNodes) {
|
|
199
|
+
const success = await this.loadFutureNodes();
|
|
200
|
+
if (!success) return;
|
|
201
|
+
this.showAllNodes = false;
|
|
202
|
+
this.$emit('toggle-predictive', {showPredictive: true, futureNodeData: this.futureNodeData});
|
|
203
|
+
} else {
|
|
204
|
+
this.showAllNodesInDiagram();
|
|
205
|
+
this.showAllNodes = true;
|
|
206
|
+
this.$emit('toggle-predictive', {showPredictive: false, futureNodeData: null});
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
// 加载未来节点(预测路线)数据
|
|
211
|
+
async loadFutureNodes() {
|
|
212
|
+
if (!this.instanceId || !this.taskId) {
|
|
213
|
+
Toast('缺少必要参数:流程实例ID或当前任务ID');
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
if (!this.diagramReady) {
|
|
217
|
+
Toast('流程图尚未加载完成,请稍后重试');
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
this.loadingFutureNodes = true;
|
|
222
|
+
try {
|
|
223
|
+
const resp = await renderFutureNode(this.instanceId, this.taskId);
|
|
224
|
+
if (resp.data.code === '200' && resp.data.data) {
|
|
225
|
+
const futureData = resp.data.data;
|
|
226
|
+
const currentNodeId = futureData.currentNodeId;
|
|
227
|
+
const elementRegistry = this.bpmnViewer && this.bpmnViewer.get('elementRegistry');
|
|
228
|
+
if (currentNodeId && elementRegistry) {
|
|
229
|
+
const currentElement = elementRegistry.get(currentNodeId);
|
|
230
|
+
if (currentElement && this.isElementInSubProcess(currentElement)) {
|
|
231
|
+
Toast('预测路线暂不支持子流程节点,请查看设计路线');
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
this.futureNodeData = futureData;
|
|
236
|
+
this.showFutureNodesOnly();
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
Toast.fail(`加载预测路线失败:${resp.data.message || '未知错误'}`);
|
|
240
|
+
return false;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
Toast.fail(`加载预测路线失败:${error.message || '未知错误'}`);
|
|
243
|
+
return false;
|
|
244
|
+
} finally {
|
|
245
|
+
this.loadingFutureNodes = false;
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
// 只显示预测路线相关节点(并保留已办路径)
|
|
250
|
+
showFutureNodesOnly() {
|
|
251
|
+
if (!this.futureNodeData || !this.bpmnViewer) return;
|
|
252
|
+
|
|
253
|
+
const elementRegistry = this.bpmnViewer.get('elementRegistry');
|
|
254
|
+
|
|
255
|
+
// 获取所有元素
|
|
256
|
+
const allElements = elementRegistry.getAll();
|
|
257
|
+
|
|
258
|
+
// 从后端数据中提取节点和边信息
|
|
259
|
+
const {nodes = [], edges = [], futureNodeIds = []} = this.futureNodeData;
|
|
260
|
+
|
|
261
|
+
// 构建节点ID到节点数据的映射
|
|
262
|
+
const nodeMap = new Map();
|
|
263
|
+
nodes.forEach(node => {
|
|
264
|
+
nodeMap.set(node.id, node);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// 构建边ID到边数据的映射
|
|
268
|
+
const edgeMap = new Map();
|
|
269
|
+
edges.forEach(edge => {
|
|
270
|
+
edgeMap.set(edge.id, edge);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// 已办连线和节点集合(使用 Set 便于判断)
|
|
274
|
+
const doneLightSet = this.doneLightSet instanceof Set ? this.doneLightSet : new Set(this.doneLightSet || []);
|
|
275
|
+
const doneNodeSet = this.doneNodeIdSet instanceof Set ? this.doneNodeIdSet : new Set(this.doneNodeIdSet || []);
|
|
276
|
+
const hiddenNodeSet = new Set(this.futureNodeData.hiddenNodeIds || []);
|
|
277
|
+
const hiddenEdgeSet = new Set(this.futureNodeData.hiddenEdgeIds || []);
|
|
278
|
+
|
|
279
|
+
const futureNodeIdSet = new Set(futureNodeIds || []);
|
|
280
|
+
void futureNodeIdSet;
|
|
281
|
+
|
|
282
|
+
const visibleSubProcessIds = new Set(this.futureNodeData.visibleSubProcessIds || []);
|
|
283
|
+
|
|
284
|
+
// 遍历所有元素
|
|
285
|
+
allElements.forEach(element => {
|
|
286
|
+
const gfx = elementRegistry.getGraphics(element);
|
|
287
|
+
if (!gfx) return;
|
|
288
|
+
|
|
289
|
+
// 开始事件和结束事件
|
|
290
|
+
if (element.type === 'bpmn:StartEvent' || element.type === 'bpmn:EndEvent') {
|
|
291
|
+
const nodeData = nodeMap.get(element.id) || {};
|
|
292
|
+
const nodeFutureVisible = nodeData.hasOwnProperty('futureVisible')
|
|
293
|
+
? nodeData.futureVisible
|
|
294
|
+
: !hiddenNodeSet.has(element.id);
|
|
295
|
+
const parentSubProcess = element.parent && element.parent.type === 'bpmn:SubProcess' ? element.parent : null;
|
|
296
|
+
const parentVisible = parentSubProcess ? visibleSubProcessIds.has(parentSubProcess.id) : true;
|
|
297
|
+
const parentDone = parentSubProcess && doneNodeSet.has(parentSubProcess.id);
|
|
298
|
+
if (parentSubProcess && !parentVisible && !parentDone) {
|
|
299
|
+
gfx.style.display = 'none';
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const shouldDisplay = nodeFutureVisible || doneNodeSet.has(element.id);
|
|
303
|
+
if (!shouldDisplay) {
|
|
304
|
+
gfx.style.display = 'none';
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
gfx.style.display = '';
|
|
308
|
+
gfx.style.opacity = '1';
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// 处理任务节点
|
|
313
|
+
if (element.type === 'bpmn:UserTask' ||
|
|
314
|
+
element.type === 'bpmn:ServiceTask' ||
|
|
315
|
+
element.type === 'bpmn:Task' ||
|
|
316
|
+
element.type === 'bpmn:CallActivity') {
|
|
317
|
+
|
|
318
|
+
const nodeData = nodeMap.get(element.id) || {};
|
|
319
|
+
const nodeFutureVisible = nodeData.hasOwnProperty('futureVisible')
|
|
320
|
+
? nodeData.futureVisible
|
|
321
|
+
: !hiddenNodeSet.has(element.id);
|
|
322
|
+
const parentId = nodeData.parentSubProcessId;
|
|
323
|
+
const parentIsDone = parentId && doneNodeSet.has(parentId);
|
|
324
|
+
const isDoneTask = doneNodeSet.has(element.id) || parentIsDone;
|
|
325
|
+
const shouldDisplay = nodeFutureVisible || isDoneTask;
|
|
326
|
+
|
|
327
|
+
if (!shouldDisplay) {
|
|
328
|
+
gfx.style.display = 'none';
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
gfx.style.display = '';
|
|
333
|
+
gfx.style.opacity = '1';
|
|
334
|
+
|
|
335
|
+
if (nodeData.futureHighlight) {
|
|
336
|
+
const visualElement = gfx.querySelector('.djs-visual > :nth-child(1)');
|
|
337
|
+
if (visualElement) {
|
|
338
|
+
visualElement.style.stroke = '#FFD700';
|
|
339
|
+
visualElement.style.strokeWidth = '2px';
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 处理连线
|
|
345
|
+
if (element.type === 'bpmn:SequenceFlow') {
|
|
346
|
+
const edgeData = edgeMap.get(element.id) || {};
|
|
347
|
+
|
|
348
|
+
// 修改:严格判断连线是否已办,只有连线本身在 doneLightSet 中才视为已办
|
|
349
|
+
const isDoneLine = doneLightSet.has(element.id);
|
|
350
|
+
|
|
351
|
+
const edgeFutureVisible = edgeData.hasOwnProperty('futureVisible')
|
|
352
|
+
? edgeData.futureVisible
|
|
353
|
+
: !hiddenEdgeSet.has(element.id);
|
|
354
|
+
|
|
355
|
+
if (!(edgeFutureVisible || isDoneLine)) {
|
|
356
|
+
gfx.style.display = 'none';
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
gfx.style.display = '';
|
|
361
|
+
gfx.style.opacity = '1';
|
|
362
|
+
|
|
363
|
+
const shouldHighlight = edgeData.futureHighlight === true;
|
|
364
|
+
if (shouldHighlight) {
|
|
365
|
+
const canvas = this.bpmnViewer.get('canvas');
|
|
366
|
+
if (canvas && canvas.hasMarker(element.id, 'done-line')) {
|
|
367
|
+
canvas.removeMarker(element.id, 'done-line');
|
|
368
|
+
}
|
|
369
|
+
const visualElement = gfx.querySelector('.djs-visual > :nth-child(1)');
|
|
370
|
+
if (visualElement) {
|
|
371
|
+
visualElement.style.stroke = '#FFD700';
|
|
372
|
+
visualElement.style.strokeWidth = '2px';
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// 处理网关节点
|
|
378
|
+
if (typeof element.type === 'string' && element.type.includes('Gateway')) {
|
|
379
|
+
const nodeData = nodeMap.get(element.id) || {};
|
|
380
|
+
const nodeFutureVisible = nodeData.hasOwnProperty('futureVisible')
|
|
381
|
+
? nodeData.futureVisible
|
|
382
|
+
: !hiddenNodeSet.has(element.id);
|
|
383
|
+
|
|
384
|
+
// 检查是否同时连接到已办节点和预测路径上的节点
|
|
385
|
+
const hasIncomingDone = element.incoming?.some(conn => {
|
|
386
|
+
const source = conn.source;
|
|
387
|
+
return doneLightSet.has(conn.id) || (source && doneNodeSet.has(source.id));
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const hasOutgoingFuture = element.outgoing?.some(conn => {
|
|
391
|
+
const target = conn.target;
|
|
392
|
+
const connData = edgeMap.get(conn.id) || {};
|
|
393
|
+
return connData.futureVisible || (target && nodeMap.get(target.id)?.futureVisible);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// 严格网关显示条件:预测可见 / 已办 / 同时连接已办与预测(避免孤立网关)
|
|
397
|
+
const shouldDisplay = nodeFutureVisible || doneNodeSet.has(element.id) || (hasIncomingDone && hasOutgoingFuture);
|
|
398
|
+
if (!shouldDisplay) {
|
|
399
|
+
gfx.style.display = 'none';
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
gfx.style.display = '';
|
|
403
|
+
gfx.style.opacity = '1';
|
|
404
|
+
const visualElement = gfx.querySelector('.djs-visual > :nth-child(1)');
|
|
405
|
+
if (visualElement) {
|
|
406
|
+
if (nodeData.futureHighlight) {
|
|
407
|
+
visualElement.style.stroke = '#FFD700';
|
|
408
|
+
visualElement.style.strokeWidth = '2px';
|
|
409
|
+
} else {
|
|
410
|
+
visualElement.style.stroke = '#000000';
|
|
411
|
+
visualElement.style.strokeWidth = '2px';
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// 处理子流程节点(SubProcess)
|
|
417
|
+
if (element.type === 'bpmn:SubProcess') {
|
|
418
|
+
const nodeData = nodeMap.get(element.id) || {};
|
|
419
|
+
const nodeFutureVisible = nodeData.hasOwnProperty('futureVisible')
|
|
420
|
+
? nodeData.futureVisible
|
|
421
|
+
: visibleSubProcessIds.has(element.id);
|
|
422
|
+
const shouldDisplay = nodeFutureVisible || doneNodeSet.has(element.id);
|
|
423
|
+
if (!shouldDisplay) {
|
|
424
|
+
gfx.style.display = 'none';
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
gfx.style.display = '';
|
|
428
|
+
gfx.style.opacity = '1';
|
|
429
|
+
const visualElement = gfx.querySelector('.djs-visual > :nth-child(1)');
|
|
430
|
+
if (visualElement) {
|
|
431
|
+
if (nodeData.futureHighlight) {
|
|
432
|
+
visualElement.style.stroke = '#FFD700';
|
|
433
|
+
visualElement.style.strokeWidth = '2px';
|
|
434
|
+
} else {
|
|
435
|
+
visualElement.style.stroke = '#000000';
|
|
436
|
+
visualElement.style.strokeWidth = '2px';
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// 处理 label(如连线名称)
|
|
442
|
+
if (element.type === 'label' && element.labelTarget) {
|
|
443
|
+
const target = element.labelTarget;
|
|
444
|
+
const targetEdgeData = edgeMap.get(target.id) || {};
|
|
445
|
+
const futureLabelVisible = targetEdgeData.hasOwnProperty('futureLabelVisible')
|
|
446
|
+
? targetEdgeData.futureLabelVisible
|
|
447
|
+
: false;
|
|
448
|
+
const targetIsDoneLine = doneLightSet.has(target.id);
|
|
449
|
+
gfx.style.display = (futureLabelVisible || targetIsDoneLine) ? '' : 'none';
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
// 恢复显示全部节点(设计路线)
|
|
455
|
+
showAllNodesInDiagram() {
|
|
456
|
+
if (!this.bpmnViewer) return;
|
|
457
|
+
this.futureNodeData = null;
|
|
458
|
+
this.showTaskNodeDetail = false;
|
|
459
|
+
this.nodeDetailsList = [];
|
|
460
|
+
this.currentIndex = 0;
|
|
461
|
+
|
|
462
|
+
// 重新加载流程图是最可靠的恢复方式(避免样式残留)
|
|
463
|
+
this.onTrack();
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
isElementInSubProcess(element) {
|
|
467
|
+
let parent = element && element.parent;
|
|
468
|
+
while (parent) {
|
|
469
|
+
if (parent.type === 'bpmn:SubProcess') {
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
parent = parent.parent;
|
|
473
|
+
}
|
|
474
|
+
return false;
|
|
475
|
+
},
|
|
476
|
+
|
|
477
|
+
removeTouchGesture() {
|
|
478
|
+
if (!this.touchGestureHandlers) return;
|
|
479
|
+
const {container, onTouchStart, onTouchMove, onTouchEnd} = this.touchGestureHandlers;
|
|
480
|
+
if (container) {
|
|
481
|
+
container.removeEventListener('touchstart', onTouchStart);
|
|
482
|
+
container.removeEventListener('touchmove', onTouchMove);
|
|
483
|
+
container.removeEventListener('touchend', onTouchEnd);
|
|
484
|
+
}
|
|
485
|
+
this.touchGestureHandlers = null;
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
async onTrack() {
|
|
489
|
+
this.removeTouchGesture();
|
|
490
|
+
if (this.bpmnViewer) {
|
|
491
|
+
this.bpmnViewer.destroy();
|
|
492
|
+
this.bpmnViewer = null;
|
|
493
|
+
}
|
|
494
|
+
this.diagramReady = false;
|
|
495
|
+
|
|
136
496
|
// 初始化 Viewer
|
|
137
497
|
this.bpmnViewer = new Viewer({
|
|
138
498
|
container: this.$refs.canvas
|
|
139
499
|
});
|
|
140
500
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
501
|
+
try {
|
|
502
|
+
// 获取并加载流程数据
|
|
503
|
+
const resp = await printData(this.applyId, this.instanceId, this.procId);
|
|
504
|
+
const result = resp && resp.data;
|
|
505
|
+
if (result && result.code === '200') {
|
|
145
506
|
const bpmnXmlStr = result.data.processXml;
|
|
146
507
|
await this.bpmnViewer.importXML(bpmnXmlStr);
|
|
147
508
|
|
|
@@ -149,48 +510,58 @@ export default {
|
|
|
149
510
|
canvas.zoom('fit-viewport', 'auto');
|
|
150
511
|
canvas.zoom(0.6); // 放大视图
|
|
151
512
|
|
|
152
|
-
|
|
513
|
+
// 给流程节点添加固定样式
|
|
153
514
|
this.doPrint(result.data.historicData);
|
|
515
|
+
this.diagramReady = true;
|
|
154
516
|
|
|
155
517
|
// 添加手势缩放和移动功能
|
|
156
518
|
this.addTouchGesture();
|
|
157
519
|
// 监听元素点击事件
|
|
158
520
|
this.addElementClickListener();
|
|
159
521
|
|
|
522
|
+
return;
|
|
160
523
|
}
|
|
161
|
-
}).catch(err => {
|
|
162
|
-
console.error("Error fetching BPMN data:", err);
|
|
163
|
-
});
|
|
164
524
|
|
|
525
|
+
this.diagramReady = false;
|
|
526
|
+
Toast.fail(`加载流程图失败:${(result && result.message) || '未知错误'}`);
|
|
527
|
+
} catch (err) {
|
|
528
|
+
this.diagramReady = false;
|
|
529
|
+
console.error("Error fetching BPMN data:", err);
|
|
530
|
+
Toast.fail(`加载流程图失败:${err.message || '未知错误'}`);
|
|
531
|
+
}
|
|
165
532
|
},
|
|
166
533
|
doPrint(printData) {
|
|
167
|
-
const {doneLightSet, donePointSet, doneTaskSet, todoPointSet} =
|
|
168
|
-
|
|
534
|
+
const {doneLightSet, donePointSet, doneTaskSet, todoPointSet} = printData || {};
|
|
535
|
+
|
|
536
|
+
// 保存已办连线和节点集合,供预测路线显示时使用
|
|
537
|
+
this.doneLightSet = new Set(doneLightSet || []);
|
|
538
|
+
this.doneTaskSet = new Set(doneTaskSet || []);
|
|
539
|
+
this.donePointIdSet = new Set(donePointSet || []);
|
|
540
|
+
this.doneNodeIdSet = new Set([
|
|
541
|
+
...this.doneTaskSet,
|
|
542
|
+
...this.donePointIdSet
|
|
543
|
+
]);
|
|
544
|
+
this.todoPointIdSet = new Set(todoPointSet || []);
|
|
545
|
+
|
|
169
546
|
const canvas = this.bpmnViewer.get("canvas");
|
|
170
547
|
const elementRegistry = this.bpmnViewer.get('elementRegistry');
|
|
171
548
|
elementRegistry.filter((item) => item.type === 'bpmn:UserTask');
|
|
172
|
-
for (
|
|
173
|
-
if (
|
|
174
|
-
canvas.addMarker(doneLightSet[k], "done-line");
|
|
175
|
-
}
|
|
549
|
+
for (const id of this.doneLightSet) {
|
|
550
|
+
if (id) canvas.addMarker(id, "done-line");
|
|
176
551
|
}
|
|
177
|
-
for (
|
|
178
|
-
if (
|
|
179
|
-
canvas.addMarker(donePointSet[k], "done-point");
|
|
180
|
-
}
|
|
552
|
+
for (const id of this.donePointIdSet) {
|
|
553
|
+
if (id) canvas.addMarker(id, "done-point");
|
|
181
554
|
}
|
|
182
|
-
for (
|
|
183
|
-
if (
|
|
184
|
-
canvas.addMarker(doneTaskSet[k], "done-task");
|
|
185
|
-
}
|
|
555
|
+
for (const id of this.doneTaskSet) {
|
|
556
|
+
if (id) canvas.addMarker(id, "done-task");
|
|
186
557
|
}
|
|
187
|
-
for (
|
|
188
|
-
if (
|
|
189
|
-
canvas.addMarker(todoPointSet[k], "todo-point");
|
|
190
|
-
}
|
|
558
|
+
for (const id of this.todoPointIdSet) {
|
|
559
|
+
if (id) canvas.addMarker(id, "todo-point");
|
|
191
560
|
}
|
|
192
561
|
},
|
|
193
562
|
addTouchGesture() {
|
|
563
|
+
if (!this.bpmnViewer) return;
|
|
564
|
+
this.removeTouchGesture();
|
|
194
565
|
const canvas = this.bpmnViewer.get('canvas');
|
|
195
566
|
const container = this.$refs.canvas;
|
|
196
567
|
let initialDistance = null;
|
|
@@ -203,7 +574,7 @@ export default {
|
|
|
203
574
|
return Math.sqrt(dx * dx + dy * dy);
|
|
204
575
|
};
|
|
205
576
|
|
|
206
|
-
|
|
577
|
+
const onTouchStart = (event) => {
|
|
207
578
|
if (event.touches.length === 2) {
|
|
208
579
|
initialDistance = calculateDistance(event.touches[0], event.touches[1]);
|
|
209
580
|
} else if (event.touches.length === 1) {
|
|
@@ -213,9 +584,9 @@ export default {
|
|
|
213
584
|
};
|
|
214
585
|
isPanning = true;
|
|
215
586
|
}
|
|
216
|
-
}
|
|
587
|
+
};
|
|
217
588
|
|
|
218
|
-
|
|
589
|
+
const onTouchMove = (event) => {
|
|
219
590
|
if (event.touches.length === 2 && initialDistance !== null) {
|
|
220
591
|
const currentDistance = calculateDistance(event.touches[0], event.touches[1]);
|
|
221
592
|
const zoomFactor = currentDistance / initialDistance;
|
|
@@ -245,60 +616,62 @@ export default {
|
|
|
245
616
|
|
|
246
617
|
event.preventDefault(); // 阻止默认的触摸事件
|
|
247
618
|
}
|
|
248
|
-
}
|
|
619
|
+
};
|
|
249
620
|
|
|
250
|
-
|
|
621
|
+
const onTouchEnd = () => {
|
|
251
622
|
initialDistance = null; // 重置初始距离
|
|
252
623
|
isPanning = false; // 停止平移
|
|
253
|
-
}
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
container.addEventListener('touchstart', onTouchStart);
|
|
627
|
+
container.addEventListener('touchmove', onTouchMove);
|
|
628
|
+
container.addEventListener('touchend', onTouchEnd);
|
|
629
|
+
|
|
630
|
+
this.touchGestureHandlers = {container, onTouchStart, onTouchMove, onTouchEnd};
|
|
254
631
|
},
|
|
255
632
|
addElementClickListener() {
|
|
256
633
|
const eventBus = this.bpmnViewer.get('eventBus');
|
|
257
634
|
eventBus.on('element.click', (event) => {
|
|
258
635
|
const elementId = event.element.id;
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
636
|
+
const tableData = Array.isArray(this.tableData) ? this.tableData : [];
|
|
637
|
+
let matchedRecords = tableData.filter(item => item.taskNode === elementId);
|
|
638
|
+
|
|
639
|
+
if (matchedRecords.length > 0) {
|
|
640
|
+
const getTime = (record) => {
|
|
641
|
+
const timeStr = record.auditDate || record.startDate || record.createTime;
|
|
642
|
+
return new Date(timeStr).getTime();
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
matchedRecords.sort((a, b) => getTime(a) - getTime(b));
|
|
646
|
+
|
|
647
|
+
this.nodeDetailsList = matchedRecords;
|
|
648
|
+
this.currentIndex = matchedRecords.length - 1;
|
|
649
|
+
this.showTaskNodeDetail = true;
|
|
267
650
|
}
|
|
268
651
|
});
|
|
269
|
-
|
|
270
652
|
},
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
};
|
|
281
|
-
return {
|
|
282
|
-
text: (statusMap[auditResult] && statusMap[auditResult].text) || '待审批',
|
|
283
|
-
icon: (statusMap[auditResult] && statusMap[auditResult].icon) || 'daishenpi',
|
|
284
|
-
color: (statusMap[auditResult] && statusMap[auditResult].color) || '#ff9900',
|
|
285
|
-
type: (statusMap[auditResult] && statusMap[auditResult].type) || 'warning',
|
|
286
|
-
};
|
|
653
|
+
prevRecord() {
|
|
654
|
+
if (this.currentIndex > 0) {
|
|
655
|
+
this.currentIndex--;
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
nextRecord() {
|
|
659
|
+
if (this.currentIndex < this.nodeDetailsList.length - 1) {
|
|
660
|
+
this.currentIndex++;
|
|
661
|
+
}
|
|
287
662
|
},
|
|
288
|
-
showAuditDetail(auditComment) {
|
|
289
|
-
Dialog.alert({
|
|
290
|
-
title: '审批意见',
|
|
291
|
-
message: auditComment,
|
|
292
|
-
}).then(() => {
|
|
293
|
-
// on close
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
663
|
}
|
|
297
664
|
}
|
|
298
665
|
</script>
|
|
299
666
|
<style scoped>
|
|
300
667
|
@import 'styles/global.css';
|
|
301
668
|
|
|
669
|
+
.predict-toggle {
|
|
670
|
+
display: flex;
|
|
671
|
+
justify-content: center;
|
|
672
|
+
margin-top: 12px;
|
|
673
|
+
}
|
|
674
|
+
|
|
302
675
|
.canvas {
|
|
303
676
|
width: 100%;
|
|
304
677
|
height: 250px;
|
|
@@ -356,38 +729,6 @@ export default {
|
|
|
356
729
|
background-color: #0d88ff; /* 线条颜色 */
|
|
357
730
|
}
|
|
358
731
|
|
|
359
|
-
.completed-icon {
|
|
360
|
-
width: 45px; /* 自定义宽度 */
|
|
361
|
-
height: 45px; /* 自定义高度 */
|
|
362
|
-
background-clip: padding-box; /* 使背景不填充边框 */
|
|
363
|
-
|
|
364
|
-
border-radius: 50%;
|
|
365
|
-
margin-right: 15px;
|
|
366
|
-
|
|
367
|
-
display: flex;
|
|
368
|
-
align-items: center;
|
|
369
|
-
justify-content: center;
|
|
370
|
-
|
|
371
|
-
background: linear-gradient(90deg, #0096FF, #1677FF);
|
|
372
|
-
border: 5px solid rgb(229, 244, 255); /* 半透明边框,颜色和背景色相同 */
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
.pending-icon {
|
|
376
|
-
width: 45px; /* 自定义宽度 */
|
|
377
|
-
height: 45px; /* 自定义高度 */
|
|
378
|
-
background-clip: padding-box; /* 使背景不填充边框 */
|
|
379
|
-
|
|
380
|
-
border-radius: 50%;
|
|
381
|
-
margin-right: 15px;
|
|
382
|
-
|
|
383
|
-
display: flex;
|
|
384
|
-
align-items: center;
|
|
385
|
-
justify-content: center;
|
|
386
|
-
|
|
387
|
-
background: linear-gradient(90deg, #FF7E00, #FFA200);
|
|
388
|
-
border: 5px solid rgb(255, 245, 229); /* 半透明边框,颜色和背景色相同 */
|
|
389
|
-
}
|
|
390
|
-
|
|
391
732
|
|
|
392
733
|
.title-info {
|
|
393
734
|
font-weight: bold;
|
|
@@ -408,4 +749,16 @@ export default {
|
|
|
408
749
|
flex: 0 auto;
|
|
409
750
|
}
|
|
410
751
|
|
|
411
|
-
|
|
752
|
+
.dialog-footer-arrows {
|
|
753
|
+
display: flex;
|
|
754
|
+
justify-content: space-between;
|
|
755
|
+
align-items: center;
|
|
756
|
+
padding: 10px 16px;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
.dialog-footer-arrows .record-counter {
|
|
760
|
+
font-size: 14px;
|
|
761
|
+
color: #666;
|
|
762
|
+
margin: 0 12px;
|
|
763
|
+
}
|
|
764
|
+
</style>
|