先上图
//index.html,有时候可能加载失败,那就再找一个别的cdn 或者npm下载,如果npm下载,
//那么需要全局引入或者局部引入,代码里面写法也会不同,详细的可以看示例
<script src="https://cdn.jsdelivr.net/npm/@antv/x6/dist/x6.js"></script>
//chart.vue
<template>
<div>
<el-button type="primary" @click="download">导出PNG</el-button>
<el-button type="primary" @click="downloadJSON">导出JSON</el-button>
<input type="file" id="select-input" ref="files" style="width: 70px"/>
删除某个节点 shift+backspace
<div id="container">
<div id="stencil"></div>
<div id="graph-container"></div>
</div>
</div>
</template>
<script>
export default {
data(){
return{
graph: null,
}
},
mounted(){
// 为了协助代码演示
const graph = new X6.Graph({
container: document.getElementById("graph-container"),
grid: true,
background: {
color: '#fffbe6', // 设置画布背景颜色
},
mousewheel: {
enabled: true,
zoomAtMousePosition: true,
modifiers: "ctrl",
minScale: 0.5,
maxScale: 3
},
connecting: {
router: {
name: "manhattan",
args: {
padding: 1
}
},
connector: {
name: "rounded",
args: {
radius: 8
}
},
anchor: "center",
connectionPoint: "anchor",
allowBlank: false,
snap: {
radius: 20
},
createEdge() {
return new X6.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"
}
}
}
},
resizing: true,
rotating: true,
selecting: {
enabled: true,
rubberband: true,
showNodeSelectionBox: true
},
snapline: true,
keyboard: true,
clipboard: true
})
this.graph = graph
// #region 初始化 stencil
const stencil = new X6.Addon.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 快捷键与事件
// copy cut paste
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.history.canUndo()) {
graph.history.undo()
}
return false
})
graph.bindKey(["meta+shift+z", "ctrl+shift+z"], () => {
if (graph.history.canRedo()) {
graph.history.redo()
}
return false
})
// select all
graph.bindKey(["meta+a", "ctrl+a"], () => {
const nodes = graph.getNodes()
if (nodes) {
graph.select(nodes)
}
})
//delete
graph.bindKey("shift+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, show) => {
for (let i = 0, len = ports.length; i < len; i = 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")
showPorts(ports, true)
})
graph.on("node:mouseleave", () => {
const container = document.getElementById("graph-container")
const ports = container.querySelectorAll(".x6-port-body")
showPorts(ports, false)
})
// #endregion
// #region 初始化图形
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"
}
]
}
X6.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 }
},
true
)
X6.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"
}
]
}
},
true
)
X6.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 }
},
true
)
X6.Graph.registerNode(
"custom-image",
{
inherit: "rect",
width: 52,
height: 52,
markup: [
{
tagName: "rect",
selector: "body"
},
{
tagName: "image"
},
{
tagName: "text",
selector: "label"
}
],
attrs: {
body: {
stroke: "#5F95FF",
fill: "#5F95FF"
},
image: {
width: 26,
height: 26,
refX: 13,
refY: 16
},
label: {
refX: 3,
refY: 2,
textAnchor: "left",
textVerticalAnchor: "top",
fontSize: 12,
fill: "#fff"
}
},
ports: { ...ports }
},
true
)
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",
attrs: {
body: {
rx: 6,
ry: 6
}
},
label: "可选过程"
})
const r4 = graph.createNode({
shape: "custom-polygon",
attrs: {
body: {
refPoints: "0,10 10,0 20,10 10,20"
}
},
label: "决策"
})
const r5 = graph.createNode({
shape: "custom-polygon",
attrs: {
body: {
refPoints: "10,0 40,0 30,20 0,20"
}
},
label: "数据"
})
const r6 = graph.createNode({
shape: "custom-circle",
label: "连接"
})
stencil.load([r1, r2, r3, r4, r5, r6], "group1")
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(imageNodes, "group2")
//编辑
graph.on('cell:dblclick', ({ cell, e }) => {
const isNode = cell.isNode()
const name = cell.isNode() ? 'node-editor' : 'edge-editor'
cell.removeTool(name)
cell.addTools({
name,
args: {
event: e,
attrs: {
backgroundColor: isNode ? '#EFF4FF' : '#FFF',
},
},
})
})
//直接加在样式上不生效
document.getElementById('graph-container').style.width = 'calc(100% - 180px)'
document.getElementById('graph-container').style.height = '100%'
document.getElementById("select-input").addEventListener("change", (e) =>{
let file = e.target.files[0];
let fileName = file.name.split('.')
if(fileName[fileName.length-1] !== 'txt') {
this.$refs.files.value = ''
return this.$message({message: '请上传.txt格式文件',type: 'warning'});
}
if(!window.FileReader) return this.$message({message: 'Not supported by your browser!',type: 'warning'});
// 创建FileReader对象(文件对象)
const reader = new FileReader();
// 读取出错时:
reader.onerror = (e)=>{
this.$message({message: '读取出错!',type: 'warning'});
};
// 读取中断时:
reader.onabort = (e)=>{
this.$message({message: '读取中断!',type: 'warning'});
};
// 读取成功时:
reader.onload = (e)=>{
// 输出文件
this.$refs.files.value = ''
this.graph.fromJSON(JSON.parse(e.target.result))
this.$message({message: '读取成功!',type: 'success'});
};
reader.readAsText(file,"utf-8");
}, false);
},
methods:{
download(){
this.graph.toPNG((dataUri) => {
// 下载
X6.DataUri.downloadDataUri(dataUri, '流程图.png')
},{
width: 600,
height: 500,
padding: 10,
})
},
downloadJSON(){
let d = this.graph.toJSON()
let el = document.createElement('a')
el.setAttribute('href','data:text.plain;charset=utf-8,'+encodeURIComponent(JSON.stringify(d)))
el.setAttribute('download','图表数据.txt')
el.style.display = 'none'
document.body.appendChild(el)
el.click()
document.body.removeChild(el)
},
}
}
</script>
<style lang="less" scoped>
#container {
display: flex;
border: 1px solid #dfe3e8;
height: 100vh;
width: 100%;
margin-top: 10px;
}
#stencil {
width: 180px;
height: 100%;
position: relative;
border-right: 1px solid #dfe3e8;
}
.x6-widget-stencil {
background-color: #fff;
}
.x6-widget-stencil-title {
background-color: #fff;
}
.x6-widget-stencil-group-title {
background-color: #fff !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;
}
.x6-widget-selection-inner {
border: 1px solid #239edd;
}
.x6-widget-selection-box {
opacity: 0;
}
</style>