1 Star 19 Fork 6

BreenCL / x6_learning_demo

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

x6_learning

Project setup

npm install

Compiles and hot-reloads for development

npm run serve

未经作者允许禁止转载! 该仓库内容仅供学习使用!

Vue 项目中使用 AntV X6 绘制流程图

一、需求

  • Vue2.xVue3.x项目同理)项目中使用AntV X6组件库绘制流程图,需要实现以下需求:
  • 需求 1:左侧菜单中的模块可以拖拽进入画布中生成对应的流程图模块
  • 需求 2:流程图中的节点之间可以进行连线交互
  • 需求 3:点击对应的节点后可以进行操作节点(删除、查看节点的相关信息参数)
  • 需求 4:鼠标悬浮在连线上时可以删除当前连线
  • 隐含需求:节点样式需要满足UI设计,所以需要自定义节点样式
  • 关于AntV X6是什么组件库,可以看X6 简介

二、解决

  • 首先分析需求,通过AntV X6组件给出的文档和API是可以满足以上需求的,以下以Vue2.x项目中使用AntV X6并满足相应需求为例,讲述AntV X6使用,帮助初学者快速上手,后文中使用x6代替AntV X6

1.安装X6组件库

  • 搭建Vue的项目后就可以安装x6了,执行命令npm install @antv/x6 --save,详见文档X6 快速上手

2.使用x6组件库

  • 安装好x6之后就可以直接使用了,找到需要使用x6的界面中引入Graph

    import { Graph } from "@antv/x6";
  • 在需要的页面中引入后即可开始初始化画布,初始化画布函数代码如下:

    HomeView.vue ...
    <div id="container"></div>
    ...
    
    <script>
      import { Graph } from '@antv/x6'
      export default {
          data() {
              return {
                  ...
                  graph: null // 画布实例对象
                  ...
              }
          }
          mounted() {
              this.initGraph()
          },
          methods: {
              // 初始化流程图画布
              initGraph() {
                  let container = document.getElementById('container')
                  this.graph = new Graph({
                      container: container, // 画布容器
                      width: container.offsetWidth, // 画布宽
                      height: container.offsetHeight, // 画布高
                      background: false, // 背景(透明)
                      snapline: true, // 对齐线
                      // 配置连线规则
                      connecting: {
                          snap: true, // 自动吸附
                          allowBlank: false, //是否允许连接到画布空白位置的点
                          allowMulti: false, //是否允许在相同的起始节点和终止之间创建多条边
                          allowLoop: false, //是否允许创建循环连线,即边的起始节点和终止节点为同一节点
                          highlight: true, //拖动边时,是否高亮显示所有可用的节点
                          validateEdge({ edge, type, previous }) {
                              // 连线时设置折线
                              edge.setRouter({
                                  name: 'er',
                              })
                              // 设置连线样式
                              edge.setAttrs({
                                  line: {
                                      stroke: '#275da3',
                                      strokeWidth: 4,
                                  },
                              })
                              return true
                          },
                      },
                      panning: {
                          enabled: true,
                      },
                      mousewheel: {
                          enabled: true, // 支持滚动放大缩小
                      },
                      grid: {
                          type: 'mesh',
                          size: 20,      // 网格大小 10px
                          visible: true, // 渲染网格背景
                          args: {
                              color: '#eeeeee', // 网格线/点颜色
                              thickness: 2,     // 网格线宽度/网格点大小
                          },
                      },
                  })
              },
          }
      }
    </script>
  • 其中初始化画布时,画布中的部分属性在注释中给出,如果想要深入了解,建议在官方文档中根据对应案例进行学习了解

(1)满足需求 1

  • 满足左侧菜单栏的拖拽效果可以利用x6stencil初始化一个左侧菜单栏,这样菜单栏内部的模块就可以进行拖动了;但是为了较高的自定义样式这里舍弃使用这种方式,而是利用H5draggable属性,帮助我们间接完成拖拽模块的功能,这里只举例出几个模块作为演示和学习,菜单栏代码如下:

    HomeView.vue ...
    <div class="menu-list">
      <div
        v-for="item in moduleList"
        :key="item.id"
        draggable="true"
        @dragend="handleDragEnd($event, item)"
      >
        <p>{{item.name}}</p>
      </div>
    </div>
    ...
    <div id="container" @dragover="dragoverDiv"></div>
    ...
    
    <script>
      data() {
          return {
              moduleList: [
                  {
                      id: 1,
                      name: '开始模块',
                      type: 'initial' // 初始模块(用于区分样式)
                  },
                  {
                      id: 2,
                      name: '结束模块',
                      type: 'initial'
                  },
                  {
                      id: 3,
                      name: '逻辑模块1',
                      type: 'logic' // 逻辑模块(用于区分样式)
                  },
                  {
                      id: 4,
                      name: '逻辑模块2',
                      type: 'logic'
                  }
              ] // 列表可拖动模块
          }
      },
      methods {
          // 拖动后松开鼠标触发事件
          handleDragEnd(e, item) {
              console.log(e, item) // 可以获取到最后拖动后松开鼠标时的坐标和拖动的节点相关信息
          },
          // 拖动节点到画布中鼠标样式变为可拖动状态
          dragoverDiv(ev) {
              ev.preventDefault()
          }
          ...
      }
    </script>

