| <!-- 审核流程插件基于https://gitee.com/xiaoka2017/easy-flow修改--> | 
| <!--感谢萌级小菜鸟 / easy-flow --> | 
| <template> | 
|     <div v-if="easyFlowVisible" class="flow-panel"> | 
|   | 
|         <div style="display: flex;height: 100%;position: relative;"> | 
|             <el-scrollbar style="height: 100%;border-right: 1px solid rgb(220, 227, 232);"> | 
|                 <div style="width: 220px;"> | 
|                     <div class="ef-node-pmenu-item"><i class="el-icon-warning-outline"></i>基础信息</div> | 
|                     <VolForm ref="form" style="padding: 10px;" :label-width="180" :loadKey="true" :formFields="formFields" | 
|                         :disabled="disabled" :formRules="formRules"></VolForm> | 
|                     <node-menu @addNode="addNode" ref="nodeMenu" v-if="!disabled"></node-menu> | 
|                 </div> | 
|             </el-scrollbar> | 
|             <div class="tools"> | 
|                 <el-button circle @click="zoomAdd"><i class="el-icon-zoom-in"></i></el-button> | 
|                 <el-button circle @click="zoomSub"><i class="el-icon-zoom-out"></i></el-button> | 
|             </div> | 
|             <div style="flex: 1;" id="efContainer" ref="efContainer" class="container efContainer" v-flowDrag> | 
|                 <template :key="node.id" v-for="node in data.nodeList"> | 
|                     <flow-node :id="node.id" @delNode="deleteNode(node.id)" :node="node" :activeElement="activeElement" | 
|                         :disabled="disabled" @changeNodeSite="changeNodeSite" @nodeRightMenu="nodeRightMenu" | 
|                         @clickNode="clickNode"> | 
|                     </flow-node> | 
|                 </template> | 
|                 <!-- 给画布一个默认的宽度和高度 --> | 
|                 <div style="position:absolute;top: 3000px;left: 4000px;"> </div> | 
|             </div> | 
|             <!-- 右侧表单 --> | 
|             <div style="width: 400px;border-left: 1px solid #dce3e8;background-color: #FBFBFB"> | 
|                 <el-scrollbar style="height: 100%;padding-bottom: 10px;"> | 
|                     <flow-node-form @delNode="deleteNode" ref="nodeForm" @setLineLabel="setLineLabel" :disabled="disabled" | 
|                         @repaintEverything="repaintEverything"></flow-node-form> | 
|                 </el-scrollbar> | 
|             </div> | 
|         </div> | 
|     </div> | 
| </template> | 
|   | 
| <script> | 
| import { VueDraggableNext as draggable } from "vue-draggable-next"; | 
| // import { jsPlumb } from 'jsplumb' | 
| // 使用修改后的jsplumb | 
| import './jsplumb' | 
| import { easyFlowMixin } from './mixins' | 
| import flowNode from './node' | 
| import nodeMenu from './node_menu' | 
| import FlowNodeForm from './node_form' | 
| import lodash from 'lodash' | 
| // import { getDataA } from './data_A' | 
| import VolForm from '@/components/basic/VolForm.vue'; | 
| export default { | 
|     props: { | 
|         disabled: { | 
|             typeof: Boolean, | 
|             default: false | 
|         } | 
|     }, | 
|     data() { | 
|         return { | 
|             formFields: { | 
|                 WorkName: '', | 
|                 WorkTable: '', | 
|                 WorkTableName: '', | 
|                 Weight: 1, | 
|                 AuditingEdit: 0, | 
|                 Remark: '' | 
|             }, | 
|             formRules: [ | 
|                 [ | 
|                     { | 
|                         dataKey: '流程名称', | 
|                         title: '流程名称', | 
|                         field: 'WorkName', | 
|                         required: true | 
|                     }], | 
|                 [{ | 
|                     dataKey: '', | 
|                     title: '流程实例', | 
|                     required: true, | 
|                     field: 'WorkTable', | 
|                     data: [], | 
|                     readonly: false, | 
|                     type: 'select', | 
|                     onChange: (value, item) => { | 
|                         this.formRules.forEach((options) => { | 
|                             options.forEach((option) => { | 
|                                 if (option.field == 'WorkTable') { | 
|                                     this.formFields.WorkTableName = option.data.find((x) => { | 
|                                         return x.key == value; | 
|                                     }).value; | 
|                                 } | 
|                             }); | 
|                         }); | 
|                     } | 
|                 }], | 
|                 [{ | 
|                     title: '权重(相同条件权重大优先)', | 
|                     field: 'Weight', | 
|                     type: "number", | 
|                 } | 
|                 ], | 
|                  | 
|                 [{ | 
|                     title: '审核中数据是否可以编辑', | 
|                     field: 'AuditingEdit', | 
|                     type: "switch", | 
|                     data: [{ key: 0, value: "否" }, { key: 1, value: "是" }] | 
|                 } | 
|                 ], | 
|                 [{ | 
|                     title: '备注', | 
|                     field: 'Remark' | 
|                 } | 
|                 ] | 
|             ], | 
|             // jsPlumb 实例 | 
|             jsPlumb: null, | 
|             // 控制画布销毁 | 
|             easyFlowVisible: true, | 
|             // 是否加载完毕标志位 | 
|             loadEasyFlowFinish: false, | 
|             // 数据 | 
|             data: {}, | 
|             // 激活的元素、可能是节点、可能是连线 | 
|             activeElement: { | 
|                 // 可选值 node 、line | 
|                 type: undefined, | 
|                 // 节点ID | 
|                 nodeId: undefined, | 
|                 // 连线ID | 
|                 sourceId: undefined, | 
|                 targetId: undefined | 
|             }, | 
|             zoom: 1 | 
|         } | 
|     }, | 
|     // 一些基础配置移动该文件中 | 
|     mixins: [easyFlowMixin], | 
|     components: { | 
|         draggable, flowNode, nodeMenu, FlowNodeForm, VolForm | 
|     }, | 
|     directives: { | 
|         'flowDrag': { | 
|             mounted(el, binding, vnode, oldNode) { | 
|                 if (!binding) { | 
|                     return | 
|                 } | 
|                 el.onmousedown = (e) => { | 
|                     if (e.button == 2) { | 
|                         // 右键不管 | 
|                         return | 
|                     } | 
|                     //  鼠标按下,计算当前原始距离可视区的高度 | 
|                     let disX = e.clientX | 
|                     let disY = e.clientY | 
|                     el.style.cursor = 'move' | 
|   | 
|                     document.onmousemove = function (e) { | 
|                         // 移动时禁止默认事件 | 
|                         e.preventDefault() | 
|                         const left = e.clientX - disX | 
|                         disX = e.clientX | 
|                         el.scrollLeft += -left | 
|   | 
|                         const top = e.clientY - disY | 
|                         disY = e.clientY | 
|                         el.scrollTop += -top | 
|                     } | 
|   | 
|                     document.onmouseup = function (e) { | 
|                         el.style.cursor = 'auto' | 
|                         document.onmousemove = null | 
|                         document.onmouseup = null | 
|                     } | 
|                 } | 
|             } | 
|         } | 
|     }, | 
|     mounted() { | 
|         this.jsPlumb = jsPlumb.getInstance() | 
|         // this.$nextTick(() => { | 
|         //     // 默认加载流程A的数据、在这里可以根据具体的业务返回符合流程数据格式的数据即可 | 
|         //     this.dataReload(getDataA()) | 
|         // }) | 
|     }, | 
|     created() { | 
|         this.http.get('api/Sys_WorkFlow/getTableInfo').then((result) => { | 
|             this.formRules.forEach((options) => { | 
|                 options.forEach((option) => { | 
|                     if (option.field == 'WorkTable') { | 
|                         option.data = result; | 
|                     } | 
|                 }); | 
|             }); | 
|         }); | 
|         this.$store.getters.data().flowTable = this.formFields; | 
|     }, | 
|     methods: { | 
|         // 返回唯一标识 | 
|         getUUID() { | 
|             return Math.random().toString(36).substr(3, 10) | 
|         }, | 
|         jsPlumbInit() { | 
|             this.jsPlumb.ready(() => { | 
|                 // 导入默认配置 | 
|                 this.jsPlumb.importDefaults(this.jsplumbSetting) | 
|                 // 会使整个jsPlumb立即重绘。 | 
|                 this.jsPlumb.setSuspendDrawing(false, true); | 
|                 // 初始化节点 | 
|                 this.loadEasyFlow() | 
|                 // 单点击了连接线, https://www.cnblogs.com/ysx215/p/7615677.html | 
|                 this.jsPlumb.bind('click', (conn, originalEvent) => { | 
|                     this.activeElement.type = 'line' | 
|                     this.activeElement.sourceId = conn.sourceId | 
|                     this.activeElement.targetId = conn.targetId | 
|                     this.$refs.nodeForm.lineInit({ | 
|                         from: conn.sourceId, | 
|                         to: conn.targetId, | 
|                         label: conn.getLabel() | 
|                     }) | 
|                     this.deleteElement(); | 
|                 }) | 
|                 // 连线 | 
|                 this.jsPlumb.bind("connection", (evt) => { | 
|                     let from = evt.source.id | 
|                     let to = evt.target.id | 
|                     if (this.loadEasyFlowFinish) { | 
|                         this.data.lineList.push({ from: from, to: to }) | 
|                     } | 
|                 }) | 
|   | 
|                 // 删除连线回调 | 
|                 this.jsPlumb.bind("connectionDetached", (evt) => { | 
|                     this.deleteLine(evt.sourceId, evt.targetId) | 
|                 }) | 
|   | 
|                 // 改变线的连接节点 | 
|                 this.jsPlumb.bind("connectionMoved", (evt) => { | 
|                     this.changeLine(evt.originalSourceId, evt.originalTargetId) | 
|                 }) | 
|   | 
|                 // 连线右击 | 
|                 this.jsPlumb.bind("contextmenu", (evt) => { | 
|                     console.log('contextmenu', evt) | 
|                 }) | 
|   | 
|                 // 连线 | 
|                 this.jsPlumb.bind("beforeDrop", (evt) => { | 
|                     let from = evt.sourceId | 
|                     let to = evt.targetId | 
|                     if (from === to) { | 
|                         this.$message.error('节点不支持连接自己') | 
|                         return false | 
|                     } | 
|                     if (this.hasLine(from, to)) { | 
|                         this.$message.error('该关系已存在,不允许重复创建') | 
|                         return false | 
|                     } | 
|                     if (this.hashOppositeLine(from, to)) { | 
|                         this.$message.error('不支持两个节点之间连线回环'); | 
|                         return false | 
|                     } | 
|                     this.$message.success('连接成功') | 
|                     setTimeout(() => { this.setLineLabel(from, to, 'x') }, 50) | 
|                     return true | 
|                 }) | 
|   | 
|                 // beforeDetach | 
|                 this.jsPlumb.bind("beforeDetach", (evt) => { | 
|                     console.log('beforeDetach', evt) | 
|                 }) | 
|                 this.jsPlumb.setContainer(this.$refs.efContainer) | 
|             }) | 
|         }, | 
|         // 加载流程图 | 
|         loadEasyFlow() { | 
|             // 初始化节点 | 
|             for (var i = 0; i < this.data.nodeList.length; i++) { | 
|                 let node = this.data.nodeList[i] | 
|                 if (node.userId && node.userId != '') { | 
|                     // userId为数值类型 | 
|                     if (typeof node.userId == 'number'){ | 
|                         node.userId = [node.userId] | 
|                     } else { | 
|                         node.userId = node.userId.split(',').map(Number); | 
|                     } | 
|                 } else { | 
|                     node.userId = [] | 
|                 } | 
|                 // 设置源点,可以拖出线连接其他节点 | 
|                 this.jsPlumb.makeSource(node.id, lodash.merge(this.jsplumbSourceOptions, {})) | 
|                 // // 设置目标点,其他源点拖出的线可以连接该节点 | 
|                 this.jsPlumb.makeTarget(node.id, this.jsplumbTargetOptions) | 
|                 if (!node.viewOnly && !this.disabled) { | 
|                     this.jsPlumb.draggable(node.id, { | 
|                         containment: 'parent', | 
|                         stop: function (el) { | 
|                             // 拖拽节点结束后的对调 | 
|                             console.log('拖拽结束: ', el) | 
|                         } | 
|                     }) | 
|                 } | 
|             } | 
|             // 初始化连线 | 
|             for (var i = 0; i < this.data.lineList.length; i++) { | 
|                 let line = this.data.lineList[i] | 
|                 var connParam = { | 
|                     source: line.from, | 
|                     target: line.to, | 
|                     label: this.disabled ? null : (line.label ? line.label : 'x'), | 
|                     connector: line.connector ? line.connector : '', | 
|                     anchors: line.anchors ? line.anchors : undefined, | 
|   | 
|                     paintStyle: line.paintStyle ? line.paintStyle : undefined, | 
|                 } | 
|                 this.jsPlumb.connect(connParam, this.jsplumbConnectOptions) | 
|             } | 
|             this.$nextTick(function () { | 
|                 this.loadEasyFlowFinish = true | 
|             }) | 
|         }, | 
|         // 设置连线条件 | 
|         setLineLabel(from, to, label) { | 
|             var conn = this.jsPlumb.getConnections({ | 
|                 source: from, | 
|                 target: to | 
|             })[0] | 
|             if (!label || label === '') { | 
|                 conn.removeClass('flowLabel ') | 
|                 conn.addClass('emptyFlowLabel') | 
|             } else { | 
|                 conn.addClass('flowLabel') | 
|             } | 
|             conn.setLabel({ | 
|                 label: 'x' //label, | 
|             }) | 
|             this.data.lineList.forEach(function (line) { | 
|                 if (line.from == from && line.to == to) { | 
|                     line.label = 'x'// label | 
|                 } | 
|             }) | 
|   | 
|         }, | 
|         // 删除激活的元素 | 
|         deleteElement() { | 
|             if (this.disabled) | 
|                 return | 
|             if (this.activeElement.type === 'node') { | 
|                 this.deleteNode(this.activeElement.nodeId) | 
|             } else if (this.activeElement.type === 'line') { | 
|                 this.$confirm('确定删除所点击的线吗?', '提示', { | 
|                     confirmButtonText: '确定', | 
|                     cancelButtonText: '取消', | 
|                     type: 'warning' | 
|                 }).then(() => { | 
|                     var conn = this.jsPlumb.getConnections({ | 
|                         source: this.activeElement.sourceId, | 
|                         target: this.activeElement.targetId | 
|                     })[0] | 
|                     this.jsPlumb.deleteConnection(conn) | 
|                 }).catch(() => { | 
|                 }) | 
|             } | 
|         }, | 
|         // 删除线 | 
|         deleteLine(from, to) { | 
|             this.data.lineList = this.data.lineList.filter(function (line) { | 
|                 if (line.from == from && line.to == to) { | 
|                     return false | 
|                 } | 
|                 return true | 
|             }) | 
|         }, | 
|         // 改变连线 | 
|         changeLine(oldFrom, oldTo) { | 
|             this.deleteLine(oldFrom, oldTo) | 
|         }, | 
|         // 改变节点的位置 | 
|         changeNodeSite(data) { | 
|             for (var i = 0; i < this.data.nodeList.length; i++) { | 
|                 let node = this.data.nodeList[i] | 
|                 if (node.id === data.nodeId) { | 
|                     node.left = data.left | 
|                     node.top = data.top | 
|                 } | 
|             } | 
|         }, | 
|         /** | 
|          * 拖拽结束后添加新的节点 | 
|          * @param evt | 
|          * @param nodeMenu 被添加的节点对象 | 
|          * @param mousePosition 鼠标拖拽结束的坐标 | 
|          */ | 
|         addNode(evt, nodeMenu, mousePosition) { | 
|             if (nodeMenu.type == 'start' && this.data.nodeList.some(x => { return x.type == 'start' })) { | 
|                 this.$message.error('【流程结束】节点已存在,只有选择一个流程开始节点'); | 
|                 return | 
|             } | 
|             if (nodeMenu.type == 'end' && this.data.nodeList.some(x => { return x.type == 'end' })) { | 
|                 this.$message.error('【流程结束】节点已存在,只有选择一个流程开始节点'); | 
|                 return | 
|             } | 
|             var screenX = evt.originalEvent.clientX, screenY = evt.originalEvent.clientY | 
|             let efContainer = this.$refs.efContainer | 
|             var containerRect = efContainer.getBoundingClientRect() | 
|             var left = screenX, top = screenY | 
|             // 计算是否拖入到容器中 | 
|             if (left < containerRect.x || left > containerRect.width + containerRect.x || top < containerRect.y || containerRect.y > containerRect.y + containerRect.height) { | 
|                 this.$message.error("请把节点拖入到画布中") | 
|                 return | 
|             } | 
|             left = left - containerRect.x + efContainer.scrollLeft | 
|             top = top - containerRect.y + efContainer.scrollTop | 
|             // 居中 | 
|             left -= 85 | 
|             top -= 16 | 
|             var nodeId = this.getUUID() | 
|             // 动态生成名字 | 
|             var origName = nodeMenu.name | 
|             var nodeName = origName | 
|             var index = 1 | 
|             while (index < 10000) { | 
|                 var repeat = false | 
|                 for (var i = 0; i < this.data.nodeList.length; i++) { | 
|                     let node = this.data.nodeList[i] | 
|                     if (node.name === nodeName) { | 
|                         nodeName = origName + index | 
|                         repeat = true | 
|                     } | 
|                 } | 
|                 if (repeat) { | 
|                     index++ | 
|                     continue | 
|                 } | 
|                 break | 
|             } | 
|             var node = { | 
|                 id: nodeId, | 
|                 name: nodeName, | 
|                 type: nodeMenu.type, | 
|                 left: left + 'px', | 
|                 top: top + 'px', | 
|                 ico: nodeMenu.ico, | 
|                 state: 'success' | 
|             } | 
|             /** | 
|              * 这里可以进行业务判断、是否能够添加该节点 | 
|              */ | 
|             this.data.nodeList.push(node) | 
|             this.$nextTick(function () { | 
|                 this.jsPlumb.makeSource(nodeId, this.jsplumbSourceOptions) | 
|                 this.jsPlumb.makeTarget(nodeId, this.jsplumbTargetOptions) | 
|                 this.jsPlumb.draggable(nodeId, { | 
|                     containment: 'parent', | 
|                     stop: function (el) { | 
|                         // 拖拽节点结束后的对调 | 
|                         console.log('拖拽结束: ', el) | 
|                     } | 
|                 }) | 
|             }) | 
|         }, | 
|         /** | 
|          * 删除节点 | 
|          * @param nodeId 被删除节点的ID | 
|          */ | 
|         deleteNode(nodeId) { | 
|             this.$confirm('确定要删除节点' + nodeId + '?', '提示', { | 
|                 confirmButtonText: '确定', | 
|                 cancelButtonText: '取消', | 
|                 type: 'warning', | 
|                 closeOnClickModal: false | 
|             }).then(() => { | 
|                 /** | 
|                  * 这里需要进行业务判断,是否可以删除 | 
|                  */ | 
|                 this.data.nodeList = this.data.nodeList.filter(function (node) { | 
|                     if (node.id === nodeId) { | 
|                         // 伪删除,将节点隐藏,否则会导致位置错位 | 
|                         // node.show = false | 
|                         return false | 
|                     } | 
|                     return true | 
|                 }) | 
|                 this.$nextTick(function () { | 
|                     this.jsPlumb.removeAllEndpoints(nodeId); | 
|                 }) | 
|             }).catch(() => { | 
|             }) | 
|             return true | 
|         }, | 
|         clickNode(nodeId) { | 
|             this.activeElement.type = 'node' | 
|             this.activeElement.nodeId = nodeId | 
|             this.$refs.nodeForm.nodeInit(this.data, nodeId, this.formFields.WorkTable) | 
|         }, | 
|         // 是否具有该线 | 
|         hasLine(from, to) { | 
|             for (var i = 0; i < this.data.lineList.length; i++) { | 
|                 var line = this.data.lineList[i] | 
|                 if (line.from === from && line.to === to) { | 
|                     return true | 
|                 } | 
|             } | 
|             return false | 
|         }, | 
|         // 是否含有相反的线 | 
|         hashOppositeLine(from, to) { | 
|             return this.hasLine(to, from) | 
|         }, | 
|         nodeRightMenu(nodeId, evt) { | 
|             this.menu.show = true | 
|             this.menu.curNodeId = nodeId | 
|             this.menu.left = evt.x + 'px' | 
|             this.menu.top = evt.y + 'px' | 
|         }, | 
|         repaintEverything(node) { | 
|             let _node = this.data.nodeList.find((x) => { | 
|                 return x.id == node.id; | 
|             }); | 
|             Object.assign(_node, node); | 
|             console.log(_node); | 
|             this.jsPlumb.repaint(); | 
|         }, | 
|         // 加载流程图 | 
|         dataReload(data, isAdd) { | 
|             this.easyFlowVisible = false | 
|             this.data.nodeList = [] | 
|             this.data.lineList = [] | 
|             this.$nextTick(() => { | 
|                 data = lodash.cloneDeep(data) | 
|                 this.easyFlowVisible = true | 
|                 this.data = data | 
|                 this.$nextTick(() => { | 
|                     this.jsPlumb = jsPlumb.getInstance() | 
|                     this.$nextTick(() => { | 
|                         this.jsPlumbInit() | 
|                     }) | 
|                 }) | 
|             }) | 
|             this.formRules.forEach(options => { | 
|                 options.forEach(option => { | 
|                     if (option.field == "WorkTable") { | 
|                         option.readonly = !isAdd; | 
|                     } | 
|                 }) | 
|             }) | 
|         }, | 
|         zoomAdd() { | 
|             if (this.zoom >= 1) { | 
|                 return | 
|             } | 
|             this.zoom = this.zoom + 0.1 | 
|             this.$refs.efContainer.style.zoom = this.zoom; | 
|             // this.jsPlumb.setZoom(this.zoom) | 
|         }, | 
|         zoomSub() { | 
|             if (this.zoom <= 0) { | 
|                 return | 
|             } | 
|             this.zoom = this.zoom - 0.1; | 
|             if (this.zoom < 0.3) { | 
|                 this.zoom = 0.3; | 
|             } | 
|             this.$refs.efContainer.style.zoom = this.zoom; | 
|             // this.jsPlumb.setZoom(this.zoom) | 
|         } | 
|     } | 
| } | 
| </script> | 
| <style scoped lang="less"> | 
| @import './index.css'; | 
|   | 
| .flow-panel { | 
|     position: absolute; | 
|     height: 100%; | 
|     width: 100%; | 
| } | 
|   | 
| .flow-panel ::v-deep(.el-form-item__label) { | 
|     margin-bottom: -2px !important; | 
|     text-align: left; | 
|     padding: 0 !important; | 
|     justify-content: flex-start; | 
| } | 
|   | 
| .flow-panel ::v-deep(.el-form-item) { | 
|     display: flex; | 
|     flex-direction: column; | 
|     margin-bottom: 7px !important; | 
|   | 
| } | 
|   | 
| .ef-node-menu-form { | 
|     padding: 0px; | 
| } | 
|   | 
| ::-webkit-scrollbar { | 
|     width: 0px; | 
|     height: 0px; | 
| } | 
|   | 
| ::-webkit-scrollbar-thumb { | 
|     border-radius: 0px; | 
|     background: #e0e3e7; | 
|     height: 20px; | 
| } | 
|   | 
| ::-webkit-scrollbar-track { | 
|     background-color: transparent; | 
| } | 
| </style> |