<!-- * @Description: * @Autor: renchao * @LastEditTime: 2023-05-17 10:41:15 --> <template> <div> <div class="process-viewer"> <div v-show="!isLoading" ref="processCanvas" class="process-canvas" style="height: 280px" /> <!-- 自定义箭头样式,用于成功状态下流程连线箭头 --> <defs ref="customSuccessDefs"> <marker id="sequenceflow-end-white-success" view-box="0 0 20 20" ref-x="11" ref-y="10" marker-width="10" marker-height="10" orient="auto"> <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; " /> </marker> <marker id="conditional-flow-marker-white-success" view-box="0 0 20 20" ref-x="-1" ref-y="10" marker-width="10" marker-height="10" orient="auto"> <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; " /> </marker> </defs> <!-- 自定义箭头样式,用于失败状态下流程连线箭头 --> <defs ref="customFailDefs"> <marker id="sequenceflow-end-white-fail" view-box="0 0 20 20" ref-x="11" ref-y="10" marker-width="10" marker-height="10" orient="auto"> <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; " /> </marker> <marker id="conditional-flow-marker-white-fail" view-box="0 0 20 20" ref-x="-1" ref-y="10" marker-width="10" marker-height="10" orient="auto"> <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; " /> </marker> </defs> <div style="position: absolute; top: 0px; left: 0px; width: 100%"> <el-row type="flex" justify="end"> <el-button-group key="scale-control" size="medium"> <el-button size="medium" type="default" :plain="true" :disabled="defaultZoom <= 0.3" icon="el-icon-zoom-out" @click="processZoomOut()" /> <el-button size="medium" type="default" style="width: 90px">{{ Math.floor(this.defaultZoom * 10 * 10) + "%" }}</el-button> <el-button size="medium" type="default" :plain="true" :disabled="defaultZoom >= 3.9" icon="el-icon-zoom-in" @click="processZoomIn()" /> <el-button size="medium" type="default" icon="el-icon-c-scale-to-original" @click="processReZoom()" /> <slot /> </el-button-group> </el-row> </div> </div> <!-- 已完成节点悬浮弹窗 --> <div class="information-list"> <el-select v-model="selectValue" @change="handleSelect"> <el-option v-for="item in selectOptions" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select> <el-table height="190" :data="taskCommentList" size="mini" border header-cell-class-name="table-header-gray"> <el-table-column label="序号" header-align="center" align="center" type="index" width="55px" /> <el-table-column label="流程状态" header-align="center" align="center"> <template slot-scope="scope"> <div v-if="scope.row.endTime">已完结</div> <div v-else>正在办理</div> </template> </el-table-column> <el-table-column label="环节名称" prop="name" minWidth="100" align="center" /> <el-table-column label="办理人" prop="agent" minWidth="120" align="center" /> <el-table-column label="转入时间" prop="createTime" :formatter="formatDate" width="160" align="center" /> <el-table-column label="认领时间" prop="claimTime" :formatter="formatDate" width="160" align="center" /> <el-table-column label="转出时间" prop="endTime" :formatter="formatDate" width="160" align="center" /> <el-table-column label="操作方式" prop="controls" align="center" /> <el-table-column label="意见" prop="idea" align="center" /> </el-table> </div> </div> </template> <script> import "@/styles/package/theme/index.scss"; import BpmnViewer from "bpmn-js/lib/Viewer"; import MoveCanvasModule from "diagram-js/lib/navigation/movecanvas"; export default { props: { formData: { type: Object, default: {}, }, }, data () { return { dlgTitle: undefined, defaultZoom: 1, // 是否正在加载流程图 isLoading: true, bpmnViewer: undefined, // 已完成流程元素 processNodeInfo: undefined, // 当前任务id selectTaskId: undefined, // 任务节点审批记录 taskList: [], taskCommentList: [], // 已完成任务悬浮延迟Timer hoverTimer: null, // 下拉 selectValue: "", selectOptions: [], }; }, created () { this.$nextTick(() => { // 获取流程记录 this.getCommentList(); this.setProcessStatus(this.formData.finishedInfo); this.importXML(this.formData.xml); }); }, destroyed () { this.clearViewer(); }, methods: { /** * @description: formatDate * @param {*} row * @param {*} column * @author: renchao */ formatDate (row, column) { let data = row[column.property]; if (data == null) { return null; } let dt = new Date(data); return ( dt.getFullYear() + "-" + (dt.getMonth() + 1) + "-" + dt.getDate() + " " + dt.getHours() + ":" + dt.getMinutes() + ":" + dt.getSeconds() ); }, /** * @description: processReZoom * @author: renchao */ processReZoom () { this.defaultZoom = 1; this.bpmnViewer.get("canvas").zoom("fit-viewport", "auto"); }, /** * @description: processZoomIn * @param {*} zoomStep * @author: renchao */ processZoomIn (zoomStep = 0.1) { const newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100; if (newZoom > 4) { throw new Error( "[Process Designer Warn ]: The zoom ratio cannot be greater than 4" ); } this.defaultZoom = newZoom; this.bpmnViewer.get("canvas").zoom(this.defaultZoom); }, /** * @description: processZoomOut * @param {*} zoomStep * @author: renchao */ processZoomOut (zoomStep = 0.1) { const newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100; if (newZoom < 0.2) { throw new Error( "[Process Designer Warn ]: The zoom ratio cannot be scss than 0.2" ); } this.defaultZoom = newZoom; this.bpmnViewer.get("canvas").zoom(this.defaultZoom); }, /** * @description: getOperationTagType * @param {*} type * @author: renchao */ getOperationTagType (type) { return "success"; }, // 流程图预览清空 /** * @description: 流程图预览清空 * @param {*} e * @author: renchao */ clearViewer (a) { if (this.$refs.processCanvas) { this.$refs.processCanvas.innerHTML = ""; } if (this.bpmnViewer) { this.bpmnViewer.destroy(); } this.bpmnViewer = null; }, // 添加自定义箭头 /** * @description: 添加自定义箭头 * @author: renchao */ addCustomDefs () { const canvas = this.bpmnViewer.get("canvas"); const svg = canvas._svg; const customSuccessDefs = this.$refs.customSuccessDefs; const customFailDefs = this.$refs.customFailDefs; svg.appendChild(customSuccessDefs); svg.appendChild(customFailDefs); }, // 任务悬浮弹窗 /** * @description: 任务悬浮弹窗 * @param {*} element * @author: renchao */ onSelectElement (element) { this.selectTaskId = undefined; this.dlgTitle = undefined; let allfinishedTaskSet = [ ...this.processNodeInfo.finishedTaskSet, ...this.processNodeInfo.unfinishedTaskSet, ]; if (this.processNodeInfo == null || allfinishedTaskSet == null) return; if (element == null || allfinishedTaskSet.indexOf(element.id) === -1) { return; } this.selectTaskId = element.id; this.selectValue = element.id; this.dlgTitle = element.businessObject ? element.businessObject.name : undefined; // 计算当前悬浮任务审批记录,如果记录为空不显示弹窗 this.taskCommentList = (this.taskList || []).filter((item) => { return item.taskDefinitionKey === this.selectTaskId; }); if (this.taskCommentList.length == 0) { this.taskCommentList = this.taskList; } }, // 下拉列表切换 /** * @description: 下拉列表切换 * @param {*} val * @author: renchao */ handleSelect (val) { this.taskCommentList = (this.taskList || []).filter((item) => { return item.taskDefinitionKey === val; }); if (this.taskCommentList.length == 0) { this.taskCommentList = this.taskList; } }, // 显示流程图 /** * @description: 显示流程图 * @param {*} xml * @author: renchao */ async importXML (xml) { let xmlData = this.$x2js.xml2js(xml).definitions.process; this.selectOptions = xmlData.userTask.map((item) => { return { value: item._id, label: item._name }; }); this.selectOptions = [ { value: xmlData.startEvent._id, label: "浏览记录" }, ...this.selectOptions, ]; this.selectOptions = this.selectOptions .map((item) => { if (this.formData.finishedInfo.finishedTaskSet.includes(item.value)) { return item; } if ( this.formData.finishedInfo.unfinishedTaskSet.includes(item.value) ) { return item; } }) .filter(Boolean); this.selectValue = xmlData.startEvent._id; this.clearViewer("a"); if (xml != null && xml !== "") { try { this.bpmnViewer = new BpmnViewer({ additionalModules: [ // 移动整个画布 MoveCanvasModule, ], container: this.$refs.processCanvas, }); // 任务节点悬浮事件 this.bpmnViewer.on("element.click", ({ element }) => { this.onSelectElement(element); }); await this.bpmnViewer.importXML(xml); this.isLoading = true; this.addCustomDefs(); } catch (e) { this.clearViewer("b"); } finally { this.isLoading = false; this.setProcessStatus(this.processNodeInfo); this.$nextTick(() => { this.processReZoom(); }); } } }, // 获取流程记录 /** * @description: 获取流程记录 * @author: renchao */ getCommentList () { this.formData.allCommentList.forEach(async (item, index) => { // item.comments.forEach(element => { // if(element.type=="COMPLETE"){ // this.formData.allCommentList[index].idea=element.message // this.formData.allCommentList[index].controls="完成" // } // }); let type = item.comments[item.comments.length - 1].type; this.formData.allCommentList[index].idea = item.comments[item.comments.length - 1].message; // 操作方式 let controls = ""; switch (type) { case "COMPLETE": controls = "完成"; break; case "CLAIM": controls = "完成"; break; case "ASSIGN": controls = "转办"; break; case "DELEGATE": controls = "委派"; break; case "UNCLAIM": controls = "取消认领"; break; case "STOP": controls = "终止"; break; case "BACK": controls = "退回"; break; } this.formData.allCommentList[index].controls = controls; this.formData.allCommentList[index].agent = item.assignee.name; }); this.formData.handlinglist.forEach(async (item, index) => { if (item.assignee.name) { this.formData.handlinglist[index].agent = item.assignee.name; } else { let str = ""; item.countersign.forEach((item) => { str += item.name + ","; }); str = str.slice(0, -1); this.formData.allCommentList[index].agent = str; } }); this.taskList = [ ...this.formData.allCommentList, ...this.formData.handlinglist, ]; // this.taskList =this.formData.allCommentList; // 处理数据之后赋值 this.taskCommentList = this.taskList; }, // 设置流程图元素状态 /** * @description: 设置流程图元素状态 * @param {*} processNodeInfo * @author: renchao */ setProcessStatus (processNodeInfo) { this.processNodeInfo = processNodeInfo; if ( this.isLoading || this.processNodeInfo == null || this.bpmnViewer == null ) return; const { finishedTaskSet, rejectedTaskSet, unfinishedTaskSet, finishedSequenceFlowSet, } = this.processNodeInfo; const canvas = this.bpmnViewer.get("canvas"); const elementRegistry = this.bpmnViewer.get("elementRegistry"); if (Array.isArray(finishedSequenceFlowSet)) { finishedSequenceFlowSet.forEach((item) => { if (item != null) { canvas.addMarker(item, "success"); const element = elementRegistry.get(item); const conditionExpression = element.businessObject.conditionExpression; if (conditionExpression) { canvas.addMarker(item, "condition-expression"); } } }); } if (Array.isArray(finishedTaskSet)) { finishedTaskSet.forEach((item) => canvas.addMarker(item, "success")); } if (Array.isArray(unfinishedTaskSet)) { unfinishedTaskSet.forEach((item) => canvas.addMarker(item, "primary")); } if (Array.isArray(rejectedTaskSet)) { rejectedTaskSet.forEach((item) => { if (item != null) { const element = elementRegistry.get(item); if (element.type.includes("Task")) { canvas.addMarker(item, "danger"); } else { canvas.addMarker(item, "warning"); } } }); } }, }, }; </script> <style scoped lang="scss"> .information-list { height: 220px; margin-top: 10px; p { font-size: 16px; line-height: 24px; } } /deep/.bjs-powered-by { display: none; } // /deep/.information-list { // height: 170px; // overflow: visible; // } </style>