Browse Source

feat: 自定义子流程

Gaokun Wang 2 weeks ago
parent
commit
c30ae816f3
7 changed files with 149 additions and 106 deletions
  1. 16 0
      package-lock.json
  2. 1 0
      package.json
  3. 3 1
      src/main.ts
  4. 83 88
      src/views/graph/index.ts
  5. 0 3
      src/views/graph/ports.ts
  6. 46 11
      src/views/graph/shape.ts
  7. 0 3
      src/views/test/index.scss

+ 16 - 0
package-lock.json

@@ -13,6 +13,7 @@
         "@electron-toolkit/preload": "^3.0.1",
         "@electron-toolkit/preload": "^3.0.1",
         "@electron-toolkit/utils": "^4.0.0",
         "@electron-toolkit/utils": "^4.0.0",
         "@element-plus/icons-vue": "^2.3.1",
         "@element-plus/icons-vue": "^2.3.1",
+        "@imengyu/vue3-context-menu": "^1.5.1",
         "@tailwindcss/vite": "^4.1.10",
         "@tailwindcss/vite": "^4.1.10",
         "@vueuse/core": "^13.3.0",
         "@vueuse/core": "^13.3.0",
         "@zklogic/draw.io": "^15.7.2-rc1",
         "@zklogic/draw.io": "^15.7.2-rc1",
@@ -1104,6 +1105,21 @@
         "url": "https://github.com/sponsors/nzakas"
         "url": "https://github.com/sponsors/nzakas"
       }
       }
     },
     },