(2)满足需求 2

  • 到目前为止已经完成了模块的拖动部分,接下来需要拖动到画布中生成相应的模块,这里需要满足隐含的需求,自定义每个模块生成节点的样式,利用x6高级指引-使用 HTML/React/Vue/Angular 渲染 出需要的节点样式,笔者在这里手写了一个工具类的函数,帮助我们生成相应的节点,需求 2 中的节点之间可以连线也在生成节点中加上可以连线的属性,这里没有使用连线桩进行连线,而是节点之间直接进行连线,如果需要使用连线桩,建议阅读官方文档深入学习群组 Group连接桩 Port的使用方法,如果你也直接使用节点之间连线的方式可以参考以下代码:

    graphTools.js;
    /* 
    antv x6图谱相关工具函数
    */
    export default {
      /* 
      初始化初始节点(开始,结束节点)
      x:x轴坐标
      y:y轴坐标
      id:开始节点id
      name:节点内容,默认为空
      type:节点类型,默认为空
      */
      initInitialNode(x, y, id, name, type) {
        let node = {
          shape: "html",
          type: type,
          id: id, // String,可选,节点的唯一标识
          x: x, // Number,必选,节点位置的 x 值
          y: y, // Number,必选,节点位置的 y 值
          width: 140, // Number,可选,节点大小的 width 值
          height: 50, // Number,可选,节点大小的 height 值
          html: `
                <div class="custom_node_initial">
                  <div>
                    <i>🌐</i>
                    <p title=${name}>${name || ""}</p>
                  </div>
                </div>
                `,
          attrs: {
            body: {
              stroke: "transparent",
              strokeWidth: 10,
              magnet: true,
            },
          },
        };
        return node;
      },
    
      /* 
      初始化逻辑节点
      x:x轴坐标
      y:y轴坐标
      id:开始节点id
      name:节点内容,默认为空
      type:节点类型,默认为空
      */
      initLogicNode(x, y, id, name, type) {
        let node = {
          shape: "html",
          type: type, // 动作所属类型
          id: id, // String,可选,节点的唯一标识
          x: x, // Number,必选,节点位置的 x 值
          y: y, // Number,必选,节点位置的 y 值
          width: 140, // Number,可选,节点大小的 width 值
          height: 50, // Number,可选,节点大小的 height 值
          html: `
                  <div class="custom_node_logic">
                    <div>
                      <i>💠</i>
                      <p title=${name}>${name || ""}</p>
                    </div>
                  </div>
                `,
          attrs: {
            body: {
              stroke: "transparent",
              strokeWidth: 10,
              magnet: true,
            },
          },
        };
        return node;
      },
    };

