"leader-line": "^1.0.7",
"anim-event": "^1.0.17",
具体的api和用法请查阅官方文档 leader-line
想要将流程图组价封装成一个组件,传递数据自动绘制出流程图,从图中我们可以看出这个结构很像一个树状结构,只不过是像右的树状结构,树节点如下图中画红线所示:
每一个框代表着一个树中的节点,
list: [
{
name: "scm-applicable-check1",
nickname: "触发预处理",
steps: ['check'],
children: [
{
name: "fetch-code1",
nickname: "拉取代码",
steps: ['clone', 'cache'],
children: [
{
name: "scan-code1",
nickname: "代码静态扫描",
steps: ['uncache', 'sonar-properties-create', 'sonar-scan', 'collect-result'],
children: [
{
name: "bake-image1",
nickname: "镜像制作(linux/AMD64)",
steps: ['uncache', 'build-and-push', 'write-url'],
},
{
name: "bake-image2",
nickname: "镜像制作(linux/AMD64)",
steps: ['uncache', 'build-and-push', 'write-url'],
},
]
}
]
},
]
}
],
那么怎么去画出这个结构呢,想到树状结构我们很容易想到递归组件,我们可以先写出每个树节点的布局
<template>
// 根元素
<div class="nimei">
// 循环生成多少个流程图
<div class="yaer" v-for="item in list" :key="item.name">
// 树节点---start
<span class="item">
<ul :id="item.name">
<li v-for="(s) in item.steps" :key="s">
<div class="step-item">
<gs-popover>
<ul>
<li>查看日志</li>
<li>重新执行</li>
<li>取消执行</li>
</ul>
<div style="padding: 0 10px" slot='reference' :id="item.name+'-'+s">
<span class="circle" ></span>
</div>
</gs-popover>
<span class="text">{{s}}</span>
</div>
</li>
</ul>
<div class="item-title">
<div>{{item.nickname}}</div>
<div>{{item.name}}</div>
</div>
</span>
// 树节点end
// 递归子子节点
<haha v-if="item.children" :list="item.children"/>
</div>
</div>
</template>
结构和样式写好后,通过leader-line将树节点用线连接起来
// 组件是递归的,会先执行最深层结构
drawLeaderLine() {
this.list.forEach(item => {
let options = {
path: 'straight',
startPlug: 'disc',
color: 'rgb(51, 51, 51, 0.8)',
size: 2,
};
// 当前节点为起始元素
let start = document.getElementById(item.name);
// 判断当前节点是否存在子节点,如果存在
if(item.children){
// 遍历子节点
item.children.forEach(sItem => {
// 将当前节点和子节点用线连接起来
let line = new LeaderLine(start, document.getElementById(sItem.name));
line.setOptions({
...options,
path: 'grid',
});
// 将线存起来
this.lines.push(line);
// 渲染步骤
if(sItem.steps){
// 遍历步骤
for (let step = 0; step < sItem.steps.length; step++) {
let start = document.getElementById(sItem.name + '-' + sItem.steps[step]);
let end = document.getElementById(sItem.name + '-' + sItem.steps[step + 1]);
if (!end) {
break;
}
// 步骤内部划线
let line = new LeaderLine(start, end);
line.setOptions({
startPlug: 'disc',
color: 'rgb(51, 51, 51, 0.8)',
path: 'straight',
size: 2,
});
this.lines.push(line);
}
}
})
}
})
}
以上,流程图算是画好了,但是会存在一个问题,就是滚动页面的时候,线不会跟着滚动,dom元素的位置更新了,但是线的位置还在原处
解决办法,leader-line 的官方文档中也有相应的处理api
// 给容器添加点击事件
var listener = AnimEvent.add(() => {
console.log(123)
this.drawPosition();
});
document.getElementById('hahashabi').addEventListener('scroll', listener, false);
// 更细所以线段的位置的方法
drawPosition() {
if (this.lines && this.lines.length) {
this.lines.forEach(line => {
line.position();
});
}
},
AnimEvent 在官方文档中也有提到
<template>
<div class="nimei">
<div class="yaer" v-for="item in list" :key="item.name">
<!-- 树节点---start -->
<span class="item">
<ul :id="item.name">
<li v-for="(s) in item.steps" :key="s">
<div class="step-item">
<gs-popover>
<ul>
<li>查看日志</li>
<li>重新执行</li>
<li>取消执行</li>
</ul>
<div style="padding: 0 10px" slot='reference' :id="item.name+'-'+s">
<span class="circle" ></span>
</div>
</gs-popover>
<span class="text">{{s}}</span>
</div>
</li>
</ul>
<div class="item-title">
<div>{{item.nickname}}</div>
<div>{{item.name}}</div>
</div>
</span>
<!-- 树节点---end -->
<haha v-if="item.children" :list="item.children"/>
</div>
</div>
</template>
<script>
import LeaderLine from 'leader-line';
import AnimEvent from 'anim-event';
export default {
name: 'haha',
props: {
list: {
type: Array,
default: () => {
return []
}
}
},
data () {
return {
lines: [],
};
},
mounted() {
this.drawLeaderLine();
var listener = AnimEvent.add(() => {
console.log(123)
this.drawPosition();
});
document.getElementById('hahashabi').addEventListener('scroll', listener, false);
},
methods: {
drawPosition() {
if (this.lines && this.lines.length) {
this.lines.forEach(line => {
line.position();
});
}
},
drawLeaderLine() {
this.list.forEach(item => {
let options = {
path: 'straight',
startPlug: 'disc',
color: 'rgb(51, 51, 51, 0.8)',
size: 2,
};
let start = document.getElementById(item.name);
if(item.children){
item.children.forEach(sItem => {
let line = new LeaderLine(start, document.getElementById(sItem.name));
line.setOptions({
...options,
path: 'grid',
});
this.lines.push(line);
if(sItem.steps){
for (let step = 0; step < sItem.steps.length; step++) {
let start = document.getElementById(sItem.name + '-' + sItem.steps[step]);
let end = document.getElementById(sItem.name + '-' + sItem.steps[step + 1]);
if (!end) {
break;
}
let line = new LeaderLine(start, end);
line.setOptions({
startPlug: 'disc',
color: 'rgb(51, 51, 51, 0.8)',
path: 'straight',
size: 2,
});
this.lines.push(line);
}
}
})
}
})
}
}
};
</script>
<style lang="scss" scoped>
.yaer{
display: flex;
align-items: center;
.item-title{
text-align: center;
}
.item{
margin-right: 60px;
margin-top:10px;
ul{
border: 1px dashed #cdcdcd;
padding: 30px;
border-radius: 3px;
display: flex;
justify-content: space-evenly;
li{
margin-right: 90px;
&:last-child{
margin-right: 0;
}
.step-item{
display: flex;
flex-direction: column;
align-items: center;
position: relative;
.circle{
display: inline-block;
width: 20px;
height: 20px;
border-radius: 10px;
border: 1px solid;
}
.text{
position: absolute;
top:20px;
width:150px;
text-align: center;
}
}
}
}
}
}
</style>