Vue2.x
(Vue3.x
项目同理)项目中使用AntV X6
组件库绘制流程图,需要实现以下需求:UI
设计,所以需要自定义节点样式AntV X6
是什么组件库,可以看X6简介AntV X6
组件给出的文档和API
是可以满足以上需求的,以下以Vue2.x
项目中使用AntV X6
并满足相应需求为例,讲述AntV X6
使用,帮助初学者快速上手,后文中使用x6
代替AntV X6
:X6
组件库Vue
的项目后就可以安装x6
了,执行命令npm install @antv/x6 --save
,详见文档X6快速上手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>
其中初始化画布时,画布中的部分属性在注释中给出,如果想要深入了解,建议在官方文档中根据对应案例进行学习了解
满足左侧菜单栏的拖拽效果可以利用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>
到目前为止已经完成了模块的拖动部分,接下来需要拖动到画布中生成相应的模块,这里需要满足隐含的需求,自定义每个模块生成节点的样式,利用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
}
}
在页面组件中引入工具函数,并添加一个节点生成函数,将模块的参数传入节点生成函数中,生成相应的节点,代码如下:
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>
这里需要利用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
)
x6
组件库已经有了一定的了解,对于一些简单的需求也可以试着做了,如果需求复杂还是需要参考官方文档,文档中大量的属性和api
都没有使用到,节点的连线逻辑也只是一笔带过,最后把这个demo
的地址分享出来,csdn资源地址:解压即用x6_learning.rar,仓库地址:x6_learning_demo: Antv X6组件库绘制流程图demo (gitee.com),希望能帮到你🌈