前言:需求是要做一个oa系统的组织权限架构流程图,支持点击添加串行&并行节点,图要看上去直观,故找到dagre-d3这个库比较适合也好看,以下是我前期调研写的demo;
1.首先安装依赖
import dagreD3 from "dagre-d3";
import * as d3 from "d3";
2.源码说明(复制到vue项目即可运行,我是用了ts)
<template>
<div class="flow-chart">
<svg
width="2400"
height="1600"
>
<g />
<rect />
</svg>
<div class="btn-position">
<button @click="isStrand = 1">串行</button>
<button @click="isStrand = 2">并行</button>
</div>
</div>
</template>
<script>
import dagreD3 from "dagre-d3";
import * as d3 from "d3";
// rect,circle,ellipse,diamond,默认值为rect
export default {
data () {
return {
isStrand: 1, // 1为串行 2位并行
list: {
nodeInfos: [
{
id: "node1",
label: "节点1",
shape: "circle",
},
{
id: "node2",
label: "节点2",
},
{
id: "node3",
label: "节点3",
rank: 2
},
{
id: "node4",
label: "节点4",
// rank: 2,
// shape: "ellipse",
// class:‘empty‘
},
{
id: "node5",
label: "节点5",
},
{
id: "node6",
label: "节点6",
},
{
id: "node7",
label: "节点7",
},
{
id: "node8",
label: "节点8",
},
{
id: "node9",
label: "节点9",
shape: "circle",
},
],
edges: [
{
source: "node1",
target: "node2",
},
{
source: "node2",
target: "node3",
},
{
source: "node3",
target: "node4",
},
{
source: "node4",
target: "node5",
},
{
source: "node5",
target: "node6",
},
{
source: "node6",
target: "node7",
},
{
source: "node7",
target: "node8",
},
{
source: "node8",
target: "node9",
}
]
},
nextNode: ‘‘,
gGraph: new dagreD3.graphlib.Graph().setGraph({
rankdir: ‘LR‘, //默认‘TB‘
// align: ‘DL‘,
nodesep: 40,
edgesep: 80,
ranksep: 60,
marginx: 140,
marginy: 140,
// ranker: ‘tight-tree‘
})
};
},
methods: {
// 删除节点
removeNode (item) {
this.gGraph.removeNode(item.id,);
},
setNodeFun () {
this.list.nodeInfos && this.list.nodeInfos.forEach((item, index) => {
item.rx = item.ry = 5;//圆角
console.log(item, ‘------‘)
if (item.class === ‘empty‘) {
console.log(88888)
this.gGraph.setNode(item.id, {
style: "stroke: #ccc; ;stroke-width:0.2px",
width: -19, //线条颜色
...item,
});
} else {
this.gGraph.setNode(item.id, {
// style: "stroke: #ccc; fill: #666;stroke-width:2px",
...item,
});
}
})
},
setEdgeFun () {
this.list.edges.forEach(item => {
this.gGraph.setEdge(item.source, item.target, {
style: "stroke: #ccc; fill: none;stroke-width:2px", //线条颜色
arrowheadStyle: "fill: #ccc;stroke: #ccc", //箭头颜色
arrowhead: ‘undirected‘, // normal,vee,undirected 三种样式
labelType: ‘‘,//可以设置文本以及 html 格式,默认为文本格式
// labelpos:‘r‘,
// labeloffset:‘80‘
});
});
},
renderFun () {
//绘制图形
var svgAb = d3.select("svg"),
innerAb = svgAb.select("g");
console.log(innerAb, ‘innerAb‘)
//缩放
// var zoom = d3.zoom().on("zoom", function () {
// inner.attr("transform", d3.event.transform);
// });
// svg.call(zoom);
var render = new dagreD3.render();
render(innerAb, this.gGraph);
console.log(innerAb, ‘inner111‘)
},
selectEvent () {
var svg = d3.select("svg"),
inner = svg.select("g");
let code;
inner.selectAll("g.node").on("mousedown", e => {
// e.preventDefault();
console.log(e,‘鼠标右键点击了‘)
})
inner.selectAll("g.node").on("click", e => {
console.log(e, ‘单击了‘)
// isStrand 1为串行 2位并行
if (this.isStrand == 1) {
//点击事件-串行
this.list.nodeInfos = this.list.nodeInfos.concat({
id: e + ‘11‘,
label: "节点" + e + ‘11‘,
})
this.list.edges = this.list.edges.map(q => {
if (q.source == e) {
console.log(q, ‘q------1‘)
this.nextNode = q.target
q.target = e + ‘11‘
}
return q
})
this.list.edges = this.list.edges && this.list.edges.concat({
source: e + ‘11‘,
target: this.nextNode,
})
console.log(this.list, ‘this.list‘)
localStorage.setItem(‘list‘, JSON.stringify(this.list))
window.location.reload()
} else {
// 并行
// 创建空节点
this.list.nodeInfos = this.list.nodeInfos.concat({
id: e + ‘0‘,
label: ‘‘,
shape: "ellipse",
class: ‘empty‘
})
this.list.nodeInfos = this.list.nodeInfos.concat({
id: e + ‘21‘,
label: "节点" + e + ‘21‘,
}, {
id: e + ‘22‘,
label: "节点" + e + ‘22‘,
})
this.list.edges = this.list.edges.map(q => {
if (q.source == e) {
console.log(q, ‘q------1‘)
this.nextNode = q.target
q.target = e + ‘21‘
}
return q
})
// 空节点
this.list.edges = this.list.edges && this.list.edges.concat({
source: e + ‘0‘,
target: this.nextNode,
})
// end
this.list.edges = this.list.edges && this.list.edges.concat({
source: e,
target: e + ‘22‘,
}, {
source: e + ‘21‘,
// target: this.nextNode,
target: e + ‘0‘,
}, {
source: e + ‘22‘,
// target: this.nextNode,
target: e + ‘0‘,
})
// console.log(this.list, ‘this.list‘)
localStorage.setItem(‘list‘, JSON.stringify(this.list))
window.location.reload()
}
code = this.list.nodeInfos.filter(item => {
return item.id == e;
});
console.log(code, ‘12212121‘);
this.setNodeFun()
this.setEdgeFun()
setTimeout(() => {
this.renderFun()
}, 1000)
});
},
// 缩放
scale () {
var initialScale = 0.75;
svg.call(
zoom.transform,
d3.zoomIdentity
.translate(
(svg.attr("width") - g.graph().width * initialScale) / 2,
20
)
.scale(initialScale)
);
svg.attr("height", g.graph().height * initialScale + 40);
},
rightEvent () {
var svgCanvas = document.getElementById(‘svg-canvas‘); //svg
var myMenu = document.getElementById("myMenu"); //右键菜单
svgCanvas.addEventListener(‘mouseover‘, function (e) {//监听鼠标右键
e.preventDefault();
if (e.target.tagName === ‘rect‘) {
myMenu.style.top = event.clientY + "px"; //获取鼠标位置
myMenu.style.left = event.clientX + "px";
myMenu.style.display = ‘block‘; //显示相应右键内容
}
})
document.addEventListener("click", (event) => {
myMenu.style.display = ‘none‘;
});
},
// 线条汇集合并
lineMerge () {
const arr = []
this.list.edges = this.list.edges.map(q => {
if (arr.includes(q.target)) {
this.list.nodeInfos = this.list.nodeInfos.map(j => {
console.log(j, ‘j==-=-=-=‘)
if (j.id == q.target) {
j.class = ‘empty‘
j.shape = "ellipse"
}
return j
})
}
arr.push(q.target)
return q
})
}
},
created () {
this.list = JSON.parse(localStorage.getItem(‘list‘)) || this.list
// this.lineMerge()
console.log(this.list, ‘this.list-get‘)
},
mounted () {
this.setNodeFun()
this.setEdgeFun()
this.renderFun()
this.selectEvent()
// g.nodes().forEach(function (v) {
// console.log("Node " + v + ": " + JSON.stringify(g.node(v)));
// });
// g.edges().forEach(function (e) {
// console.log("Edge " + e.v + " -> " + e.w + ": " + JSON.stringify(g.edge(e)));
// });
}
};
</script>
<style lang="less">
.flow-chart {
width: 2400px;
height: 800px;
border: solid 1px #666;
}
svg {
font-size: 14px;
}
.node rect {
stroke: #606266;
fill: #fff;
}
.edgePath path {
stroke: #606266;
fill: #333;
stroke-width: 1.5px;
}
.node circle {
stroke: #606266;
fill: #fff;
stroke-width: 0.5px;
}
.node ellipse {
fill: #606266;
opacity: 0.2;
stroke-width: 1px;
}
// .nodeNo{
// width: 3px!important;
// height: 3px!important;
// stroke-width: 0.5px;
// }
// .empty{
// width: 42px!important;
// height: 42px!important;
// stroke-width: 1.5px;
// fill: #606266;
// stroke: #606266;
// }
.btn-position {
position: fixed;
top: 20px;
left: 20px;
button {
margin-left: 16px;
}
}
</style>
3.效果图
a.串行效果图

b.并行效果图

4.注意
我把操作生成的流程图数据存在了缓存中,若想刷新初始化删掉缓存即可

还有一个重要问题,做出demo后还是不够美观,所以我改了dagre-d3库源码让线条链接更美观了(主要是让线条90度折线) ,修改node_modules中如下位置代码

添加红色框中的优化代码即可
// 节点连接线90度角优化 if(points.length > 0 ){ var point1 = points[0]; var point2 = points[1]; var point3 = points[2]; var stepX = point3.x - point1.x; var stepY = point3.y - point1.y; if(stepX > 0 && stepY > 0){ // point a to c && b to d if(point3.y - point2.y > 0){ point2.x = point3.x point2.y = point1.y } else { point2.x = point1.x point2.y = point3.y } }else if(stepX > 0 && stepY < 0){ // point a to b if(point3.y - point2.y == 0){ point2.x = point1.x point2.y = point3.y } else{ point2.x = point3.x point2.y = point1.y } } } // --end--
tips:还有不懂的可以加微信交流:844271163
原文:https://www.cnblogs.com/xiaohuizhang/p/14754630.html