vue基于leader-line绘制流水线流程图

这里写目录标题

  • 实现的效果
  • 依赖版本
  • leader-line
  • 流程图分解和数据结构分析
    • 流程图分解
    • 对应数据结构
    • 完整代码

实现的效果

vue基于leader-line绘制流水线流程图_第1张图片

依赖版本

 "leader-line": "^1.0.7",
 "anim-event": "^1.0.17",

leader-line

具体的api和用法请查阅官方文档 leader-line

流程图分解和数据结构分析

流程图分解

想要将流程图组价封装成一个组件,传递数据自动绘制出流程图,从图中我们可以看出这个结构很像一个树状结构,只不过是像右的树状结构,树节点如下图中画红线所示:
vue基于leader-line绘制流水线流程图_第2张图片
每一个框代表着一个树中的节点,

对应数据结构

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'],
                },
              ]
            }
          ]
        },
      ]
    }
  ],

steps 表示每个树节点中的步骤数
vue基于leader-line绘制流水线流程图_第3张图片

那么怎么去画出这个结构呢,想到树状结构我们很容易想到递归组件,我们可以先写出每个树节点的布局

<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元素的位置更新了,但是线的位置还在原处
vue基于leader-line绘制流水线流程图_第4张图片
解决办法,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>

你可能感兴趣的:(javascript,vue.js)