Vue项目中使用AntV X6绘制流程图
在Vue2.x项目中使用AntV X6组件库绘制流程图,需要实现以下需求:需求1:左侧菜单中的模块可以拖拽进入画布中生成对应的流程图模块需求2:流程图中的节点之间可以进行连线交互需求3:点击对应的节点后可以进行操作节点(删除、查看节点的相关信息参数)需求4:鼠标悬浮在连线上时可以删除当前连线隐含需求:节点样式需要满足`UI`设计,所以需要自定义节点样式............
Vue项目中使用AntV X6绘制流程图
一、需求
- 在
Vue2.x
(Vue3.x
项目同理)项目中使用AntV X6
组件库绘制流程图,需要实现以下需求: - 需求1:左侧菜单中的模块可以拖拽进入画布中生成对应的流程图模块
- 需求2:流程图中的节点之间可以进行连线交互
- 需求3:点击对应的节点后可以进行操作节点(删除、查看节点的相关信息参数)
- 需求4:鼠标悬浮在连线上时可以删除当前连线
- 隐含需求:节点样式需要满足
UI
设计,所以需要自定义节点样式 - 关于
AntV X6
是什么组件库,可以看X6简介 - 该项目demo的仓库地址在章末
二、解决
- 首先分析需求,通过
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
-
满足左侧菜单栏的拖拽效果可以利用
x6
的stencil
初始化一个左侧菜单栏,这样菜单栏内部的模块就可以进行拖动了;但是为了较高的自定义样式这里舍弃使用这种方式,而是利用H5
的draggable
属性,帮助我们间接完成拖拽模块的功能,这里只举例出几个模块作为演示和学习,菜单栏代码如下: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
提供的方法,给节点绑定相应的事件,代码如下:HomeView.vue <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.成果展示和demo仓库地址
- 到这里以上的需求就都完成了,如果跟着做完相信你对
x6
组件库已经有了一定的了解,对于一些简单的需求也可以试着做了,如果需求复杂还是需要参考官方文档,文档中大量的属性和api
都没有使用到,节点的连线逻辑也只是一笔带过,最后把这个demo
的地址分享出来,csdn资源地址:解压即用x6_learning.rar,仓库地址:x6_learning_demo: Antv X6组件库绘制流程图demo (gitee.com),希望能帮到你🌈
更多推荐
所有评论(0)