Vue3使用AntV | X6绘制流程图:开箱即用
x6官方地址X6·图编辑引擎 | AntV
官方文档仔细地介绍了很多丰富的功能,这里的demo可以满足基本的使用,具体拓展还需要仔细看文档内容
先上效果图
1、安装
通过 npm 或 yarn 命令安装 X6。
# npm
npm install @antv/x6 --save
# yarn
yarn add @antv/x6
初始化画布
<div id="container"></div>
import { Graph } from '@antv/x6'
const graph = new Graph({
container: document.getElementById('container'),
width: 800,
height: 600,
background: {
color: '#F2F7FA',
},
})
使用插件
import { Snapline } from '@antv/x6-plugin-snapline'
graph.use(
new Snapline({
enabled: true,
}),
)
数据导出
graph.toJSON()
具体依赖package.json
{
"@antv/x6": "^2.0.0",
"@antv/x6-plugin-clipboard": "^2.0.0", // 如果使用剪切板功能,需要安装此包
"@antv/x6-plugin-history": "^2.0.0", // 如果使用撤销重做功能,需要安装此包
"@antv/x6-plugin-keyboard": "^2.0.0", // 如果使用快捷键功能,需要安装此包
"@antv/x6-plugin-minimap": "^2.0.0", // 如果使用小地图功能,需要安装此包
"@antv/x6-plugin-scroller": "^2.0.0", // 如果使用滚动画布功能,需要安装此包
"@antv/x6-plugin-selection": "^2.0.0", // 如果使用框选功能,需要安装此包
"@antv/x6-plugin-snapline": "^2.0.0", // 如果使用对齐线功能,需要安装此包
"@antv/x6-plugin-dnd": "^2.0.0", // 如果使用 dnd 功能,需要安装此包
"@antv/x6-plugin-stencil": "^2.0.0", // 如果使用 stencil 功能,需要安装此包
"@antv/x6-plugin-transform": "^2.0.0", // 如果使用图形变换功能,需要安装此包
"@antv/x6-plugin-export": "^2.0.0", // 如果使用图片导出功能,需要安装此包
"@antv/x6-react-components": "^2.0.0", // 如果使用配套 UI 组件,需要安装此包
"@antv/x6-react-shape": "^2.0.0", // 如果使用 react 渲染功能,需要安装此包
"@antv/x6-vue-shape": "^2.0.0" // 如果使用 vue 渲染功能,需要安装此包
}
本文依赖
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"element-plus": "^2.7.6",
"pinia": "^2.1.7",
"vue": "^3.4.29",
"vue-router": "^4.3.3",
"@antv/x6": "latest",
"@antv/x6-plugin-clipboard": "latest",
"@antv/x6-plugin-history": "latest",
"@antv/x6-plugin-keyboard": "latest",
"@antv/x6-plugin-selection": "latest",
"@antv/x6-plugin-snapline": "latest",
"@antv/x6-plugin-stencil": "latest",
"@antv/x6-plugin-transform": "latest",
"@antv/x6-plugin-node-editor": "latest",
"insert-css": "latest"
},
完整代码,开箱即用
<template>
<div id="container">
<div id="stencil"></div>
<div id="graph-container"></div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Graph, Shape } from '@antv/x6'
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 { Keyboard } from '@antv/x6-plugin-keyboard'
import { Clipboard } from '@antv/x6-plugin-clipboard'
import { History } from '@antv/x6-plugin-history'
// import { NodeEditor } from '@antv/x6-plugin-node-editor'
import insertCss from 'insert-css'
onMounted(() => {
// 初始化画布
//- `Graph` 对象用于初始化流程图画布,`container` 指向 HTML 中 `graph-container` 这个 DOM 元素。流程图支持缩放、连线、拖拽等功能。
const graph = new Graph({
container: document.getElementById('graph-container'),
grid: true,
mousewheel: {
enabled: true,
zoomAtMousePosition: true,
modifiers: 'ctrl',
minScale: 0.5,
maxScale: 3
},
connecting: {
router: 'manhattan',
connector: {
name: 'rounded',
args: { radius: 8 }
},
anchor: 'center',
connectionPoint: 'anchor',
allowBlank: false,
snap: { radius: 20 },
createEdge() {
return new Shape.Edge({
attrs: {
line: {
stroke: '#A2B1C3',
strokeWidth: 2,
targetMarker: {
name: 'block',
width: 12,
height: 8
}
}
},
zIndex: 0
})
},
validateConnection({ targetMagnet }) {
return !!targetMagnet
}
},
highlighting: {
magnetAdsorbed: {
name: 'stroke',
args: { attrs: { fill: '#5F95FF', stroke: '#5F95FF' } }
}
}
})
// 插件配置
//- 通过 `.use()` 方法注册了多个插件,包括 `Transform`(支持调整大小、旋转)、`Selection`(选择功能)、`Snapline`(自动对齐线)、`Keyboard`(键盘支持)等。
graph
.use(new Transform({ resizing: true, rotating: true }))
.use(new Selection({ rubberband: true, showNodeSelectionBox: true }))
.use(new Snapline())
.use(new Keyboard())
.use(new Clipboard())
.use(new History())
// 初始化 stencil
//- `Stencil` 是用于提供左侧工具栏的组件,用户可以从工具栏中拖拽图形到画布中。支持基本图形和系统设计图的两类分组
const stencil = new Stencil({
title: '流程图',
target: graph,
stencilGraphWidth: 200,
stencilGraphHeight: 180,
collapsable: true,
groups: [
{ title: '基础流程图', name: 'group1' },
{ title: '系统设计图', name: 'group2', graphHeight: 250, layoutOptions: { rowHeight: 70 } }
],
layoutOptions: { columns: 2, columnWidth: 80, rowHeight: 55 }
})
document.getElementById('stencil')?.appendChild(stencil.container)
// #region 快捷键与事件
graph.bindKey(['meta+c', 'ctrl+c'], () => {
const cells = graph.getSelectedCells()
if (cells.length) {
graph.copy(cells)
}
return false
})
graph.bindKey(['meta+x', 'ctrl+x'], () => {
const cells = graph.getSelectedCells()
if (cells.length) {
graph.cut(cells)
}
return false
})
graph.bindKey(['meta+v', 'ctrl+v'], () => {
if (!graph.isClipboardEmpty()) {
const cells = graph.paste({ offset: 32 })
graph.cleanSelection()
graph.select(cells)
}
return false
})
// undo redo
graph.bindKey(['meta+z', 'ctrl+z'], () => {
if (graph.canUndo()) {
graph.undo()
}
return false
})
graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => {
if (graph.canRedo()) {
graph.redo()
}
return false
})
// select all
graph.bindKey(['meta+a', 'ctrl+a'], () => {
const nodes = graph.getNodes()
if (nodes) {
graph.select(nodes)
}
})
// delete
graph.bindKey('backspace', () => {
const cells = graph.getSelectedCells()
if (cells.length) {
graph.removeCells(cells)
}
})
// zoom
graph.bindKey(['ctrl+1', 'meta+1'], () => {
const zoom = graph.zoom()
if (zoom < 1.5) {
graph.zoom(0.1)
}
})
graph.bindKey(['ctrl+2', 'meta+2'], () => {
const zoom = graph.zoom()
if (zoom > 0.5) {
graph.zoom(-0.1)
}
})
// 控制连接桩显示/隐藏
const showPorts = (ports: NodeListOf<SVGElement>, show: boolean) => {
for (let i = 0, len = ports.length; i < len; i += 1) {
ports[i].style.visibility = show ? 'visible' : 'hidden'
}
}
graph.on('node:mouseenter', () => {
const container = document.getElementById('graph-container')!
const ports = container.querySelectorAll('.x6-port-body') as NodeListOf<SVGElement>
showPorts(ports, true)
})
graph.on('node:mouseleave', () => {
const container = document.getElementById('graph-container')!
const ports = container.querySelectorAll('.x6-port-body') as NodeListOf<SVGElement>
showPorts(ports, false)
})
// 注册自定义节点
//- 通过 `Graph.registerNode` 注册了不同形状(矩形、圆形、多边形等)的自定义节点,并且定义了连接桩的位置和样式。
const ports = {
groups: {
top: {
position: 'top',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: { visibility: 'hidden' }
}
}
},
right: {
position: 'right',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: { visibility: 'hidden' }
}
}
},
bottom: {
position: 'bottom',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: { visibility: 'hidden' }
}
}
},
left: {
position: 'left',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: { visibility: 'hidden' }
}
}
}
},
items: [{ group: 'top' }, { group: 'right' }, { group: 'bottom' }, { group: 'left' }]
}
// 注册不同形状的自定义节点
Graph.registerNode('custom-rect', {
inherit: 'rect',
width: 66,
height: 36,
attrs: {
body: { strokeWidth: 1, stroke: '#5F95FF', fill: '#EFF4FF' },
text: { fontSize: 12, fill: '#262626' }
},
ports: { ...ports },
//支持文字编辑
tools: [
{
name: 'node-editor',
args: {
attrs: {
backgroundColor: '#EFF4FF'
}
}
}
]
})
Graph.registerNode('custom-polygon', {
inherit: 'polygon',
width: 66,
height: 36,
attrs: {
body: { strokeWidth: 1, stroke: '#5F95FF', fill: '#EFF4FF' },
text: { fontSize: 12, fill: '#262626' }
},
ports: { ...ports, items: [{ group: 'top' }, { group: 'bottom' }] },
//支持文字编辑
tools: [
{
name: 'node-editor',
args: {
attrs: {
backgroundColor: '#EFF4FF'
}
}
}
]
})
Graph.registerNode('custom-circle', {
inherit: 'circle',
width: 45,
height: 45,
attrs: {
body: { strokeWidth: 1, stroke: '#5F95FF', fill: '#EFF4FF' },
text: { fontSize: 12, fill: '#262626' }
},
ports: { ...ports },
//支持文字编辑
tools: [
{
name: 'node-editor',
args: {
attrs: {
backgroundColor: '#EFF4FF'
}
}
}
]
})
// 加载图形节点到 stencil
const r1 = graph.createNode({
shape: 'custom-rect',
label: '开始',
attrs: { body: { rx: 20, ry: 26 } }
})
const r2 = graph.createNode({ shape: 'custom-rect', label: '过程' })
const r3 = graph.createNode({
shape: 'custom-rect',
label: '可选过程',
attrs: { body: { rx: 6, ry: 6 } }
})
const r4 = graph.createNode({
shape: 'custom-polygon',
label: '决策',
attrs: { body: { refPoints: '0,10 10,0 20,10 10,20' } }
})
const r5 = graph.createNode({
shape: 'custom-polygon',
label: '数据',
attrs: { body: { refPoints: '10,0 40,0 30,20 0,20' } }
})
const r6 = graph.createNode({ shape: 'custom-circle', label: '连接' })
stencil.load([r1, r2, r3, r4, r5, r6], 'group1')
//------------------------------------------文字编辑--------------------
// const source = graph.addNode({
// x: 180,
// y: 60,
// width: 100,
// height: 40,
// attrs: {
// body: {
// stroke: '#5F95FF',
// fill: '#EFF4FF',
// strokeWidth: 1
// }
// },
// tools: [
// {
// name: 'node-editor',
// args: {
// attrs: {
// backgroundColor: '#EFF4FF'
// }
// }
// }
// ]
// })
// const target = graph.addNode({
// x: 320,
// y: 250,
// width: 100,
// height: 40,
// attrs: {
// body: {
// stroke: '#5F95FF',
// fill: '#EFF4FF',
// strokeWidth: 1
// }
// },
// tools: [
// {
// name: 'node-editor',
// args: {
// attrs: {
// backgroundColor: '#EFF4FF'
// }
// }
// }
// ]
// })
// graph.addEdge({
// source,
// target,
// attrs: {
// line: {
// stroke: '#A2B1C3',
// strokeWidth: 2
// }
// },
// tools: [
// {
// name: 'edge-editor',
// args: {
// attrs: {
// backgroundColor: '#fff'
// }
// }
// }
// ]
// })
//-------------------------------------------------------
// 加载图像节点
const imageShapes = [
{
label: 'Client',
image: 'https://gw.alipayobjects.com/zos/bmw-prod/687b6cb9-4b97-42a6-96d0-34b3099133ac.svg'
},
{
label: 'Http',
image: 'https://gw.alipayobjects.com/zos/bmw-prod/dc1ced06-417d-466f-927b-b4a4d3265791.svg'
},
{
label: 'Api',
image: 'https://gw.alipayobjects.com/zos/bmw-prod/c55d7ae1-8d20-4585-bd8f-ca23653a4489.svg'
},
{
label: 'Sql',
image: 'https://gw.alipayobjects.com/zos/bmw-prod/6eb71764-18ed-4149-b868-53ad1542c405.svg'
},
{
label: 'Clound',
image: 'https://gw.alipayobjects.com/zos/bmw-prod/c36fe7cb-dc24-4854-aeb5-88d8dc36d52e.svg'
},
{
label: 'Mq',
image: 'https://gw.alipayobjects.com/zos/bmw-prod/2010ac9f-40e7-49d4-8c4a-4fcf2f83033b.svg'
}
]
const imageNodes = imageShapes.map((item) =>
graph.createNode({
shape: 'custom-image',
label: item.label,
attrs: { image: { 'xlink:href': item.image } }
})
)
//- `stencil.load()` 方法用于将自定义节点加载到 Stencil 中,用户可以从左侧拖拽这些节点到画布上。
stencil.load(imageNodes, 'group2')
// 添加 CSS 样式
//- 使用 `insertCss()` 动态添加了样式,使得画布、工具栏以及选择框等元素的样式更加统一。
insertCss(`
#container {
display: flex;
border: 1px solid #dfe3e8;
}
#stencil {
width: 180px;
height: 100%;
position: relative;
border-right: 1px solid #dfe3e8;
}
#graph-container {
width: calc(100% - 180px);
height: 100%;
}
.x6-widget-stencil, .x6-widget-stencil-title, .x6-widget-stencil-group-title {
background-color: #fff !important;
}
.x6-widget-transform
, .x6-widget-selection-box, .x6-widget-selection-inner {
border: 1px solid #239edd;
}
`)
})
</script>
<style scoped lang="scss">
#container {
display: flex;
height: 50vh;
}
#stencil {
width: 180px;
height: 100%;
border-right: 1px solid #dfe3e8;
}
#graph-container {
flex-grow: 1;
width: 800px;
}
</style>