vue2 + antvx6 实现流程图功能

导入关键包

npm install @antv/x6 --save

npm install @antv/x6-vue-shape

保存插件 (可选)

npm install --save @antv/x6-plugin-clipboard @antv/x6-plugin-history @antv/x6-plugin-keyboard @antv/x6-plugin-selection @antv/x6-plugin-snapline @antv/x6-plugin-stencil @antv/x6-plugin-transform insert-css

写好的组件直接导入即可

javascript 复制代码
<template>
  <div>

    <el-container>
      <el-aside>
        <div id="stencil">
          <div>
            <div class="dnd-circle dnd-start" @mousedown="startDrag('start',$event)"></div>
            <span>开始</span>
          </div>
          <div>
            <div class="dnd-rect" @mousedown="startDrag('rect',$event)"></div>
            <span>节点1</span>
          </div>
          <div>
            <div class="dnd-polygon" @mousedown="startDrag('polygon',$event)"></div>
            <span>节点2</span>
          </div>
          <div>
            <div class="dnd-circle" @mousedown="startDrag('end',$event)"></div>
            <span>结束</span>
          </div>
        </div>
      </el-aside>
      <el-main>

        <div ref="graphContainer">

        </div>
      </el-main>
    </el-container>

    <!--  todo drawer 抽屉实现节点内容编辑  -->
    <el-drawer
      title="节点属性编辑"
      :visible.sync="drawer"
      :direction="direction"
      :before-close="handleClose">
      <el-form :data="editNode" :inline="true">
        <el-form-item label="节点名称" prop="label">
          <el-input v-model="editNode.label"></el-input>
        </el-form-item>
        <el-form-item label="节点形状" prop="shape">
          <el-select v-model="editNode.shape">
            <el-option v-for="(item,index) in shapeList" :key="item.value" :label="item.label"
                       :value="item.value"></el-option>
          </el-select>
        </el-form-item>
        <el-button type="primary" @click="saveNode">保存</el-button>
      </el-form>
    </el-drawer>

  </div>
</template>

<script>
import {Graph} from '@antv/x6'
import '@antv/x6-vue-shape'
// 插件 键盘监听事件
import {Keyboard} from '@antv/x6-plugin-keyboard'
// 拖拽事件
import {Dnd} from '@antv/x6-plugin-dnd'

import {Stencil} from '@antv/x6-plugin-stencil'
import {Transform} from '@antv/x6-plugin-transform'
import {Selection} from '@antv/x6-plugin-selection'
import {Snapline} from '@antv/x6-plugin-snapline'
import {Clipboard} from '@antv/x6-plugin-clipboard'
import {History} from '@antv/x6-plugin-history'
import {register} from '@antv/x6-vue-shape'
import insertCss from 'insert-css'

