1 <template> 2 <v-container class="workflow-container" grid-list-xl fluid> 3 <div class="super-flow-demo"> 4 <div class="node-container"> 5 <div 6 class="node-item" 7 v-for="(item, index) in nodeItemList" 8 :key="index" 9 @mousedown="evt => nodeItemMouseDown(evt, item.value)"> 10 {{ item.label }} 11 </div> 12 </div> 13 <div 14 class="flow-container" 15 ref="flowContainer"> 16 <super-flow 17 ref="superFlow" 18 :graph-menu="graphMenu" 19 :node-menu="nodeMenu" 20 :link-menu="linkMenu" 21 :link-desc="linkDesc" 22 :node-list="nodeList" 23 :link-list="linkList"> 24 <template v-slot:node="{meta}"> 25 <div 26 :class="meta.type? `flow-node-${meta.type}`: ''" 27 class="flow-node ellipsis"> 28 <div class="node-content" :title="meta.name">{{ meta.name }}</div> 29 </div> 30 </template> 31 </super-flow> 32 <v-btn 33 @click="saveFlow" 34 color="primary" 35 class="saveIcon" 36 >SAVE 37 </v-btn> 38 </div> 39 </div> 40 41 <el-dialog 42 :title="drawerConf.title" 43 :visible.sync="drawerConf.visible" 44 :close-on-click-modal="false" 45 width="500px"> 46 <el-form 47 @keyup.native.enter="settingSubmit" 48 @submit.native.prevent 49 v-show="drawerConf.type === drawerType.node" 50 ref="nodeSetting" 51 :model="nodeSetting"> 52 <el-form-item 53 label="node name" 54 prop="name"> 55 <el-input 56 v-model="nodeSetting.name" 57 placeholder="Please enter the node name" 58 maxlength="30"> 59 </el-input> 60 </el-form-item> 61 <el-form-item 62 label="node description" 63 prop="desc"> 64 <el-input 65 v-model="nodeSetting.desc" 66 placeholder="Please enter a node description" 67 maxlength="30"> 68 </el-input> 69 </el-form-item> 70 </el-form> 71 <el-form 72 @keyup.native.enter="settingSubmit" 73 @submit.native.prevent 74 v-show="drawerConf.type === drawerType.link" 75 ref="linkSetting" 76 :model="linkSetting"> 77 <el-form-item 78 label="link description" 79 prop="desc"> 80 <el-input 81 v-model="linkSetting.desc" 82 placeholder="Please enter a link description"> 83 </el-input> 84 </el-form-item> 85 </el-form> 86 <span 87 slot="footer" 88 class="dialog-footer"> 89 <el-button @click="drawerConf.cancel"> 90 CANCEL 91 </el-button> 92 <el-button type="primary" @click="settingSubmit"> 93 OK 94 </el-button> 95 </span> 96 </el-dialog> 97 </v-container> 98 </template> 99 100 <script> 101 import SuperFlow from 'vue-super-flow' 102 import 'vue-super-flow/lib/index.css' 103 104 const drawerType = { 105 node: 0, 106 link: 1 107 } 108 109 export default { 110 components: { 111 SuperFlow 112 }, 113 data () { 114 return { 115 drawerType, 116 nodeList: [ 117 { 118 id: "N1", 119 coordinate: [771, 32], 120 width: 120, 121 height: 40, 122 meta: { 123 label: 'start', 124 name: 'start', 125 type: 'start' 126 } 127 }, 128 { 129 id: "N2", 130 coordinate: [731, 137], 131 width: 200, 132 height: 40, 133 meta: { 134 desc: '1', 135 label: 'process', 136 name: 'process11111', 137 type: 'process' 138 } 139 }, 140 { 141 id: "N3", 142 coordinate: [747, 237], 143 width: 168, 144 height: 168, 145 meta: { 146 desc: '?', 147 label: 'if', 148 name: 'if?????', 149 type: 'if' 150 } 151 }, 152 { 153 id: "N4", 154 coordinate: [731, 505], 155 width: 200, 156 height: 40, 157 meta: { 158 desc: '2', 159 label: 'process', 160 name: 'process22222', 161 type: 'process' 162 } 163 }, 164 { 165 id: "N5", 166 coordinate: [1088, 300], 167 width: 200, 168 height: 40, 169 meta: { 170 desc: '3', 171 label: 'process', 172 name: 'process33333', 173 type: 'process' 174 } 175 }, 176 { 177 id: "N6", 178 coordinate: [771, 597], 179 width: 120, 180 height: 40, 181 meta: { 182 label: 'end', 183 name: 'end', 184 type: 'end' 185 } 186 } 187 ], 188 linkList: [ 189 { 190 id: "L1", 191 startAt: [60, 40], 192 startId: "N1", 193 endAt: [100, 0], 194 endId: "N2", 195 meta: null 196 }, 197 { 198 id: "L2", 199 startAt: [100, 40], 200 startId: "N2", 201 endAt: [84, 0], 202 endId: "N3", 203 meta: null 204 }, 205 { 206 id: "L3", 207 startAt: [100, 40], 208 startId: "N4", 209 endAt: [60, 0], 210 endId: "N6", 211 meta: null 212 }, 213 { 214 id: "L4", 215 startAt: [84, 168], 216 startId: "N3", 217 endAt: [100, 0], 218 endId: "N4", 219 meta: { 220 desc: 'YES' 221 } 222 }, 223 { 224 id: "L5", 225 startAt: [168, 84], 226 startId: "N3", 227 endAt: [0, 20], 228 endId: "N5", 229 meta: { 230 desc: 'NO' 231 } 232 }, 233 { 234 id: "L6", 235 startAt: [100, 0], 236 startId: "N5", 237 endAt: [200, 20], 238 endId: "N2", 239 meta: null 240 } 241 ], 242 drawerConf: { 243 title: '', 244 visible: false, 245 type: null, 246 info: null, 247 open: (type, info) => { 248 const conf = this.drawerConf 249 conf.visible = true 250 conf.type = type 251 conf.info = info 252 if (conf.type === drawerType.node) { 253 conf.title = 'NODE' 254 if (this.$refs.nodeSetting) this.$refs.nodeSetting.resetFields() 255 this.$set(this.nodeSetting, 'name', info.meta.name) 256 this.$set(this.nodeSetting, 'desc', info.meta.desc) 257 } else { 258 conf.title = 'LINK' 259 if (this.$refs.linkSetting) this.$refs.linkSetting.resetFields() 260 this.$set(this.linkSetting, 'desc', info.meta ? info.meta.desc : '') 261 } 262 }, 263 cancel: () => { 264 this.drawerConf.visible = false 265 if (this.drawerConf.type === drawerType.node) { 266 this.$refs.nodeSetting.clearValidate() 267 } else { 268 this.$refs.linkSetting.clearValidate() 269 } 270 } 271 }, 272 linkSetting: { 273 desc: '' 274 }, 275 nodeSetting: { 276 name: '', 277 desc: '' 278 }, 279 nodeItemList: [ 280 { 281 label: 'start', 282 value: () => ({ 283 width: 120, 284 height: 40, 285 meta: { 286 label: 'start', 287 name: 'start', 288 type: 'start' 289 } 290 }) 291 }, 292 { 293 label: 'process', 294 value: () => ({ 295 width: 200, 296 height: 40, 297 meta: { 298 label: 'process', 299 name: 'process', 300 type: 'process' 301 } 302 }) 303 }, 304 { 305 label: 'if', 306 value: () => ({ 307 width: 168, 308 height: 168, 309 meta: { 310 label: 'if', 311 name: 'if', 312 type: 'if' 313 } 314 }) 315 }, 316 { 317 label: 'end', 318 value: () => ({ 319 width: 120, 320 height: 40, 321 meta: { 322 label: 'end', 323 name: 'end', 324 type: 'end' 325 } 326 }) 327 } 328 ], 329 graphMenu: [ 330 [ 331 { 332 // 选项 label 333 label: 'start', 334 // 选项是否禁用 335 disable (graph) { 336 return !!graph.nodeList.find(node => node.meta.label === 'start') 337 }, 338 // 选项选中后回调函数 339 selected (graph, coordinate) { 340 graph.addNode({ 341 width: 120, 342 height: 40, 343 coordinate, 344 meta: { 345 label: 'start', 346 name: 'start', 347 type: 'start' 348 } 349 }) 350 } 351 }, 352 { 353 label: 'process', 354 selected (graph, coordinate) { 355 graph.addNode({ 356 width: 200, 357 height: 40, 358 coordinate, 359 meta: { 360 label: 'process', 361 name: 'process', 362 type: 'process' 363 } 364 }) 365 } 366 }, 367 { 368 label: 'if', 369 selected (graph, coordinate) { 370 graph.addNode({ 371 width: 168, 372 height: 168, 373 coordinate, 374 meta: { 375 label: 'if', 376 name: 'if', 377 type: 'if' 378 } 379 }) 380 } 381 } 382 ], 383 [ 384 { 385 label: 'end', 386 selected (graph, coordinate) { 387 graph.addNode({ 388 width: 120, 389 height: 40, 390 coordinate, 391 meta: { 392 label: 'end', 393 name: 'end', 394 type: 'end' 395 } 396 }) 397 } 398 } 399 ], 400 [ 401 { 402 label: 'select all', 403 selected: graph => { 404 graph.selectAll() 405 } 406 } 407 ] 408 ], 409 nodeMenu: [ 410 [ 411 { 412 label: 'delete', 413 selected: node => { 414 node.remove() 415 } 416 }, 417 { 418 label: 'edit', 419 selected: node => { 420 this.drawerConf.open(drawerType.node, node) 421 } 422 } 423 ] 424 ], 425 linkMenu: [ 426 [ 427 { 428 label: 'delete', 429 selected: link => { 430 link.remove() 431 } 432 }, 433 { 434 label: 'edit', 435 selected: link => { 436 this.drawerConf.open(drawerType.link, link) 437 } 438 } 439 ] 440 ], 441 dragConf: { 442 isDown: false, 443 isMove: false, 444 offsetTop: 0, 445 offsetLeft: 0, 446 clientX: 0, 447 clientY: 0, 448 ele: null, 449 info: null 450 } 451 } 452 }, 453 mounted () { 454 document.addEventListener('mousemove', this.docMousemove) 455 document.addEventListener('mouseup', this.docMouseup) 456 this.$once('hook:beforeDestroy', () => { 457 document.removeEventListener('mousemove', this.docMousemove) 458 document.removeEventListener('mouseup', this.docMouseup) 459 }) 460 }, 461 methods: { 462 saveFlow () { 463 this.nodeList = this.$refs.superFlow.toJSON().nodeList 464 this.linkList = this.$refs.superFlow.toJSON().linkList 465 console.log(this.nodeList) 466 console.log(this.linkList) 467 }, 468 linkDesc (link) { 469 return link.meta ? link.meta.desc : '' 470 }, 471 settingSubmit () { 472 const conf = this.drawerConf 473 if (this.drawerConf.type === drawerType.node) { 474 if (!conf.info.meta) conf.info.meta = {} 475 Object.keys(this.nodeSetting).forEach(key => { 476 this.$set(conf.info.meta, key, this.nodeSetting[key]) 477 }) 478 this.$refs.nodeSetting.resetFields() 479 } else { 480 if (!conf.info.meta) conf.info.meta = {} 481 Object.keys(this.linkSetting).forEach(key => { 482 this.$set(conf.info.meta, key, this.linkSetting[key]) 483 }) 484 this.$refs.linkSetting.resetFields() 485 } 486 conf.visible = false 487 }, 488 docMousemove ({ clientX, clientY }) { 489 const conf = this.dragConf 490 if (conf.isMove) { 491 conf.ele.style.top = clientY - conf.offsetTop + 'px' 492 conf.ele.style.left = clientX - conf.offsetLeft + 'px' 493 } else if (conf.isDown) { 494 // 鼠标移动量大于 5 时 移动状态生效 495 conf.isMove = Math.abs(clientX - conf.clientX) > 5 || Math.abs(clientY - conf.clientY) > 5 496 } 497 }, 498 docMouseup ({ clientX, clientY }) { 499 const conf = this.dragConf 500 conf.isDown = false 501 502 if (conf.isMove) { 503 const { 504 top, 505 right, 506 bottom, 507 left 508 } = this.$refs.flowContainer.getBoundingClientRect() 509 510 // 判断鼠标是否进入 flow container 511 if ( 512 clientX > left && clientX < right && clientY > top && clientY < bottom 513 ) { 514 // 获取拖动元素左上角相对 super flow 区域原点坐标 515 const coordinate = this.$refs.superFlow.getMouseCoordinate( 516 clientX - conf.offsetLeft, 517 clientY - conf.offsetTop 518 ) 519 // 添加节点 520 this.$refs.superFlow.addNode({ 521 coordinate, 522 ...conf.info 523 }) 524 } 525 conf.isMove = false 526 } 527 if (conf.ele) { 528 conf.ele.remove() 529 conf.ele = null 530 } 531 }, 532 nodeItemMouseDown (evt, infoFun) { 533 const { 534 clientX, 535 clientY, 536 currentTarget 537 } = evt 538 539 const { 540 top, 541 left 542 } = evt.currentTarget.getBoundingClientRect() 543 544 const conf = this.dragConf 545 const ele = currentTarget.cloneNode(true) 546 547 Object.assign(this.dragConf, { 548 offsetLeft: clientX - left, 549 offsetTop: clientY - top, 550 clientX: clientX, 551 clientY: clientY, 552 info: infoFun(), 553 ele, 554 isDown: true 555 }) 556 557 ele.style.position = 'fixed' 558 ele.style.margin = '0' 559 ele.style.top = clientY - conf.offsetTop + 'px' 560 ele.style.left = clientX - conf.offsetLeft + 'px' 561 562 this.$el.appendChild(this.dragConf.ele) 563 } 564 } 565 } 566 </script> 567 568 <style lang="scss" scoped> 569 .workflow-container { 570 width: calc(100vw - 80px); 571 height: calc(100vh - 128px); 572 box-shadow: 0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%); 573 margin: 32px; 574 padding: 0; 575 background: #fff; 576 overflow: hidden; 577 } 578 .ellipsis { 579 white-space : nowrap; 580 text-overflow : ellipsis; 581 overflow : hidden; 582 word-wrap : break-word; 583 } 584 .super-flow-demo { 585 position: relative; 586 margin: 20px; 587 background: #f5f5f5; 588 height: calc(100vh - 168px); 589 590 .node-container { 591 width: 100%; 592 height: 50px; 593 background-color: #FFFFFF; 594 595 .node-item { 596 display: inline-block; 597 font-size: 14px; 598 height: 30px; 599 width: 120px; 600 margin: 0 20px 0 0; 601 background: #FFFFFF; 602 line-height: 30px; 603 box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.3); 604 cursor: pointer; 605 user-select: none; // 防止鼠标左键拖动选中页面的文字 606 text-align: center; 607 &:hover { 608 box-shadow : 1px 1px 8px rgba(0, 0, 0, 0.4); 609 } 610 } 611 } 612 .flow-container { 613 width: 100%; 614 height: calc(100% - 50px); 615 616 .super-flow { 617 overflow: auto; 618 } 619 } 620 .saveIcon { 621 position: absolute; 622 right: 0px; 623 top: 0px; 624 } 625 .super-flow__node { 626 .flow-node { 627 box-sizing: border-box; 628 width: 100%; 629 height: 100%; 630 line-height: 40px; 631 padding: 0 6px; 632 font-size: 16px; 633 color: #fff; 634 font-weight: bold; 635 .node-content { 636 text-align: center; 637 overflow: hidden; 638 text-overflow: ellipsis; 639 white-space: nowrap; 640 } 641 } 642 } 643 /*开始节点样式*/ 644 .ellipsis.flow-node-start { 645 background: #55ABFC; 646 border-radius: 10px; 647 border: 1px solid #b4b4b4; 648 } 649 /*流程节点样式*/ 650 .ellipsis.flow-node-process { 651 position: relative; 652 background: #30B95C; 653 border: 1px solid #b4b4b4; 654 } 655 /*条件节点样式*/ 656 .ellipsis.flow-node-if { 657 width: 120px; 658 height: 120px; 659 position: relative; 660 top: 24px; 661 left: 24px; 662 background: #BC1D16; 663 border: 1px solid #b4b4b4; 664 transform: rotateZ(45deg); //倾斜 665 .node-content { 666 position: absolute; 667 top: 50%; 668 left: 20px; 669 width: 100%; 670 transform: rotateZ(-45deg) translateY(-75%); 671 } 672 } 673 /*结束节点样式*/ 674 .ellipsis.flow-node-end { 675 background: #000; 676 border-radius: 10px; 677 border: 1px solid #b4b4b4; 678 } 679 } 680 </style> 681 <style> 682 .super-flow-demo .super-flow__node { 683 border: none; 684 background: none; 685 box-shadow: none; 686 } 687 </style>View Code
标签:node,vue,type,flow,label,meta,conf,height,super From: https://www.cnblogs.com/lljboke/p/17080327.html