+    "node_modules/@imengyu/vue-scroll-rect": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmmirror.com/@imengyu/vue-scroll-rect/-/vue-scroll-rect-0.1.7.tgz",
+      "integrity": "sha512-a1ysL8LV2iFcHzMJ6tlrqr8JKtYIBrb1U3XcbjNmhponENUSbRvxs4b1sJd5sXdCFIIkDydwQM/SjVhfT7uBUw==",
+      "license": "MIT"
+    },
+    "node_modules/@imengyu/vue3-context-menu": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmmirror.com/@imengyu/vue3-context-menu/-/vue3-context-menu-1.5.1.tgz",
+      "integrity": "sha512-Y3M/PVOj0Fz7lu3aviIu6NKFYjqMP1tZSffSiYy55JdAfcm/bD06dRT9RL5AccOqTSJdvcAImhiYvmBnPKtYEg==",
+      "license": "MIT",
+      "dependencies": {
+        "@imengyu/vue-scroll-rect": "^0.1.4"
+      }
+    },
     "node_modules/@isaacs/cliui": {
     "node_modules/@isaacs/cliui": {
       "version": "8.0.2",
       "version": "8.0.2",
       "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz",
       "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz",

+ 1 - 0
package.json

@@ -25,6 +25,7 @@
     "@electron-toolkit/preload": "^3.0.1",
     "@electron-toolkit/preload": "^3.0.1",
     "@electron-toolkit/utils": "^4.0.0",
     "@electron-toolkit/utils": "^4.0.0",
     "@element-plus/icons-vue": "^2.3.1",
     "@element-plus/icons-vue": "^2.3.1",
+    "@imengyu/vue3-context-menu": "^1.5.1",
     "@tailwindcss/vite": "^4.1.10",
     "@tailwindcss/vite": "^4.1.10",
     "@vueuse/core": "^13.3.0",
     "@vueuse/core": "^13.3.0",
     "@zklogic/draw.io": "^15.7.2-rc1",
     "@zklogic/draw.io": "^15.7.2-rc1",

+ 3 - 1
src/main.ts

@@ -4,12 +4,14 @@ import App from './App.vue'
 import { setupRouter } from '@/router'
 import { setupRouter } from '@/router'
 // import { setupAuthRoutes } from './router/before'
 // import { setupAuthRoutes } from './router/before'
 import { createPinia } from 'pinia'
 import { createPinia } from 'pinia'
-
+import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css'
+import ContextMenu from '@imengyu/vue3-context-menu'
 const pinia = createPinia()
 const pinia = createPinia()
 // 创建实例
 // 创建实例
 const setupAll = async () => {
 const setupAll = async () => {
   const app = createApp(App)
   const app = createApp(App)
   setupRouter(app)
   setupRouter(app)
+  app.use(ContextMenu)
   // setupAuthRoutes()
   // setupAuthRoutes()
   app.use(pinia)
   app.use(pinia)
   app.mount('#app')
   app.mount('#app')

+ 83 - 88
src/views/graph/index.ts

@@ -1,12 +1,13 @@
 import { Graph, FunctionExt, Shape, Addon } from '@antv/x6'
 import { Graph, FunctionExt, Shape, Addon } from '@antv/x6'
+import ContextMenu from '@imengyu/vue3-context-menu'
 
 
 import insertCss from 'insert-css'
 import insertCss from 'insert-css'
 import './shape'
 import './shape'
 import { customPorts } from './ports'
 import { customPorts } from './ports'
 import graphData from './data/data'
 import graphData from './data/data'
 import { getImageUrl } from '@/utils'
 import { getImageUrl } from '@/utils'
-
-insertCss(`
+import { text } from 'stream/consumers'
+;(insertCss as any)(`
   @keyframes ant-line {
   @keyframes ant-line {
     to {
     to {
         stroke-dashoffset: -1000
         stroke-dashoffset: -1000
@@ -187,6 +188,7 @@ export default class FlowGraph {
       target: this.graph,
       target: this.graph,
       title: '节点搜索',
       title: '节点搜索',
       stencilGraphWidth: 250,
       stencilGraphWidth: 250,
+      stencilGraphHeight: 0,
       placeholder: '请输入节点关键字',
       placeholder: '请输入节点关键字',
       notFoundText: '未搜索到结果',
       notFoundText: '未搜索到结果',
       search: { rect: true },
       search: { rect: true },
@@ -194,23 +196,22 @@ export default class FlowGraph {
       groups: [
       groups: [
         {
         {
           name: 'basic',
           name: 'basic',
-          title: '基础节点',
-          graphHeight: 180
+          title: '基础节点'
+          // graphHeight: 180
         },
         },
         {
         {
           name: 'logic',
           name: 'logic',
-          title: '逻辑节点',
-          graphHeight: 180
+          title: '逻辑节点'
+          // graphHeight: 180
         },
         },
         {
         {
           name: 's-pro',
           name: 's-pro',
           title: '子流程节点',
           title: '子流程节点',
-          graphHeight: 180
-        },
-        {
-          name: 'custom-image',
-          title: '系统设计图',
-          graphHeight: 600
+          layoutOptions: {
+            columns: 1,
+            marginX: 60
+          }
+          // graphHeight: 260
         },
         },
         {
         {
           name: 'combination',
           name: 'combination',
@@ -218,13 +219,13 @@ export default class FlowGraph {
           layoutOptions: {
           layoutOptions: {
             columns: 1,
             columns: 1,
             marginX: 60
             marginX: 60
-          },
-          graphHeight: 260
+          }
+          // graphHeight: 260
         },
         },
         {
         {
           name: 'group',
           name: 'group',
           title: '节点组',
           title: '节点组',
-          graphHeight: 100,
+          // graphHeight: 100,
           layoutOptions: {
           layoutOptions: {
             columns: 1,
             columns: 1,
             marginX: 60
             marginX: 60
@@ -247,7 +248,7 @@ export default class FlowGraph {
           ry: 24
           ry: 24
         },
         },
         text: {
         text: {
-          text: '起始节点'
+          text: '开始'
         }
         }
       }
       }
     })
     })
@@ -255,7 +256,7 @@ export default class FlowGraph {
       shape: 'flow-chart-rect',
       shape: 'flow-chart-rect',
       attrs: {
       attrs: {
         text: {
         text: {
-          text: '流程节点'
+          text: '流程'
         }
         }
       }
       }
     })
     })
@@ -266,7 +267,7 @@ export default class FlowGraph {
       angle: 45,
       angle: 45,
       attrs: {
       attrs: {
         text: {
         text: {
-          text: '判断节点',
+          text: '判断',
           transform: 'rotate(-45deg)'
           transform: 'rotate(-45deg)'
         }
         }
       },
       },
@@ -309,18 +310,43 @@ export default class FlowGraph {
     })
     })
     const r4 = graph.createNode({
     const r4 = graph.createNode({
       shape: 'flow-chart-rect',
       shape: 'flow-chart-rect',
-      width: 70,
-      height: 70,
+      width: 38,
+      height: 12,
       attrs: {
       attrs: {
-        body: {
-          rx: 35,
-          ry: 35
+        text: {
+          text: '点'
+        }
+      }
+    })
+
+    // 子流程节点
+    const s1 = graph.createNode({
+      shape: 'sub-flow-rect',
+      attrs: {
+        title: {
+          text: 'XX子流程'
         },
         },
         text: {
         text: {
-          text: '链接节点'
+          text: '测试xx流程'
         }
         }
       }
       }
     })
     })
+    const s2 = graph.createNode({
+      shape: 'sub-flow-rect'
+    })
+    const s3 = graph.createNode({
+      shape: 'sub-flow-rect'
+    })
+    const s4 = graph.createNode({
+      shape: 'sub-flow-rect'
+    })
+    const s5 = graph.createNode({
+      shape: 'sub-flow-rect'
+    })
+    const s6 = graph.createNode({
+      shape: 'sub-flow-rect'
+    })
+
     // 组合节点
     // 组合节点
     const c1 = graph.createNode({
     const c1 = graph.createNode({
       shape: 'flow-chart-image-rect'
       shape: 'flow-chart-image-rect'
@@ -343,77 +369,16 @@ export default class FlowGraph {
         parent: true
         parent: true
       }
       }
     })
     })
-    // 系统设计图
-    const imgs = [
-      {
-        image: getImageUrl('ldb.png')
-      },
-      {
-        image: getImageUrl('wft1.png')
-      },
-      {
-        image: getImageUrl('wft2.png')
-      },
-      {
-        image: getImageUrl('wft3.png')
-      },
-      {
-        image: getImageUrl('wft4.png')
-      },
-      {
-        image: getImageUrl('wft5.png')
-      },
-      {
-        image: getImageUrl('lqt.png')
-      },
-      {
-        image: getImageUrl('lqb-o.png')
-      },
-      {
-        image: getImageUrl('ldb-o.png')
-      },
-      {
-        image: getImageUrl('bh.png')
-      },
-      {
-        image: getImageUrl('f1.png')
-      },
-      {
-        image: getImageUrl('f2.png')
-      },
-      {
-        image: getImageUrl('f3.png')
-      }
-    ]
-    const imgNodes = imgs.map(item => {
-      return graph.createNode({
-        // shape: 'flow-chart-image-rect-custom',
-        // attrs: {
-        //   image: {
-        //     'xlink:href': item.image,
-        //   }
-        // }
-        shape: 'image', //可选值:Rect Circle Ellipse Polygon Polyline Path Image HTML TextBlock BorderedImage EmbeddedImage InscribedImage Cylinder
-        imageUrl: item.image,
-        attrs: {
-          image: {
-            // fill: 'yellow',
-          }
-        },
-        width: 52,
-        height: 52,
-        ports: { ...customPorts }
-      })
-    })
+
     this.stencil.load([r1, r2, r3, r4], 'basic')
     this.stencil.load([r1, r2, r3, r4], 'basic')
-    this.stencil.load(imgNodes, 'custom-image')
+    this.stencil.load([s1, s2, s3, s4, s5], 's-pro')
     this.stencil.load([c1, c2, c3], 'combination')
     this.stencil.load([c1, c2, c3], 'combination')
     this.stencil.load([g1], 'group')
     this.stencil.load([g1], 'group')
   }
   }
 
 
   // 根据json渲染节点和边
   // 根据json渲染节点和边
   public static initGraphShape(gd: any = graphData) {
   public static initGraphShape(gd: any = graphData) {
-    this.graph.fromJSON(gd)
+    // this.graph.fromJSON(gd)
   }
   }
 
 
   // 连接桩显示时机
   // 连接桩显示时机
@@ -423,6 +388,32 @@ export default class FlowGraph {
     }
     }
   }
   }
 
 