(3)满足隐含需求

  • 在页面组件中引入工具函数,并添加一个节点生成函数,将模块的参数传入节点生成函数中,生成相应的节点,代码如下:

    HomeView.vue ...
    <script>
      ...
      import Tools from '@/assets/js/graphTools.js'
      ...
      export default {
          methods: {
              //
              addHandleNode(x, y, id, name, type) {
                  type === 'initial'
                      ?
                      this.graph.addNode(Tools.initInitialNode(x, y, id, name, type))
                      :
                      this.graph.addNode(Tools.initLogicNode(x, y, id, name, type))
              },
              // 拖动后松开鼠标触发事件
              handleDragEnd(e, item) {
                  this.addHandleNode(e.pageX - 240, e.pageY - 40, new Date().getTime(), item.name, item.type)
              },
          }
      }
    </script>
    <style lang="less">
      // 其中节点样式加到没有scoped包裹的style标签中否则样式不生效
      // 初始节点样式
      .custom_node_initial {
        width: 100%;
        height: 100%;
        display: flex;
        border-radius: 3px;
        background: rgba(22, 184, 169, 0.6);
        flex-direction: column;
        overflow: hidden;
        > div {
          width: 100%;
          height: 100%;
          display: flex;
          align-items: center;
          justify-content: center;
          padding: 5px;
          box-sizing: border-box;
          border: 5px solid rgba(47, 128, 235, 0.6);
          i {
            line-height: 22px;
            font-size: 18px;
            color: #ffffff;
            display: flex;
            align-items: center;
            margin-right: 5px;
            justify-content: center;
            font-style: normal;
          }
          p {
            color: #ffffff;
            font-size: 16px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
          }
        }
      }
      // 逻辑节点样式
      .custom_node_logic {
        width: 100%;
        height: 100%;
        display: flex;
        background: rgba(47, 128, 235, 0.5);
        flex-direction: column;
        overflow: hidden;
        border-radius: 5px;
        > div {
          width: 100%;
          height: 100%;
          display: flex;
          align-items: center;
          justify-content: center;
          padding: 5px;
          box-sizing: border-box;
          border: 5px solid rgba(22, 184, 169, 0.5);
          border-radius: 5px;
          line-height: 22px;
          i {
            line-height: 22px;
            font-size: 18px;
            color: #b5cde9;
            margin-right: 5px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-style: normal;
          }
          p {
            color: #ffffff;
            font-size: 14px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
          }
        }
      }
    </style>

(4)满足需求 3 和 4

  • 这里需要利用x6提供的方法,给节点绑定相应的事件,代码如下:

    <script>
      export default {
          data() {
              return{
                  ...
                  curSelectNode: null, // 当前选中的节点和节点相关信息
              }
          },
          methods: {
              initGraph() {
                  ...
                  this.nodeAddEvent()
              }
              // 节点绑定事件
              nodeAddEvent() {
                // 节点绑定点击事件
                this.graph.on('node:click', ({ e, x, y, node, view }) => {
                  // 判断是否有选中过节点
                  if (this.curSelectNode) {
                    // 移除选中状态
                    this.curSelectNode.removeTools()
                    // 判断两次选中节点是否相同
                    if (this.curSelectNode !== node) {
                      node.addTools([{
                        name: 'boundary',
                        args: {
                          attrs: {
                            fill: '#16B8AA',
                            stroke: '#2F80EB',
                            strokeWidth: 1,
                            fillOpacity: 0.1
                          }
                        }
                      }, {
                        name: 'button-remove',
                        args: {
                          x: '100%',
                          y: 0,
                          offset: {
                            x: 0,
                            y: 0
                          }
                        }
                      }])
                      this.curSelectNode = node
                    } else {
                      this.curSelectNode = null
                    }
                  } else {
                    this.curSelectNode = node
                    node.addTools([{
                      name: 'boundary',
                      args: {
                        attrs: {
                          fill: '#16B8AA',
                          stroke: '#2F80EB',
                          strokeWidth: 1,
                          fillOpacity: 0.1
                        }
                      }
                    }, {
                      name: 'button-remove',
                      args: {
                        x: '100%',
                        y: 0,
                        offset: {
                          x: 0,
                          y: 0
                        }
                      }
                    }])
                  }
                })
                // 连线绑定悬浮事件
                this.graph.on('cell:mouseenter', ({ cell }) => {
                  if (cell.shape == 'edge') {
                    cell.addTools([
                      {
                        name: 'button-remove',
                        args: {
                          x: '100%',
                          y: 0,
                          offset: {
                            x: 0,
                            y: 0
                          },
                        },
                      }])
                    cell.setAttrs({
                      line: {
                        stroke: '#409EFF',
                      },
                    })
                    cell.zIndex = 99
                  }
                })
                this.graph.on('cell:mouseleave', ({ cell }) => {
                  if (cell.shape === 'edge') {
                    cell.removeTools()
                    cell.setAttrs({
                      line: {
                        stroke: '#275da3',
                      },
                    })
                    cell.zIndex = 1
                  }
                })
              }
          }
      }
    </script>
  • 节点相关信息全部都储存在变量curSelectNode中,一般用到的属性值都在store->data中,自定义的属性也在这个里面(如笔者自定义的type

3.成果展示

成果展示图

空文件

简介

Antv X6组件库绘制流程图demo 展开 收起
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/breencl/x6_learning_demo.git
git@gitee.com:breencl/x6_learning_demo.git
breencl
x6_learning_demo
x6_learning_demo
master

搜索帮助