xm
2024-06-14 722af26bc6fec32bb289b1df51a9016a4935610f
提交 | 用户 | 时间
722af2 1 <template>
X 2   <div class="process-viewer">
3     <div class="process-canvas" style="height: 100%;" ref="processCanvas" v-show="!isLoading" />
4     <!-- 自定义箭头样式,用于成功状态下流程连线箭头 -->
5     <defs ref="customSuccessDefs">
6       <marker id="sequenceflow-end-white-success" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10" markerHeight="10" orient="auto">
7         <path class="success-arrow" d="M 1 5 L 11 10 L 1 15 Z" style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;"></path>
8       </marker>
9       <marker id="conditional-flow-marker-white-success" viewBox="0 0 20 20" refX="-1" refY="10" markerWidth="10" markerHeight="10" orient="auto">
10         <path class="success-conditional" d="M 0 10 L 8 6 L 16 10 L 8 14 Z" style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;"></path>
11       </marker>
12     </defs>
13     <!-- 自定义箭头样式,用于失败状态下流程连线箭头 -->
14     <defs ref="customFailDefs">
15       <marker id="sequenceflow-end-white-fail" viewBox="0 0 20 20" refX="11" refY="10" markerWidth="10" markerHeight="10" orient="auto">
16         <path class="fail-arrow" d="M 1 5 L 11 10 L 1 15 Z" style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;"></path>
17       </marker>
18       <marker id="conditional-flow-marker-white-fail" viewBox="0 0 20 20" refX="-1" refY="10" markerWidth="10" markerHeight="10" orient="auto">
19         <path class="fail-conditional" d="M 0 10 L 8 6 L 16 10 L 8 14 Z" style="stroke-width: 1px; stroke-linecap: round; stroke-dasharray: 10000, 1;"></path>
20       </marker>
21     </defs>
22     <!-- 已完成节点悬浮弹窗 -->
23     <el-dialog class="comment-dialog" :title="dlgTitle || '审批记录'" :visible.sync="dialogVisible">
24       <el-row>
25         <el-table :data="taskCommentList" size="mini" border header-cell-class-name="table-header-gray">
26           <el-table-column label="序号" header-align="center" align="center" type="index" width="55px" />
27           <el-table-column label="候选办理" prop="candidate" width="150px" align="center"/>
28           <el-table-column label="实际办理" prop="assigneeName" width="100px" align="center"/>
29           <el-table-column label="处理时间" prop="createTime" width="140px" align="center"/>
30           <el-table-column label="办结时间" prop="finishTime" width="140px" align="center" />
31           <el-table-column label="耗时" prop="duration" width="100px" align="center"/>
32           <el-table-column label="审批意见" align="center">
33             <template slot-scope="scope">
34               {{scope.row.commentList&&scope.row.commentList[0]?scope.row.commentList[0].fullMessage:''}}
35             </template>
36           </el-table-column>
37         </el-table>
38       </el-row>
39     </el-dialog>
40     <div style="position: absolute; top: 0px; left: 0px; width: 100%;">
41       <el-row type="flex" justify="end">
42         <el-button-group key="scale-control" size="medium">
43           <el-button size="medium" type="default" :plain="true" :disabled="defaultZoom <= 0.3" icon="el-icon-zoom-out" @click="processZoomOut()" />
44           <el-button size="medium" type="default" style="width: 90px;">{{ Math.floor(this.defaultZoom * 10 * 10) + "%" }}</el-button>
45           <el-button size="medium" type="default" :plain="true" :disabled="defaultZoom >= 3.9" icon="el-icon-zoom-in" @click="processZoomIn()" />
46           <el-button size="medium" type="default" icon="el-icon-c-scale-to-original" @click="processReZoom()" />
47           <slot />
48         </el-button-group>
49       </el-row>
50     </div>
51   </div>
52 </template>
53
54 <script>
55 import '@/plugins/package/theme/index.scss';
56 import BpmnViewer from 'bpmn-js/lib/Viewer';
57 import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
58
59 export default {
60   props: {
61     xml: {
62       type: String
63     },
64     finishedInfo: {
65       type: Object
66     },
67     // 所有节点审批记录
68     allCommentList: {
69       type: Array
70     }
71   },
72   data () {
73     return {
74       dialogVisible: false,
75       dlgTitle: undefined,
76       defaultZoom: 1,
77       // 是否正在加载流程图
78       isLoading: false,
79       bpmnViewer: undefined,
80       // 已完成流程元素
81       processNodeInfo: undefined,
82       // 当前任务id
83       selectTaskId: undefined,
84       // 任务节点审批记录
85       taskCommentList: [],
86       // 已完成任务悬浮延迟Timer
87       hoverTimer: null
88     }
89   },
90   watch: {
91     xml: {
92       handler(newXml) {
93         this.importXML(newXml);
94       },
95       immediate: true
96     },
97     finishedInfo: {
98       handler(newInfo) {
99         this.setProcessStatus(newInfo);
100       },
101       immediate: true
102     }
103   },
104   created() {
105     this.$nextTick(() => {
106       this.importXML(this.xml)
107       this.setProcessStatus(this.finishedInfo);
108     })
109   },
110   methods: {
111     processReZoom() {
112       this.defaultZoom = 1;
113       this.bpmnViewer.get('canvas').zoom('fit-viewport', 'auto');
114     },
115     processZoomIn(zoomStep = 0.1) {
116       let newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100;
117       if (newZoom > 4) {
118         throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4');
119       }
120       this.defaultZoom = newZoom;
121       this.bpmnViewer.get('canvas').zoom(this.defaultZoom);
122     },
123     processZoomOut(zoomStep = 0.1) {
124       let newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100;
125       if (newZoom < 0.2) {
126         throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2');
127       }
128       this.defaultZoom = newZoom;
129       this.bpmnViewer.get('canvas').zoom(this.defaultZoom);
130     },
131     getOperationTagType(type) {
132       return 'success';
133       // switch (type) {
134       //   case this.SysFlowTaskOperationType.AGREE:
135       //   case this.SysFlowTaskOperationType.MULTI_AGREE:
136       //     return 'success';
137       //   case this.SysFlowTaskOperationType.REFUSE:
138       //   case this.SysFlowTaskOperationType.PARALLEL_REFUSE:
139       //   case this.SysFlowTaskOperationType.MULTI_REFUSE:
140       //     return 'warning';
141       //   case this.SysFlowTaskOperationType.STOP:
142       //     return 'danger'
143       //   default:
144       //     return 'primary';
145       // }
146     },
147     // 流程图预览清空
148     clearViewer() {
149       if (this.$refs.processCanvas) {
150         this.$refs.processCanvas.innerHTML = '';
151       }
152       if (this.bpmnViewer) {
153         this.bpmnViewer.destroy();
154       }
155       this.bpmnViewer = null;
156     },
157     // 添加自定义箭头
158     addCustomDefs() {
159       const canvas = this.bpmnViewer.get('canvas');
160       const svg = canvas._svg;
161       const customSuccessDefs = this.$refs.customSuccessDefs;
162       const customFailDefs = this.$refs.customFailDefs;
163       svg.appendChild(customSuccessDefs);
164       svg.appendChild(customFailDefs);
165     },
166     // 任务悬浮弹窗
167     onSelectElement(element) {
168       this.selectTaskId = undefined;
169       this.dlgTitle = undefined;
170
171       if (this.processNodeInfo == null || this.processNodeInfo.finishedTaskSet == null) return;
172
173       if (element == null || this.processNodeInfo.finishedTaskSet.indexOf(element.id) === -1) {
174         return;
175       }
176
177       this.selectTaskId = element.id;
178       this.dlgTitle = element.businessObject ? element.businessObject.name : undefined;
179       // 计算当前悬浮任务审批记录,如果记录为空不显示弹窗
180       this.taskCommentList = (this.allCommentList || []).filter(item => {
181         return item.activityId === this.selectTaskId;
182       });
183       this.dialogVisible = true;
184     },
185     // 显示流程图
186     async importXML(xml) {
187       this.clearViewer();
188       if (xml != null && xml !== '') {
189         try {
190           this.bpmnViewer = new BpmnViewer({
191             additionalModules: [
192               // 移动整个画布
193               MoveCanvasModule
194             ],
195             container: this.$refs.processCanvas,
196           });
197           // 任务节点悬浮事件
198           this.bpmnViewer.on('element.click', ({ element }) => {
199             this.onSelectElement(element);
200           });
201
202           this.isLoading = true;
203           await this.bpmnViewer.importXML(xml);
204           this.addCustomDefs();
205         } catch (e) {
206           this.clearViewer();
207         } finally {
208           this.isLoading = false;
209           this.setProcessStatus(this.processNodeInfo);
210         }
211       }
212     },
213     // 设置流程图元素状态
214     setProcessStatus (processNodeInfo) {
215       this.processNodeInfo = processNodeInfo;
216       if (this.isLoading || this.processNodeInfo == null || this.bpmnViewer == null) return;
217       let { finishedTaskSet, rejectedTaskSet, unfinishedTaskSet, finishedSequenceFlowSet } = this.processNodeInfo;
218       const canvas = this.bpmnViewer.get('canvas');
219       const elementRegistry = this.bpmnViewer.get('elementRegistry');
220       if (Array.isArray(finishedSequenceFlowSet)) {
221         finishedSequenceFlowSet.forEach(item => {
222           if (item != null) {
223             canvas.addMarker(item, 'success');
224             let element = elementRegistry.get(item);
225             const conditionExpression = element.businessObject.conditionExpression;
226             if (conditionExpression) {
227               canvas.addMarker(item, 'condition-expression');
228             }
229           }
230         });
231       }
232       if (Array.isArray(finishedTaskSet)) {
233         finishedTaskSet.forEach(item => canvas.addMarker(item, 'success'));
234       }
235       if (Array.isArray(unfinishedTaskSet)) {
236         unfinishedTaskSet.forEach(item => canvas.addMarker(item, 'primary'));
237       }
238       if (Array.isArray(rejectedTaskSet)) {
239         rejectedTaskSet.forEach(item => {
240           if (item != null) {
241             let element = elementRegistry.get(item);
242             if (element.type.includes('Task')) {
243               canvas.addMarker(item, 'danger');
244             } else {
245               canvas.addMarker(item, 'warning');
246             }
247           }
248         })
249       }
250     }
251   },
252   destroyed() {
253     this.clearViewer();
254   }
255 }
256 </script>
257
258 <style scoped>
259 </style>