export default {
  name: 'MindMap',
  data () {
    return {
      graphOut: {},
      drawer: false,
      direction: 'rtl',
      currentNode: {},
      editNode: {},
      dnd: {},
      // 节点形状
      shapeList: [{
        label: '矩形',
        value: 'rect'
      }, {
        label: '圆形',
        value: 'circle'
      }, {
        label: '椭圆',
        value: 'ellipse'
      }, {
        label: '多边形',
        value: 'polygon'
      }, {
        label: '折线',
        value: 'polyline'
      }, {
        label: '路径',
        value: 'path'
      }, {
        label: '图片',
        value: 'image'
      },],
      // 连接桩
      ports: {
        groups: {
          top: {
            position: 'top',
            attrs: {
              circle: {
                magnet: true,
                stroke: 'black',
                r: 4,
              },
            },
          },
          bottom: {
            position: 'bottom',
            attrs: {
              circle: {
                magnet: true,
                stroke: 'black',
                r: 4,
              },
            },
          },
          left: {
            position: 'left',
            attrs: {
              circle: {
                magnet: true,
                stroke: 'black',
                r: 4,
              },
            },
          },
          right: {
            position: 'right',
            attrs: {
              circle: {
                magnet: true,
                stroke: 'black',
                r: 4,
              },
            },
          },
        },
        items: [
          {
            id: 'port_1',
            group: 'bottom',
          }, {
            id: 'port_2',
            group: 'top',
          }, {
            id: 'port_3',
            group: 'left',
          }, {
            id: 'port_4',
            group: 'right',
          }
        ]
      }
    }
  },

  mounted () {
    this.graphOut = this.initGraph()
  },
  methods: {
    initGraph () {
      const graph = new Graph({
        container: this.$refs.graphContainer,
        // autoResize: true, // 大小自适应
        height: 400,
        width: '100%',
        grid: true,
        magnetThreshold: 'onleave',
        panning: {
          enabled: true,
          modifiers: 'shift',
          magnetThreshold: 1,
          // 鼠标画布移动
          eventTypes: ['leftMouseDown']
        },
        // 开启自动吸附
        connecting: {
          // 距离节点或者连接桩 50 px 触发自动吸附
          snap: true,
          // 是否允许连接到画布空白位置的点
          allowBlank: false,
          // 是否允许创建循环连线
          allowLoop: false,
          // 拖动边时,是否高亮显示所有可用连接桩或节点
          highlight: true,
        },

        modes: {
          default: ['drag-node']
        },
        background: {
          color: '#F2F7FA',
        },
        mousewheel: {
          // 是否开启滚轮缩放交互
          enabled: true,
          // 滚动缩放因子 默认 1.2
          factor: 1.2,
          // 是否将鼠标位置作为中心缩放、默认为true
          zoomAtMousePosition: true,
          // 按下什么键 才会缩放
          modifiers: ['ctrl', 'meta'],
          // 判断什么情况下 滚轮事件被处理
          // guard: false,
        },
        connector: {
          name: 'rounded',
          args: {
            radius: 8
          }
        }

      })

      // 支持拖拽
      this.dnd = new Dnd({
        target: graph,
        scaled: false,
      })

      Graph.registerNode(
        'custom-node-width-port',
        {
          inherit: 'rect',
          width: 100,
          height: 40,
          attrs: {
            body: {
              stroke: '#8f8f8f',
              strokeWidth: 1,
              fill: '#fff',
              rx: 6,
              ry: 6,
            },
          },
          // 上下左右 四条边都有连接桩
          ports: this.ports
        },
        true,
      )

      Graph.registerNode(
        'custom-circle-start',
        {
          inherit: 'circle',
          ports: this.ports
        },

        true,
      )

      Graph.registerNode(
        'custom-polygon',
        {
          inherit: 'polygon',
          points: '0,10 10,0 20,10 10,20',
          ports: this.ports
        },
        true,
      )

      Graph.registerNode(
        'custom-rect',
        {
          inherit: 'rect',
          ports: this.ports
        },
        true,
      )

      graph.addNode({
        x: 100,
        y: 40,
        width: 180,
        height: 30,
        label: '中心主题',
        shape: 'custom-node-width-port', // 节点形状
        attrs: {
          body: {
            fill: '#f5f5f5',
            stroke: '#333',
          },
          type: 'root'
        },
        tools: [
          {
            name: 'boundary',
            args: {
              attrs: {
                fill: '#16B8AA',
                stroke: '#2F80EB',
                strokeWidth: 1,
                fillOpacity: 0.1,
              },
            },
          }
        ]
      })

      // 添加 plugin 插件
      graph.use(new Keyboard()) // 键盘事件
        .use(new Selection({
          enabled: true,
          multiple: true,
          rubberband: true,
          movable: true,
          showEdgeSelectionBox: true,
          showNodeSelectionBox: true,
          pointerEvents: 'none'
        })) // 绑定框选
        .use(new Snapline({
          enabled: true,
          sharp: true,
        })) // 对齐线
        .use(new Clipboard())
        .use(new History({enabled: true})) // 绑定撤销

      // 鼠标事件
      this.mouseEvent(graph)

      // 键盘时间
      this.keyboardEvent(graph)

      // 添加子节点的逻辑...
      return graph
    },
    addChildNode (nodeId, type) {
      console.log(nodeId, type)
    },
    handleClose (done) {
      this.$confirm('确认关闭?')
        .then(_ => {
          done()
        })
        .catch(_ => {
        })
    },
    saveNode () {
      this.$confirm('确认保存?')
        .then(_ => {
          console.log(this.editNode)
          this.currentNode['label'] = this.editNode['label']
          // this.currentNode['shape'] = this.editNode['shape']
        })
        .catch(_ => {
        })
      // 关闭当前 抽屉 el-drawer
      this.drawer = false

    },

    startDrag (type, e) {
      this.startDragToGraph(this.graphOut, type, e)
    },

    startDragToGraph (graph, type, e) {
      const startNode = this.graphOut.createNode({
        shape: 'custom-circle-start',
        width: 38,
        height: 38,
        attrs: {
          body: {
            strokeWidth: 1,
            stroke: '#000000',
            fill: '#ffffff',
            rx: 10,
            ry: 10,
          },
        },
      })
      const polygonNode = this.graphOut.createNode({
        shape: 'custom-polygon',
        width: 80,
        height: 60,
        attrs: {
          body: {
            strokeWidth: 1,
            stroke: '#000000',
            fill: '#ffffff',
            rx: 10,
            ry: 10,
          },
          label: {
            fontSize: 13,
            fontWeight: 'bold',
          },
        },

      })
      const rectNode = this.graphOut.createNode({
        shape: 'custom-rect',
        width: 80,
        height: 60,
        attrs: {
          body: {
            strokeWidth: 1,
            stroke: '#000000',
            fill: '#ffffff',
            rx: 10,
            ry: 10,
          },
          label: {
            fontSize: 13,
            fontWeight: 'bold',
          },
        },
      })
      const endNode = this.graphOut.createNode({
        shape: 'custom-circle-start',
        width: 38,
        height: 38,
        key: 'end',
        attrs: {
          body: {
            strokeWidth: 4,
            stroke: '#000000',
            fill: '#ffffff',
            rx: 10,
            ry: 10,
          },
          label: {
            text: '结束',
            fontSize: 13,
            fontWeight: 'bold',
          },
        },
      })
      let dragNode
      if (type === 'start') {
        dragNode = startNode
      } else if (type === 'end') {
        dragNode = endNode
      } else if (type === 'rect') {
        dragNode = rectNode
      } else if (type === 'polygon') {
        dragNode = polygonNode
      }
      console.log('dnd', dragNode, e, type)
      this.dnd.start(dragNode, e)
    },

    // 删除事件 节点
    removeNode (node) {
      this.graphOut.removeNode(node)
    },

    // 鼠标事件
    mouseEvent (graph) {
      // 鼠标事件

      // 鼠标 Hover 时添加按钮
      graph.on('node:mouseenter', ({node}) => {
        node.addTools({
          name: 'button',
          args: {
            x: 0,
            y: 0,
            offset: {x: 18, y: 18},
            // onClick({ view }) { ... },
          },
        })
      })

      // 鼠标移开时删除按钮
      graph.on('node:mouseleave', ({node}) => {
        node.removeTools() // 删除所有的工具
      })

      graph.on('node:dblclick', ({node}) => {
        // 添加连接桩
        node.addPort({
          group: 'top',
          attrs: {
            circle: {
              magnet: true,
              stroke: '#8f8f8f',
              r: 5,
            },
          },
        })
        // 编辑node
        this.currentNode = node
        this.drawer = true
      })

      graph.on('edge:mouseenter', ({cell}) => {
        cell.addTools([
          {name: 'vertices'},
          {
            name: 'button-remove',
            args: {distance: 20},
          },
        ])
      })

      graph.on('node:click', ({node}) => {
        this.currentNode = node
      })
    },

    // 键盘事件
    keyboardEvent (graph) {

      // 键盘事件
      graph.bindKey('tab', (e) => {
        e.preventDefault()

        const selectedNodes = graph.getCells().filter((item) => item.isNode())
        console.log(selectedNodes)
        if (selectedNodes.length) {
          const node = selectedNodes[0]
          const type = node.attrs['type']
          this.addChildNode(node.id, type)
        }
      })

      graph.bindKey('delete', (e) => {
        this.removeNode(this.currentNode)
      })

      graph.bindKey('backspace', (e) => {
        this.removeNode(this.currentNode)
      })
    },

  },
  watch: {
    // currentNode: {
    //   handler (nwVal, old) {
    //   },
    //   immediate: true,
    //   deep: true
    // }
  }
}
</script>

