vue+antv g6+element-ui完整流程图
最近一直在研究流程图相关的技术,一次在逛 GitHub 时发现了一个技术栈为 vue+g6+element-ui 的项目,基础功能完好,如 node 与 edge 的托拉拽,主界面如下:
GitHub 链接为:https://github.com/caoyu48/vue-g6-editor
线上访问地址为:http://62.234.69.136/
g6 官方 API 文档:https://antv-g6.gitee.io/zh/docs/manual/introduction
但由于作者没有写代码的说明文档,本文仅仅只是我本人对读该源码的一些理解,如有不同理解还希望各位朋友指出订正。
一、本地运行
首先从上面的 GitHub 网址下载该项目,下载该项目需要的依赖包。这里很多人下载完依赖包之后发现启动报错无法运行,需要注意的是这里
不要使用 cnpm install,一定要使用 npm install!!!具体为什么 cnpm 不行,我也不知道。
二、给连线加上文字 [本人自己增加]
1、修改 src/components/DetailPanel/index.vue
<template> <div class="detailpannel"> <div> <div v-if="status=='node-selected'" class="pannel" id="node_detailpannel"> <div class="pannel-title">模型详情</div> <div class="block-container"> <el-row :gutter="10"> <el-col :span="8">名称</el-col> <el-col :span="16"> <el-input v-model="node.label" @change="handleChangeName" /> </el-col> <el-col :span="8">任意属性</el-col> <el-col :span="16"> <el-input v-model="node.xxx" /> </el-col> </el-row> </div> </div> <div v-if="status==='canvas-selected'" class="pannel" id="canvas_detailpannel"> <div class="pannel-title">画布</div> <div class="block-container"> <el-checkbox v-model="showGrid" @change="changeGridState">网格对齐</el-checkbox> </div> </div>
<!-- 我添加的 --> <div v-if="status ==='edge-selected'" id="edge_detailpannel" class="pannel"> <div class="pannel-title">连线</div> <div class="block-container"> <el-col :span="8">内容</el-col> <el-col :span="16"> <el-input v-model="edge.label" @change="handleChange" /> </el-col> <el-col :span="8">文字颜色</el-col> <el-col :span="16"> <el-color-picker v-model="textColor" @change="handleChangeColor" /> </el-col> </div> </div> <!-- <div v-if="status==='group-selected'" class="pannel"id="node_detailpannel"> <div class="pannel-title"> 群组详情 </div> <div class="block-container"> <div class="p"> 名称: <el-input v-model="name" /> </div> <div class="p"> 任意属性: <el-input v-model="color" /> </div> </div> </div> --> </div> </div> </template><script>
import eventBus from "@/utils/eventBus";
import Grid from "@antv/g6/build/grid";
export default {
data() {
return {
status: "canvas-selected",
showGrid: false,
page: {},
graph: {},
item: {},
node: {},
//【我添加的】
edge:{},
grid: null,
//【我添加的】
textColor: 'rgba(19, 206, 102, 0.8)'
};
},
created() {
this.init();
this.bindEvent();
},
methods: {
init(){},
bindEvent() {
let self = this;
eventBus.$on("afterAddPage", page => {
self.page = page;
self.graph = self.page.graph;
eventBus.$on("nodeselectchange", item => {
if (item.select === true && item.target.getType() === "node") {
self.status = "node-selected";
self.item = item.target;
self.node = item.target.getModel();
}
//【我添加的】
else if (item.select === true && item.target.getType() === "edge") {
self.status = "edge-selected";
self.item = item.target;
self.edge = item.target.getModel();
}
else {
self.status = "canvas-selected";
self.item = null;
self.node = null;
}
});
});
},
handleChangeName(e) {
const model = {
label: e
};
this.graph.update(this.item, model);
},
changeGridState(value) {
if (value) {
this.grid = new Grid();
this.graph.addPlugin(this.grid);
} else {
this.graph.removePlugin(this.grid);
}
},
//【我添加的】
handleChange(e) {
const model = {
label: e
};
console.log(model)
this.graph.update(this.item, model);
},
handleChangeColor(e) {
const model = {
textColor: e
};
this.graph.update(this.item, model);
}
}
};
</script><style scoped>
.detailpannel {
height: 100%;
position: absolute;
right: 0px;
z-index: 2;
background: #f7f9fb;
width: 200px;
border-left: 1px solid #e6e9ed;
}
.detailpannel .block-container {
padding: 16px 8px;
}
.block-container .el-col {
height: 28px;
display: flex;
align-items: center;
margin-bottom: 10px;
}
.pannel-title {
height: 32px;
border-top: 1px solid #dce3e8;
border-bottom: 1px solid #dce3e8;
background: #ebeef2;
color: #000;
line-height: 28px;
padding-left: 12px;
}
</style>
2、修改 src/components/Flow/customEdge.js
import G6 from "@antv/g6/build/g6"; import {uniqueId} from '@/utils' const MIN_ARROW_SIZE = 3const customEdge = {
init() {
const dashArray = [
[0, 1],
[0, 2],
[1, 2],
[0, 1, 1, 2],
[0, 2, 1, 2],
[1, 2, 1, 2],
[2, 2, 1, 2],
[3, 2, 1, 2],
[4, 2, 1, 2]
];const lineDash </span>= [4,2,1,2<span style="color: rgba(0, 0, 0, 1)">]; const interval </span>= 9<span style="color: rgba(0, 0, 0, 1)">; G6.registerEdge(</span>'customEdge'<span style="color: rgba(0, 0, 0, 1)">, { draw(cfg, group) { let sourceNode, targetNode, start, end </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">typeof</span> (cfg.souxrce) === 'string'<span style="color: rgba(0, 0, 0, 1)">) { cfg.source </span>=<span style="color: rgba(0, 0, 0, 1)"> cfg.sourceNode } </span><span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(0, 0, 0, 1)">cfg.start){ cfg.start</span>=<span style="color: rgba(0, 0, 0, 1)">{ x:</span>0<span style="color: rgba(0, 0, 0, 1)">, y:</span>17<span style="color: rgba(0, 0, 0, 1)"> } } </span><span style="color: rgba(0, 0, 255, 1)">if</span>(!<span style="color: rgba(0, 0, 0, 1)">cfg.end){ cfg.end</span>=<span style="color: rgba(0, 0, 0, 1)">{ x:</span>0<span style="color: rgba(0, 0, 0, 1)">, y:</span>-17<span style="color: rgba(0, 0, 0, 1)"> } } </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">cfg.source.x) { sourceNode </span>=<span style="color: rgba(0, 0, 0, 1)"> cfg.source.getModel() start </span>= { x: sourceNode.x + cfg.start.x, y: sourceNode.y +<span style="color: rgba(0, 0, 0, 1)"> cfg.start.y } } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> { start </span>=<span style="color: rgba(0, 0, 0, 1)"> cfg.source } </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">typeof</span> (cfg.target) === 'string'<span style="color: rgba(0, 0, 0, 1)">) { cfg.target </span>=<span style="color: rgba(0, 0, 0, 1)"> cfg.targetNode } </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">cfg.target.x) { targetNode </span>=<span style="color: rgba(0, 0, 0, 1)"> cfg.target.getModel() end </span>= { x: targetNode.x + cfg.end.x, y: targetNode.y +<span style="color: rgba(0, 0, 0, 1)"> cfg.end.y } } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> { end </span>=<span style="color: rgba(0, 0, 0, 1)"> cfg.target } let path </span>=<span style="color: rgba(0, 0, 0, 1)"> [] let hgap </span>= Math.abs(end.x -<span style="color: rgba(0, 0, 0, 1)"> start.x) </span><span style="color: rgba(0, 0, 255, 1)">if</span> (end.x ><span style="color: rgba(0, 0, 0, 1)"> start.x) { path </span>=<span style="color: rgba(0, 0, 0, 1)"> [ [</span>'M'<span style="color: rgba(0, 0, 0, 1)">, start.x, start.y], [ </span>'C'<span style="color: rgba(0, 0, 0, 1)">, start.x, start.y </span>+ hgap / (hgap / 50<span style="color: rgba(0, 0, 0, 1)">), end.x, end.y </span>- hgap / (hgap / 50<span style="color: rgba(0, 0, 0, 1)">), end.x, end.y </span>- 4<span style="color: rgba(0, 0, 0, 1)"> ], [ </span>'L'<span style="color: rgba(0, 0, 0, 1)">, end.x, end.y ] ] } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> { path </span>=<span style="color: rgba(0, 0, 0, 1)"> [ [</span>'M'<span style="color: rgba(0, 0, 0, 1)">, start.x, start.y], [ </span>'C'<span style="color: rgba(0, 0, 0, 1)">, start.x, start.y </span>+ hgap / (hgap / 50<span style="color: rgba(0, 0, 0, 1)">), end.x, end.y </span>- hgap / (hgap / 50<span style="color: rgba(0, 0, 0, 1)">), end.x, end.y </span>- 4<span style="color: rgba(0, 0, 0, 1)"> ], [ </span>'L'<span style="color: rgba(0, 0, 0, 1)">, end.x, end.y ] ] } let lineWidth </span>= 1<span style="color: rgba(0, 0, 0, 1)">; lineWidth </span>= lineWidth > MIN_ARROW_SIZE ?<span style="color: rgba(0, 0, 0, 1)"> lineWidth : MIN_ARROW_SIZE; const width </span>= lineWidth * 10 / 3<span style="color: rgba(0, 0, 0, 1)">; const halfHeight </span>= lineWidth * 4 / 3<span style="color: rgba(0, 0, 0, 1)">; const radius </span>= lineWidth * 4<span style="color: rgba(0, 0, 0, 1)">; const endArrowPath </span>=<span style="color: rgba(0, 0, 0, 1)"> [ [</span>'M', -<span style="color: rgba(0, 0, 0, 1)">width, halfHeight], [</span>'L', 0, 0<span style="color: rgba(0, 0, 0, 1)">], [</span>'L', -width, -<span style="color: rgba(0, 0, 0, 1)">halfHeight], [</span>'A', radius, radius, 0, 0, 1, -<span style="color: rgba(0, 0, 0, 1)">width, halfHeight], [</span>'Z'<span style="color: rgba(0, 0, 0, 1)">] ]; const keyShape </span>= group.addShape('path'<span style="color: rgba(0, 0, 0, 1)">, { attrs: { id: </span>'edge' +<span style="color: rgba(0, 0, 0, 1)"> uniqueId(), path: path, stroke: </span>'#b8c3ce'<span style="color: rgba(0, 0, 0, 1)">, lineAppendWidth: </span>10<span style="color: rgba(0, 0, 0, 1)">, endArrow: { path: endArrowPath, } } });<br> <strong><span style="color: rgba(51, 153, 102, 1)">//此处是我修改的,增加连线的样式即线上文本 </span></strong></span><strong><span style="color: rgba(255, 0, 0, 1)">if (cfg.label) { group.addShape('text', { attrs: { id: 'edgeText' + uniqueId(), x: end.x - (end.x - start.x) / 2, y: end.y - (end.y - start.y) / 2, text: cfg.label, fill: cfg.textColor ? cfg.textColor : '#000000' } }) } </span></strong><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> keyShape }, afterDraw(cfg, group) { </span><span style="color: rgba(0, 0, 255, 1)">if</span> (cfg.source.getModel().isDoingStart &&<span style="color: rgba(0, 0, 0, 1)"> cfg.target.getModel().isDoingEnd) { const shape </span>= group.get('children')[0<span style="color: rgba(0, 0, 0, 1)">]; const length </span>= shape.getTotalLength(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> G 增加了 totalLength 的接口</span> let totalArray =<span style="color: rgba(0, 0, 0, 1)"> []; </span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> i = 0; i < length; i +=<span style="color: rgba(0, 0, 0, 1)"> interval) { totalArray </span>=<span style="color: rgba(0, 0, 0, 1)"> totalArray.concat(lineDash); } let index </span>= 0<span style="color: rgba(0, 0, 0, 1)">; shape.animate({ onFrame() { const cfg </span>=<span style="color: rgba(0, 0, 0, 1)"> { lineDash: dashArray[index].concat(totalArray) }; index </span>= (index + 1) %<span style="color: rgba(0, 0, 0, 1)"> interval; </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> cfg; }, repeat: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)"> }, </span>3000<span style="color: rgba(0, 0, 0, 1)">); } }, setState(name, value, item) { const group </span>=<span style="color: rgba(0, 0, 0, 1)"> item.getContainer(); const shape </span>= group.get("children")[0<span style="color: rgba(0, 0, 0, 1)">]; const selectStyles </span>= () =><span style="color: rgba(0, 0, 0, 1)"> { shape.attr(</span>"stroke", "#6ab7ff"<span style="color: rgba(0, 0, 0, 1)">); }; const unSelectStyles </span>= () =><span style="color: rgba(0, 0, 0, 1)"> { shape.attr(</span>"stroke", "#b8c3ce"<span style="color: rgba(0, 0, 0, 1)">); }; </span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (name) { </span><span style="color: rgba(0, 0, 255, 1)">case</span> "selected"<span style="color: rgba(0, 0, 0, 1)">: </span><span style="color: rgba(0, 0, 255, 1)">case</span> "hover"<span style="color: rgba(0, 0, 0, 1)">: </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (value) { selectStyles() } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> { unSelectStyles() } </span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">; } } }); G6.registerEdge(</span>'link-edge'<span style="color: rgba(0, 0, 0, 1)">, { draw(cfg, group) { let sourceNode, targetNode, start, end </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">cfg.source.x) { sourceNode </span>=<span style="color: rgba(0, 0, 0, 1)"> cfg.source.getModel() start </span>= { x: sourceNode.x + cfg.start.x, y: sourceNode.y +<span style="color: rgba(0, 0, 0, 1)"> cfg.start.y } } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> { start </span>=<span style="color: rgba(0, 0, 0, 1)"> cfg.source } </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">cfg.target.x) { targetNode </span>=<span style="color: rgba(0, 0, 0, 1)"> cfg.target.getModel() end </span>= { x: targetNode.x + cfg.end.x, y: targetNode.y +<span style="color: rgba(0, 0, 0, 1)"> cfg.end.y } } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> { end </span>=<span style="color: rgba(0, 0, 0, 1)"> cfg.target } let path </span>=<span style="color: rgba(0, 0, 0, 1)"> [] path </span>=<span style="color: rgba(0, 0, 0, 1)"> [ [</span>'M'<span style="color: rgba(0, 0, 0, 1)">, start.x, start.y], [</span>'L'<span style="color: rgba(0, 0, 0, 1)">, end.x, end.y] ] const keyShape </span>= group.addShape('path'<span style="color: rgba(0, 0, 0, 1)">, { attrs: { id: </span>'edge' +<span style="color: rgba(0, 0, 0, 1)"> uniqueId(), path: path, stroke: </span>'#1890FF'<span style="color: rgba(0, 0, 0, 1)">, strokeOpacity: </span>0.9<span style="color: rgba(0, 0, 0, 1)">, lineDash: [</span>5, 5<span style="color: rgba(0, 0, 0, 1)">] } }); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> keyShape }, });
}
}export default customEdge
三、发现的 BUG
当删除文本框中的内容时,会发现连节点也删除了,解决办法就是修改 src/behavior/keyboard.js
作者:不若为止
欢迎任何形式的转载,但请务必注明出处。
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。