antv3 x6 基本语法-流程图(二)
实现效果
* 在左侧拖拽到右侧绘制流程图
* 相关基本操作 )

页面布局创建: 左右两部分 vue部分
给每个拖拽区域绑定
@mousedown="handleDragEnd
<template> <div class="conatainer"> <div class="left"> <!-- 图形部分--start --> <div class="custom-node"> <div class="node-dom dnd-circle dnd-start" @mousedown="handleDragEnd($event, { name: '开始', type: 'start' })" ></div> <div>开始</div> </div> <div class="custom-node"> <div class="node-dom dnd-rect" @mousedown="handleDragEnd($event, { name: 'rect', type: 'node1' })" ></div> <div>节点1</div> </div> <div class="custom-node"> <div class="node-dom dnd-polygon" @mousedown="handleDragEnd($event, { name: 'start', type: 'node2' })" ></div> <div>节点2</div> </div> <div class="custom-node"> <div class="node-dom dnd-circle" @mousedown="handleDragEnd($event, { name: 'start', type: 'end' })" ></div> <div>结束</div> </div> <!-- 图形部分--end --> <div v-for="item in moduleList" :key="item.id" @mousedown="handleDragEnd($event, item)" style="text-align: center" > <img :src="item.image" alt="" width="50px" /> <p>{{ item.name }}</p> </div> </div> <div class="right" ref="right"> <div style="width: 100%; height: 100%"> <div id="container"></div> </div> </div> </div> </template>
js 部分
插件使用都在antv 直接导入,v3版本做了整合,不用每个单独引入
import { Clipboard, Graph, History, Keyboard, Selection, Shape, Snapline, Stencil, Transform, Dnd, } from "@antv/x6";
桩子节点,上下左右 ,如果某个节点只有上下,可以在节点创建时进行覆盖
ports: { groups: { top: { position: "top" /* 顶部端口 */, attrs: { circle: { r: 4, magnet: true, stroke: "#5F95FF", strokeWidth: 1, fill: "#fff", }, }, }, bottom: { position: "bottom" /* 底部端口 */, attrs: { circle: { r: 4, magnet: true, stroke: "#5F95FF", strokeWidth: 1, fill: "#fff", }, }, }, left: { position: "left" /* 左侧端口 */, attrs: { circle: { r: 4, magnet: true, stroke: "#5F95FF", strokeWidth: 1, fill: "#fff", }, }, }, right: { position: "right" /* 右侧端口 */, attrs: { circle: { r: 4, magnet: true, stroke: "#5F95FF", strokeWidth: 1, fill: "#fff", }, }, }, }, items: [ // 端口实例(启用四个方向的端口) { group: "top", id: "top" }, { group: "bottom", id: "bottom" }, { group: "left", id: "left" }, { group: "right", id: "right" }, ], },
创建画布-基本配置
this.graph = new Graph({ container: document.getElementById("container"), // 设置画布大小,默认为容器大小 // width: rightRef.offsetWidth, // height: rightRef.offsetHeight, grid: 10, 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", }, }, }, }, }, autoResize: true, panning: true, // 设置画布背景颜色 background: { color: "#F2F7FA" }, grid: { visible: true, type: "doubleMesh", // 或者不用 args: [ { color: "#eee", // 主网格线颜色 thickness: 1, // 主网格线宽度 }, { color: "#ddd", // 次网格线颜色 thickness: 1, // 次网格线宽度 factor: 4, // 主次网格线间隔 }, ], }, });
使用dnd 插件
this.dnd = new Dnd({ target: this.graph, scaled: false, // preview: (graph, node) => { // node.attr({ // "body/stroke": "#40a9ff", // "body/strokeWidth": 2, // "body/fill": "#f0f8ff", // }); // return node; // }, });
注册节点
Graph.registerNode( "custom-circle-start", { inherit: "circle", ports: { ...this.ports }, }, true, );
创建节点
// 创建圆圈节点 const startNode = this.graph.createNode({ shape: "custom-circle-start", id: IdValue, width: 38, height: 38, attrs: { body: { strokeWidth: 1, stroke: "#000000", fill: "#ffffff", rx: 10, ry: 10, }, }, });
画布渲染显示
this.dnd.start(node_type, e);
辅助操作
this.graph.bindKey(["ctrl+2", "meta+2"], () => { const zoom = this.graph.zoom(); if (zoom > 0.5) { this.graph.zoom(-0.1); } }); this.graph.on("cell:mouseenter", ({ cell }) => { const container = document.getElementById("container"); const ports = container.querySelectorAll(".x6-port-body"); showPorts(ports, true); if (cell.isNode()) { cell.addTools([ { name: "button-remove", args: { x: 0, y: 0, offset: { x: 10, y: 10 }, }, }, ]); } else { cell.addTools([ { name: "button-remove", args: { distance: -40 }, }, ]); } }); this.graph.on("cell:mouseleave", ({ cell }) => { if (cell.hasTool("button-remove")) { cell.removeTool("button-remove"); } const container = document.getElementById("container"); const ports = container.querySelectorAll(".x6-port-body"); showPorts(ports, false); });
完整代码
<template> <div class="conatainer"> <div class="left"> <!-- 图形部分--start --> <div class="custom-node"> <div class="node-dom dnd-circle dnd-start" @mousedown="handleDragEnd($event, { name: '开始', type: 'start' })" ></div> <div>开始</div> </div> <div class="custom-node"> <div class="node-dom dnd-rect" @mousedown="handleDragEnd($event, { name: 'rect', type: 'node1' })" ></div> <div>节点1</div> </div> <div class="custom-node"> <div class="node-dom dnd-polygon" @mousedown="handleDragEnd($event, { name: 'start', type: 'node2' })" ></div> <div>节点2</div> </div> <div class="custom-node"> <div class="node-dom dnd-circle" @mousedown="handleDragEnd($event, { name: 'start', type: 'end' })" ></div> <div>结束</div> </div> <!-- 图形部分--end --> <div v-for="item in moduleList" :key="item.id" @mousedown="handleDragEnd($event, item)" style="text-align: center" > <img :src="item.image" alt="" width="50px" /> <p>{{ item.name }}</p> </div> </div> <div class="right" ref="right"> <div style="width: 100%; height: 100%"> <div id="container"></div> </div> </div> </div> </template> <script> import { Clipboard, Graph, History, Keyboard, Selection, Shape, Snapline, Stencil, Transform, Dnd, } from "@antv/x6"; export default { name: "AntvX6", data() { return { graph: null, dnd: null, moduleList: [ { id: 1, name: "节点1", image: "https://ts1.tc.mm.bing.net/th/id/OIP-C.mH9YLFEL5YdVxJM82mjVJQHaEo?w=285&h=211&c=8&rs=1&qlt=90&o=6&dpr=2&pid=3.1&rm=2", }, { id: 8, name: "节点2", image: "https://ts1.tc.mm.bing.net/th/id/OIP-C.Mq9zt66lU2fko_h2OWHIlAHaE8?w=255&h=211&c=8&rs=1&qlt=90&o=6&dpr=2&pid=3.1&rm=2", }, { id: 2, name: "节点3", image: "https://ts1.tc.mm.bing.net/th/id/OIP-C.g9UbVfyVZX-SfD09JcYr5QHaEK?w=283&h=211&c=8&rs=1&qlt=90&o=6&dpr=2&pid=3.1&rm=2", }, { id: 3, name: "节点4", image: "https://ts1.tc.mm.bing.net/th/id/OIP-C.IJZgTNx1vp9EML_1wV5p2gHaEo?w=255&h=211&c=8&rs=1&qlt=90&o=6&dpr=2&pid=3.1&rm=2", }, ], ports: { groups: { top: { position: "top" /* 顶部端口 */, attrs: { circle: { r: 4, magnet: true, stroke: "#5F95FF", strokeWidth: 1, fill: "#fff", }, }, }, bottom: { position: "bottom" /* 底部端口 */, attrs: { circle: { r: 4, magnet: true, stroke: "#5F95FF", strokeWidth: 1, fill: "#fff", }, }, }, left: { position: "left" /* 左侧端口 */, attrs: { circle: { r: 4, magnet: true, stroke: "#5F95FF", strokeWidth: 1, fill: "#fff", }, }, }, right: { position: "right" /* 右侧端口 */, attrs: { circle: { r: 4, magnet: true, stroke: "#5F95FF", strokeWidth: 1, fill: "#fff", }, }, }, }, items: [ // 端口实例(启用四个方向的端口) { group: "top", id: "top" }, { group: "bottom", id: "bottom" }, { group: "left", id: "left" }, { group: "right", id: "right" }, ], }, }; }, mounted() { this.initGraph(); }, methods: { initGraph() { const rightRef = this.$refs.right; this.graph = new Graph({ container: document.getElementById("container"), // 设置画布大小,默认为容器大小 // width: rightRef.offsetWidth, // height: rightRef.offsetHeight, grid: 10, 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", }, }, }, }, }, autoResize: true, panning: true, // 设置画布背景颜色 background: { color: "#F2F7FA" }, grid: { visible: true, type: "doubleMesh", // 或者不用 args: [ { color: "#eee", // 主网格线颜色 thickness: 1, // 主网格线宽度 }, { color: "#ddd", // 次网格线颜色 thickness: 1, // 次网格线宽度 factor: 4, // 主次网格线间隔 }, ], }, }); // #region 使用插件 this.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()); // #endregion this.dnd = new Dnd({ target: this.graph, scaled: false, // preview: (graph, node) => { // node.attr({ // "body/stroke": "#40a9ff", // "body/strokeWidth": 2, // "body/fill": "#f0f8ff", // }); // return node; // }, }); // **************** 注册节点 -start **************** Graph.registerNode( "custom-node", { inherit: "rect", // 继承于 rect 节点 width: 100, height: 40, markup: [ { tagName: "rect", // 标签名称 selector: "body", // 选择器 }, { tagName: "image", selector: "img", }, { tagName: "text", selector: "label", }, ], attrs: { body: { stroke: "#8f8f8f", strokeWidth: 1, fill: "#fff", rx: 6, ry: 6, }, img: { "xlink:href": "https://gw.alipayobjects.com/zos/antfincdn/FLrTNDvlna/antv.png", width: 16, height: 16, x: 12, y: 12, }, }, }, true, ); Graph.registerNode( "custom-circle-start", { inherit: "circle", ports: { ...this.ports }, }, true, ); Graph.registerNode( "custom-polygon", { inherit: "polygon", points: "0,10 10,0 20,10 10,20", ports: { ...this.ports, }, }, true, ); Graph.registerNode( "custom-rect", { inherit: "rect", // attrs 可自定义 ports: { ...this.ports }, }, true, ); // **************** 注册节点 -end **************** this.graph.centerContent(); // 居中显示 // 控制连接桩显示/隐藏 const showPorts = (ports, show) => { for (let i = 0, len = ports.length; i < len; i += 1) { ports[i].style.visibility = show ? "visible" : "hidden"; } }; this.graph.bindKey(["meta+c", "ctrl+c"], () => { const cells = this.graph.getSelectedCells(); if (cells.length) { this.graph.copy(cells); } return false; }); this.graph.bindKey(["meta+x", "ctrl+x"], () => { const cells = this.graph.getSelectedCells(); if (cells.length) { this.graph.cut(cells); } return false; }); this.graph.bindKey(["meta+v", "ctrl+v"], () => { if (!this.graph.isClipboardEmpty()) { const cells = this.graph.paste({ offset: 32 }); this.graph.cleanSelection(); this.graph.select(cells); } return false; }); this.graph.bindKey(["meta+z", "ctrl+z"], () => { if (this.graph.canUndo()) { this.graph.undo(); } return false; }); this.graph.bindKey(["meta+shift+z", "ctrl+shift+z"], () => { if (this.graph.canRedo()) { this.graph.redo(); } return false; }); this.graph.bindKey(["meta+a", "ctrl+a"], () => { const nodes = this.graph.getNodes(); if (nodes) { this.graph.select(nodes); } }); this.graph.bindKey("backspace", () => { const cells = this.graph.getSelectedCells(); if (cells.length) { this.graph.removeCells(cells); } }); this.graph.bindKey(["ctrl+1", "meta+1"], () => { const zoom = this.graph.zoom(); if (zoom < 1.5) { this.graph.zoom(0.1); } }); this.graph.bindKey(["ctrl+2", "meta+2"], () => { const zoom = this.graph.zoom(); if (zoom > 0.5) { this.graph.zoom(-0.1); } }); this.graph.on("cell:mouseenter", ({ cell }) => { const container = document.getElementById("container"); const ports = container.querySelectorAll(".x6-port-body"); showPorts(ports, true); if (cell.isNode()) { cell.addTools([ { name: "button-remove", args: { x: 0, y: 0, offset: { x: 10, y: 10 }, }, }, ]); } else { cell.addTools([ { name: "button-remove", args: { distance: -40 }, }, ]); } }); this.graph.on("cell:mouseleave", ({ cell }) => { if (cell.hasTool("button-remove")) { cell.removeTool("button-remove"); } const container = document.getElementById("container"); const ports = container.querySelectorAll(".x6-port-body"); showPorts(ports, false); }); this.graph.on("node:click", ({ x, y, node, cell }) => { this.currentCell = cell; if (cell.isNode() && !cell.attrs.typeName) { // 这可以写一些点击节点时和右侧表单交互的效果 } // if (cell.hasTool("button")) { // cell.removeTool("button"); // } else { // if ( // cell.isNode() && // !cell.attrs.typeName && // cell.shape !== "custom-circle-start" // ) { // const container = document.getElementById("container"); // const ports = container.querySelectorAll(".x6-port-body"); // showPorts(ports, false); // } // } }); this.graph.on("blank:click", () => { // this.currentCell && this.currentCell.removeTools(); if (this.currentCell) { this.currentCell.removeTool("button"); this.currentCell.removeTool("button-move"); } // this.isClose = true; // this.isGloable = true; }); }, handleDragEnd(e, item) { // console.log(e); this.addHandleNode(e, item); }, addHandleNode(e, item) { const IdValue = new Date().getTime(); // 创建圆圈节点 const startNode = this.graph.createNode({ shape: "custom-circle-start", id: IdValue, width: 38, height: 38, attrs: { body: { strokeWidth: 1, stroke: "#000000", fill: "#ffffff", rx: 10, ry: 10, }, }, }); const polygonNode = this.graph.createNode({ shape: "custom-polygon", width: 80, height: 60, attrs: { body: { strokeWidth: 1, stroke: "#000000", fill: "#ffffff", rx: 10, ry: 10, }, label: { fontSize: 13, fontWeight: "bold", }, }, }); const rectNode = this.graph.createNode({ shape: "custom-rect", width: 80, height: 60, attrs: { body: { strokeWidth: 1, stroke: "#000000", fill: "#ffffff", rx: 10, ry: 10, }, label: { fontSize: 13, fontWeight: "bold", }, }, }); const endNode = this.graph.createNode({ shape: "custom-circle-start", width: 38, height: 38, key: "end", attrs: { body: { strokeWidth: 4, stroke: "#000000", fill: "#ffffff", rx: 10, ry: 10, }, label: { fontSize: 13, fontWeight: "bold", }, }, }); let node_type = null; if (item.type === "start") { node_type = startNode; } else if (item.type === "node1") { node_type = rectNode; } else if (item.type === "node2") { node_type = polygonNode; } else if (item.type === "end") { node_type = endNode; } this.dnd.start(node_type, e); }, }, }; </script> <style lang="scss" scoped> .conatainer { display: flex; height: 100vh; // padding: 10px; .left { min-width: 150px; border: 1px solid red; margin: 5px; } .right { flex: 1; margin-left: 10px; border: 1px solid blue; margin: 5px; } } .dnd-rect { width: 50px; height: 30px; line-height: 40px; text-align: center; border: 2px solid #000000; border-radius: 6px; cursor: move; font-size: 12px; margin-top: 30px; } .dnd-polygon { width: 35px; height: 35px; border: 2px solid #000000; transform: rotate(45deg); cursor: move; font-size: 12px; margin-top: 30px; margin-bottom: 10px; } .dnd-circle { width: 35px; height: 35px; line-height: 45px; text-align: center; border: 5px solid #000000; border-radius: 100%; cursor: move; font-size: 12px; margin-top: 30px; } .dnd-start { border: 2px solid #000000; } .custom-node { text-align: center; } .node-dom { display: inline-block; } </style>
浙公网安备 33010602011771号