<style>
/* 样式调整 */
#stencil {
  width: 100px;
  height: 100%;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  border-right: 1px solid #dfe3e8;
  text-align: center;
  font-size: 12px;
}

.dnd-rect {
  width: 50px;
  height: 30px;
  line-height: 40px;
  text-align: center;
  border: 2px solid #000000;
  border-radius: 6px;
  cursor: move;
  font-size: 12px;
  margin-top: 30px;
}

.dnd-polygon {
  width: 35px;
  height: 35px;
  border: 2px solid #000000;
  transform: rotate(45deg);
  cursor: move;
  font-size: 12px;
  margin-top: 30px;
  margin-bottom: 10px;
}

.dnd-circle {
  width: 35px;
  height: 35px;
  line-height: 45px;
  text-align: center;
  border: 5px solid #000000;
  border-radius: 100%;
  cursor: move;
  font-size: 12px;
  margin-top: 30px;
}

.dnd-start {
  border: 2px solid #000000;
}

.x6-widget-stencil {
  background-color: #f8f9fb;
}

.x6-widget-stencil-title {
  background: #eee;
  font-size: 1rem;
}

.x6-widget-stencil-group-title {
  font-size: 1rem !important;
  background-color: #fff !important;
  height: 40px !important;
}

.x6-widget-transform {
  margin: -1px 0 0 -1px;
  padding: 0px;
  border: 1px solid #239edd;
}

.x6-widget-transform > div {
  border: 1px solid #239edd;
}

.x6-widget-transform > div:hover {
  background-color: #3dafe4;
}

.x6-widget-transform-active-handle {
  background-color: #3dafe4;
}

.x6-widget-transform-resize {
  border-radius: 0;
}


</style>
相关推荐
zincsweet3 天前
Linux 命名管道(FIFO)详解:原理分析、源码封装与通信流程图解
linux·服务器·c++·流程图
优思学苑5 天前
价值流程图:看到流程,而不只是步骤【精益管理CLMP】
流程图
bug总结6 天前
前端流程图vueflow
前端·流程图
米饭不加菜7 天前
Mermaid 流程图语法参考四
流程图
米饭不加菜9 天前
Mermaid 流程图语法参考三
流程图
米饭不加菜9 天前
Typora 原生流程图语法完全指南(Flowchart.js)
前端·javascript·流程图
米饭不加菜9 天前
Mermaid 流程图语法参考二
数据库·流程图
米饭不加菜10 天前
Mermaid 流程图语法参考一
流程图
Ysn071910 天前
利用豆包和draw.io快速绘制流程图
流程图·draw.io
Daorigin_com11 天前
从“被动领罚”到“主动合规”:强监管时代下,道本科技用数字化为企业筑牢“合规生命线”
大数据·数据仓库·科技·流程图·软件构建·数据库开发·数据库架构