+  // 右键菜单
+  public static handleContextmenu = (e: { pageX: any; pageY: any }) => {
+    const cells = this.graph.getSelectedCells()
+    ContextMenu.showContextMenu({
+      x: e.pageX,
+      y: e.pageY,
+      items: [
+        {
+          label: '编辑子流程',
+          onClick: () => {
+            if (cells.length) {
+            }
+          }
+        },
+        {
+          label: '删除节点',
+          onClick: () => {
+            if (cells.length) {
+              this.graph.removeCells(cells)
+            }
+          }
+        }
+      ]
+    })
+  }
+
   // 事件相关
   // 事件相关
   private static initEvent() {
   private static initEvent() {
     const { graph } = this
     const { graph } = this
@@ -499,6 +490,10 @@ export default class FlowGraph {
         graph.removeCells(cells)
         graph.removeCells(cells)
       }
       }
     })
     })
+
+    graph.on('node:contextmenu', ({ e, x, y, cell, view }) => {
+      this.handleContextmenu(e)
+    })
     // 鼠标动态添加/删除小工具。
     // 鼠标动态添加/删除小工具。
     graph.on('edge:mouseenter', ({ cell }) => {
     graph.on('edge:mouseenter', ({ cell }) => {
       /**
       /**

+ 0 - 3
src/views/graph/ports.ts

@@ -29,9 +29,6 @@ export const basicPorts = {
     }
     }
   },
   },
   items: [
   items: [
-    {
-      group: 'top'
-    },
     {
     {
       group: 'top'
       group: 'top'
     },
     },

+ 46 - 11
src/views/graph/shape.ts

@@ -1,5 +1,6 @@
 import { Graph, Dom, Node } from '@antv/x6'
 import { Graph, Dom, Node } from '@antv/x6'
 import { basicPorts, customPorts } from './ports'
 import { basicPorts, customPorts } from './ports'
+import ContextMenu from '@imengyu/vue3-context-menu'
 
 
 // 基础节点
 // 基础节点
 export const FlowChartRect = Graph.registerNode('flow-chart-rect', {
 export const FlowChartRect = Graph.registerNode('flow-chart-rect', {
@@ -46,8 +47,9 @@ export const FlowChartRect = Graph.registerNode('flow-chart-rect', {
   ],
   ],
   ports: { ...basicPorts }
   ports: { ...basicPorts }
 })
 })
-// 组合节点
-export const FlowChartImageRect = Graph.registerNode('flow-chart-image-rect', {
+
+// 子流程节点
+export const SubRect = Graph.registerNode('sub-flow-rect', {
   inherit: 'rect',
   inherit: 'rect',
   width: 200,
   width: 200,
   height: 60,
   height: 60,
@@ -57,13 +59,6 @@ export const FlowChartImageRect = Graph.registerNode('flow-chart-image-rect', {
       strokeWidth: 1,
       strokeWidth: 1,
       fill: 'rgba(95,149,255,0.05)'
       fill: 'rgba(95,149,255,0.05)'
     },
     },
-    image: {
-      'xlink:href': 'https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png',
-      width: 16,
-      height: 16,
-      x: 12,
-      y: 12
-    },
     title: {
     title: {
       text: 'Node',
       text: 'Node',
       refX: 40,
       refX: 40,
@@ -87,8 +82,48 @@ export const FlowChartImageRect = Graph.registerNode('flow-chart-image-rect', {
       selector: 'body'
       selector: 'body'
     },
     },
     {
     {
-      tagName: 'image',
-      selector: 'image'
+      tagName: 'text',
+      selector: 'title'
+    },
+    {
+      tagName: 'text',
+      selector: 'text'
+    }
+  ],
+  ports: { ...basicPorts }
+})
+// 组合节点
+export const FlowChartImageRect = Graph.registerNode('flow-chart-image-rect', {
+  inherit: 'rect',
+  width: 200,
+  height: 60,
+  attrs: {
+    body: {
+      stroke: '#5F95FF',
+      strokeWidth: 1,
+      fill: 'rgba(95,149,255,0.05)'
+    },
+    title: {
+      text: 'Node',
+      refX: 40,
+      refY: 14,
+      fill: 'rgba(0,0,0,0.85)',
+      fontSize: 12,
+      'text-anchor': 'start'
+    },
+    text: {
+      text: 'this is content text',
+      refX: 40,
+      refY: 38,
+      fontSize: 12,
+      fill: 'rgba(0,0,0,0.6)',
+      'text-anchor': 'start'
+    }
+  },
+  markup: [
+    {
+      tagName: 'rect',
+      selector: 'body'
     },
     },
     {
     {
       tagName: 'text',
       tagName: 'text',

+ 0 - 3
src/views/test/index.scss

@@ -43,6 +43,3 @@
   border-left: 1px solid rgba(0, 0, 0, 0.08);
   border-left: 1px solid rgba(0, 0, 0, 0.08);
   box-sizing: border-box;
   box-sizing: border-box;
 }
 }
-
-
-