vue super flow 多种形状

  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

 

posted on 2023-01-31 19:51  longlinji  阅读(175)  评论(0编辑  收藏  举报