c9014f84 by tianhaohao@pashanhoo.com

1111

1 parent 4f079afb
...@@ -90,7 +90,7 @@ export default { ...@@ -90,7 +90,7 @@ export default {
90 case "B1": 90 case "B1":
91 getWorkFlowImage(this.bsmSlsq, this.bestepid ? this.bestepid : '').then(res => { 91 getWorkFlowImage(this.bsmSlsq, this.bestepid ? this.bestepid : '').then(res => {
92 let { result } = res 92 let { result } = res
93 this.$popupDialog("流程图", "workflow/components/processViewer", { 93 this.$popupDialog("流程图", "workflow/top/processViewer/index", {
94 xml: result.xml, 94 xml: result.xml,
95 finishedInfo: { 95 finishedInfo: {
96 finishedTaskSet: result.finishedActivityIds, 96 finishedTaskSet: result.finishedActivityIds,
......
1 <!--
2 * @Description:
3 * @Autor: renchao
4 * @LastEditTime: 2023-05-17 10:41:15
5 -->
6 <template>
7 <div>
8 <div class="process-viewer">
9 <div
10 v-show="!isLoading"
11 ref="processCanvas"
12 class="process-canvas"
13 style="height: 280px"
14 />
15 <!-- 自定义箭头样式,用于成功状态下流程连线箭头 -->
16 <defs ref="customSuccessDefs">
17 <marker
18 id="sequenceflow-end-white-success"
19 view-box="0 0 20 20"
20 ref-x="11"
21 ref-y="10"
22 marker-width="10"
23 marker-height="10"
24 orient="auto"
25 >
26 <path
27 class="success-arrow"
28 d="M 1 5 L 11 10 L 1 15 Z"
29 style="
30 stroke-width: 1px;
31 stroke-linecap: round;
32 stroke-dasharray: 10000, 1;
33 "
34 />
35 </marker>
36 <marker
37 id="conditional-flow-marker-white-success"
38 view-box="0 0 20 20"
39 ref-x="-1"
40 ref-y="10"
41 marker-width="10"
42 marker-height="10"
43 orient="auto"
44 >
45 <path
46 class="success-conditional"
47 d="M 0 10 L 8 6 L 16 10 L 8 14 Z"
48 style="
49 stroke-width: 1px;
50 stroke-linecap: round;
51 stroke-dasharray: 10000, 1;
52 "
53 />
54 </marker>
55 </defs>
56 <!-- 自定义箭头样式,用于失败状态下流程连线箭头 -->
57 <defs ref="customFailDefs">
58 <marker
59 id="sequenceflow-end-white-fail"
60 view-box="0 0 20 20"
61 ref-x="11"
62 ref-y="10"
63 marker-width="10"
64 marker-height="10"
65 orient="auto"
66 >
67 <path
68 class="fail-arrow"
69 d="M 1 5 L 11 10 L 1 15 Z"
70 style="
71 stroke-width: 1px;
72 stroke-linecap: round;
73 stroke-dasharray: 10000, 1;
74 "
75 />
76 </marker>
77 <marker
78 id="conditional-flow-marker-white-fail"
79 view-box="0 0 20 20"
80 ref-x="-1"
81 ref-y="10"
82 marker-width="10"
83 marker-height="10"
84 orient="auto"
85 >
86 <path
87 class="fail-conditional"
88 d="M 0 10 L 8 6 L 16 10 L 8 14 Z"
89 style="
90 stroke-width: 1px;
91 stroke-linecap: round;
92 stroke-dasharray: 10000, 1;
93 "
94 />
95 </marker>
96 </defs>
97
98 <div style="position: absolute; top: 0px; left: 0px; width: 100%">
99 <el-row type="flex" justify="end">
100 <el-button-group key="scale-control" size="medium">
101 <el-button
102 size="medium"
103 type="default"
104 :plain="true"
105 :disabled="defaultZoom <= 0.3"
106 icon="el-icon-zoom-out"
107 @click="processZoomOut()"
108 />
109 <el-button size="medium" type="default" style="width: 90px">{{
110 Math.floor(this.defaultZoom * 10 * 10) + "%"
111 }}</el-button>
112 <el-button
113 size="medium"
114 type="default"
115 :plain="true"
116 :disabled="defaultZoom >= 3.9"
117 icon="el-icon-zoom-in"
118 @click="processZoomIn()"
119 />
120 <el-button
121 size="medium"
122 type="default"
123 icon="el-icon-c-scale-to-original"
124 @click="processReZoom()"
125 />
126 <slot />
127 </el-button-group>
128 </el-row>
129 </div>
130 </div>
131 <!-- 已完成节点悬浮弹窗 -->
132 <div class="information-list">
133 <el-select v-model="selectValue" @change="handleSelect">
134 <el-option
135 v-for="item in selectOptions"
136 :key="item.value"
137 :label="item.label"
138 :value="item.value"
139 >
140 </el-option>
141 </el-select>
142 <div class="cutline">
143 <p class="cutlines">图例</p>
144 <div v-for="item in cutlinelist" :key="item.value" class="concent" :style="{ backgroundColor: item.backgroundColor,borderColor:item.color }">
145 {{item.value}}
146 </div>
147 </div>
148 <el-table
149 height="190"
150 :data="taskCommentList"
151 size="mini"
152 border
153 header-cell-class-name="table-header-gray"
154 >
155 <el-table-column
156 label="序号"
157 header-align="center"
158 align="center"
159 type="index"
160 width="55px"
161 />
162 <el-table-column label="环节" prop="name" align="center" />
163 <el-table-column
164 label="转入时间"
165 prop="createTime"
166 :formatter="formatDate"
167 width="160"
168 align="center"
169 />
170 <el-table-column
171 label="认领时间"
172 prop="claimTime"
173 :formatter="formatDate"
174 width="160"
175 align="center"
176 />
177 <el-table-column
178 label="转出时间"
179 prop="endTime"
180 :formatter="formatDate"
181 width="160"
182 align="center"
183 />
184 <el-table-column label="经办人" prop="agent" align="center" />
185 <el-table-column label="操作方式" prop="controls" align="center" />
186 <el-table-column label="意见" prop="idea" align="center" />
187 </el-table>
188 </div>
189 </div>
190 </template>
191 <script>
192 import "@/styles/package/theme/index.scss";
193 import BpmnViewer from "bpmn-js/lib/Viewer";
194 import MoveCanvasModule from "diagram-js/lib/navigation/movecanvas";
195 export default {
196 props: {
197 formData: {
198 type: Object,
199 default: {},
200 },
201 },
202 data() {
203 return {
204 dlgTitle: undefined,
205 defaultZoom: 1,
206 // 是否正在加载流程图
207 isLoading: true,
208 bpmnViewer: undefined,
209 // 已完成流程元素
210 processNodeInfo: undefined,
211 // 当前任务id
212 selectTaskId: undefined,
213 // 任务节点审批记录
214 taskList: [],
215 taskCommentList: [],
216 // 已完成任务悬浮延迟Timer
217 hoverTimer: null,
218 // 下拉
219 selectValue: "",
220 selectOptions: [],
221 cutlinelist:[
222 {
223 value: "完成节点",
224 color: "#4eb819",
225 backgroundColor :"rgba(78, 184, 25,0.2)"
226 },
227 {
228 value: "当前节点",
229 color: "#409EFF",
230 backgroundColor :"rgba(64, 158, 255,0.2)"
231 },
232 {
233 value: "挂起节点",
234 color: "#E6A23C",
235 backgroundColor :"rgba(230, 162, 60,0.2)"
236 },
237 {
238 value: "阻塞节点",
239 color: "#F56C6C",
240 backgroundColor :"rgb(245, 108, 108,0.2)"
241 },
242 {
243 value: "未激活节点",
244 color: "#000000",
245 backgroundColor :"none",
246 }
247 ],
248 };
249 },
250 created() {
251 this.$nextTick(() => {
252 // 获取流程记录
253 this.getCommentList();
254 this.setProcessStatus(this.formData.finishedInfo);
255 this.importXML(this.formData.xml);
256 });
257 },
258 destroyed() {
259 this.clearViewer();
260 },
261 methods: {
262 /**
263 * @description: formatDate
264 * @param {*} row
265 * @param {*} column
266 * @author: renchao
267 */
268 formatDate(row, column) {
269 let data = row[column.property];
270 if (data == null) {
271 return null;
272 }
273 let dt = new Date(data);
274 return (
275 dt.getFullYear() +
276 "-" +
277 (dt.getMonth() + 1) +
278 "-" +
279 dt.getDate() +
280 " " +
281 dt.getHours() +
282 ":" +
283 dt.getMinutes() +
284 ":" +
285 dt.getSeconds()
286 );
287 },
288 /**
289 * @description: processReZoom
290 * @author: renchao
291 */
292 processReZoom() {
293 this.defaultZoom = 1;
294 this.bpmnViewer.get("canvas").zoom("fit-viewport", "auto");
295 },
296 /**
297 * @description: processZoomIn
298 * @param {*} zoomStep
299 * @author: renchao
300 */
301 processZoomIn(zoomStep = 0.1) {
302 const newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100;
303 if (newZoom > 4) {
304 throw new Error(
305 "[Process Designer Warn ]: The zoom ratio cannot be greater than 4"
306 );
307 }
308 this.defaultZoom = newZoom;
309 this.bpmnViewer.get("canvas").zoom(this.defaultZoom);
310 },
311 /**
312 * @description: processZoomOut
313 * @param {*} zoomStep
314 * @author: renchao
315 */
316 processZoomOut(zoomStep = 0.1) {
317 const newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100;
318 if (newZoom < 0.2) {
319 throw new Error(
320 "[Process Designer Warn ]: The zoom ratio cannot be scss than 0.2"
321 );
322 }
323 this.defaultZoom = newZoom;
324 this.bpmnViewer.get("canvas").zoom(this.defaultZoom);
325 },
326 /**
327 * @description: getOperationTagType
328 * @param {*} type
329 * @author: renchao
330 */
331 getOperationTagType(type) {
332 return "success";
333 },
334 // 流程图预览清空
335 /**
336 * @description: 流程图预览清空
337 * @param {*} e
338 * @author: renchao
339 */
340 clearViewer(a) {
341 if (this.$refs.processCanvas) {
342 this.$refs.processCanvas.innerHTML = "";
343 }
344 if (this.bpmnViewer) {
345 this.bpmnViewer.destroy();
346 }
347 this.bpmnViewer = null;
348 },
349 // 添加自定义箭头
350 /**
351 * @description: 添加自定义箭头
352 * @author: renchao
353 */
354 addCustomDefs() {
355 const canvas = this.bpmnViewer.get("canvas");
356 const svg = canvas._svg;
357 const customSuccessDefs = this.$refs.customSuccessDefs;
358 const customFailDefs = this.$refs.customFailDefs;
359 svg.appendChild(customSuccessDefs);
360 svg.appendChild(customFailDefs);
361 },
362 // 任务悬浮弹窗
363 /**
364 * @description: 任务悬浮弹窗
365 * @param {*} element
366 * @author: renchao
367 */
368 onSelectElement(element) {
369 this.selectTaskId = undefined;
370 this.dlgTitle = undefined;
371 let allfinishedTaskSet = [
372 ...this.processNodeInfo.finishedTaskSet,
373 ...this.processNodeInfo.unfinishedTaskSet,
374 ];
375 if (this.processNodeInfo == null || allfinishedTaskSet == null) return;
376 if (element == null || allfinishedTaskSet.indexOf(element.id) === -1) {
377 return;
378 }
379 this.selectTaskId = element.id;
380 this.selectValue = element.id;
381 this.dlgTitle = element.businessObject
382 ? element.businessObject.name
383 : undefined;
384 // 计算当前悬浮任务审批记录,如果记录为空不显示弹窗
385 this.taskCommentList = (this.taskList || []).filter((item) => {
386 return item.taskDefinitionKey === this.selectTaskId;
387 });
388 if (this.taskCommentList.length == 0) {
389 this.taskCommentList = this.taskList;
390 }
391 },
392 // 下拉列表切换
393 /**
394 * @description: 下拉列表切换
395 * @param {*} val
396 * @author: renchao
397 */
398 handleSelect(val) {
399 this.taskCommentList = (this.taskList || []).filter((item) => {
400 return item.taskDefinitionKey === val;
401 });
402 if (this.taskCommentList.length == 0) {
403 this.taskCommentList = this.taskList;
404 }
405 },
406 // 显示流程图
407 /**
408 * @description: 显示流程图
409 * @param {*} xml
410 * @author: renchao
411 */
412 async importXML(xml) {
413 let xmlData = this.$x2js.xml2js(xml).definitions.process;
414 this.selectOptions = xmlData.userTask.map((item) => {
415 return { value: item._id, label: item._name };
416 });
417 this.selectOptions = [
418 { value: xmlData.startEvent._id, label: "浏览记录" },
419 ...this.selectOptions,
420 ];
421 this.selectOptions = this.selectOptions
422 .map((item) => {
423 if (this.formData.finishedInfo.finishedTaskSet.includes(item.value)) {
424 return item;
425 }
426 if (
427 this.formData.finishedInfo.unfinishedTaskSet.includes(item.value)
428 ) {
429 return item;
430 }
431 })
432 .filter(Boolean);
433 this.selectValue = xmlData.startEvent._id;
434 this.clearViewer("a");
435 if (xml != null && xml !== "") {
436 try {
437 this.bpmnViewer = new BpmnViewer({
438 additionalModules: [
439 // 移动整个画布
440 MoveCanvasModule,
441 ],
442 container: this.$refs.processCanvas,
443 });
444 // 任务节点悬浮事件
445 this.bpmnViewer.on("element.click", ({ element }) => {
446 this.onSelectElement(element);
447 });
448 await this.bpmnViewer.importXML(xml);
449 this.isLoading = true;
450 this.addCustomDefs();
451 } catch (e) {
452 this.clearViewer("b");
453 } finally {
454 this.isLoading = false;
455 this.setProcessStatus(this.processNodeInfo);
456 this.$nextTick(() => {
457 this.processReZoom();
458 });
459 }
460 }
461 },
462 // 获取流程记录
463 /**
464 * @description: 获取流程记录
465 * @author: renchao
466 */
467 getCommentList() {
468 this.formData.allCommentList.forEach(async (item, index) => {
469 // item.comments.forEach(element => {
470 // if(element.type=="COMPLETE"){
471 // this.formData.allCommentList[index].idea=element.message
472 // this.formData.allCommentList[index].controls="完成"
473 // }
474 // });
475 let type = item.comments[item.comments.length - 1].type;
476 this.formData.allCommentList[index].idea =
477 item.comments[item.comments.length - 1].message;
478 // 操作方式
479 let controls = "";
480 // 正在办理
481 // 已完结
482 // 已退回
483 switch (type) {
484 case "COMPLETE":
485 controls = "完成";
486 break;
487 case "CLAIM":
488 controls = "完成";
489 break;
490 case "ASSIGN":
491 controls = "转办";
492 break;
493 case "DELEGATE":
494 controls = "委派";
495 break;
496 case "UNCLAIM":
497 controls = "取消认领";
498 break;
499 case "STOP":
500 controls = "终止";
501 break;
502 case "BACK":
503 controls = "退回";
504 break;
505 }
506 this.formData.allCommentList[index].controls = controls;
507 this.formData.allCommentList[index].agent = item.assignee.name;
508 });
509 this.formData.handlinglist.forEach(async (item, index) => {
510 if (item.assignee.name) {
511 this.formData.handlinglist[index].agent = item.assignee.name;
512 } else {
513 let str = "";
514 item.countersign.forEach((item) => {
515 str += item.name + ",";
516 });
517 str = str.slice(0, -1);
518 this.formData.allCommentList[index].agent = str;
519 }
520 });
521 this.taskList = [
522 ...this.formData.allCommentList,
523 ...this.formData.handlinglist,
524 ];
525 // this.taskList =this.formData.allCommentList;
526 // 处理数据之后赋值
527 this.taskCommentList = this.taskList;
528 this.taskCommentList = this.taskCommentList.sort(this.sortDownDate);
529 },
530 /**
531 * 时间排序函数
532 * @description: formatDate
533 * @param {*} row
534 * @param {*} column
535 * @author: renchao
536 */
537
538 sortDownDate(a, b) {
539 return Date.parse(a.createTime) - Date.parse(b.createTime);
540 },
541 // 设置流程图元素状态
542 /**
543 * @description: 设置流程图元素状态
544 * @param {*} processNodeInfo
545 * @author: renchao
546 */
547 setProcessStatus(processNodeInfo) {
548 this.processNodeInfo = processNodeInfo;
549 if (
550 this.isLoading ||
551 this.processNodeInfo == null ||
552 this.bpmnViewer == null
553 )
554 return;
555 const {
556 finishedTaskSet,
557 rejectedTaskSet,
558 unfinishedTaskSet,
559 finishedSequenceFlowSet,
560 } = this.processNodeInfo;
561 const canvas = this.bpmnViewer.get("canvas");
562 const elementRegistry = this.bpmnViewer.get("elementRegistry");
563 if (Array.isArray(finishedSequenceFlowSet)) {
564 finishedSequenceFlowSet.forEach((item) => {
565 if (item != null) {
566 canvas.addMarker(item, "success");
567 const element = elementRegistry.get(item);
568 const conditionExpression =
569 element.businessObject.conditionExpression;
570 if (conditionExpression) {
571 canvas.addMarker(item, "condition-expression");
572 }
573 }
574 });
575 }
576 if (Array.isArray(finishedTaskSet)) {
577 finishedTaskSet.forEach((item) => canvas.addMarker(item, "success"));
578 }
579 if (Array.isArray(unfinishedTaskSet)) {
580 unfinishedTaskSet.forEach((item) => canvas.addMarker(item, "primary"));
581 }
582 if (Array.isArray(rejectedTaskSet)) {
583 rejectedTaskSet.forEach((item) => {
584 if (item != null) {
585 const element = elementRegistry.get(item);
586 if (element.type.includes("Task")) {
587 canvas.addMarker(item, "danger");
588 } else {
589 canvas.addMarker(item, "warning");
590 }
591 }
592 });
593 }
594 },
595 },
596 };
597 </script>
598 <style scoped lang="scss">
599 .information-list {
600 height: 220px;
601 margin-top: 10px;
602
603 p {
604 font-size: 16px;
605 line-height: 24px;
606 }
607 }
608 /deep/.bjs-powered-by {
609 display: none;
610 }
611 // /deep/.information-list {
612 // height: 170px;
613 // overflow: visible;
614 // }
615 .cutline {
616
617 float: right;
618 width: 30%;
619 height: 30px;
620 display: flex;
621 margin-right: 30px;
622 justify-content: space-between;
623 .cutlines{
624 line-height: 30px;
625 font-weight: 600;
626 margin-right: 50px;
627 }
628 .concent{
629 line-height: 30px;
630 line-height: 14px;
631 text-align: center;
632 align-items: center;
633 margin: auto;
634 padding: 3px;
635 border-radius: 4px;
636 border:1px solid #fff;
637 }
638
639 }
640 </style>
...@@ -86,7 +86,7 @@ export default { ...@@ -86,7 +86,7 @@ export default {
86 case "B1": 86 case "B1":
87 getWorkFlowImage(this.bsmSlsq, this.bestepid ? this.bestepid : '').then(res => { 87 getWorkFlowImage(this.bsmSlsq, this.bestepid ? this.bestepid : '').then(res => {
88 let { result } = res 88 let { result } = res
89 this.$popupDialog("流程图", "workflow/components/processViewer", { 89 this.$popupDialog("流程图", "workflow/top/processViewer/index", {
90 xml: result.xml, 90 xml: result.xml,
91 finishedInfo: { 91 finishedInfo: {
92 finishedTaskSet: result.finishedActivityIds, 92 finishedTaskSet: result.finishedActivityIds,
......