Bootstrap

antv-g6—在vue项目中实现网格拓扑流程图自定义绘制

实现效果图

在这里插入图片描述

这个是自己写着玩的,利用@antv/g6自定义绘制流程图,然后保存到localstorage中,在左侧表格展示,还可以通过表格操作来查看对应的流程图以及删除;

这里特别注意一下,@antv/g6版本是1.2.8,vue版本是2.5.x;

下面我会把实现代码全部粘贴出来,不需要需改,开袋即食,放到你的项目中直接可以展示;

代码中我也会加上注释,不懂的可以看注释;

流程图组件

flow.vue

<template>
    <div id="flowChart">
      <!-- 头部工具栏 -->
      <div class="operating">
        <div class="btn-group">
          <div class="btn" @click="addCircle" title="开始节点">
            <i class="iconfont icon-weixuanzhongyuanquan"></i>
          </div>
          <div class="btn" @click="addRect" title="普通节点">
            <i class="iconfont icon-gl-square"></i>
          </div>
          <div class="btn" @click="addRhombus" title="条件节点">
            <i class="iconfont icon-tubiao"></i>
          </div>
        </div>
        <div class="btn-group">
          <div class="btn" @click="addLine" title="直线">
            <i class="iconfont icon-line"></i>
          </div>
          <div class="btn" @click="addSmooth" title="曲线">
            <i class="iconfont icon-byangtiaoquxian"></i>
          </div>
          <div class="btn" @click="addArrowLine" title="箭头直线">
            <i class="iconfont icon-gl-arrowRd"></i>
          </div>
          <div class="btn" @click="addArrowSmooth" title="箭头曲线">
            <i class="iconfont icon-a-18"></i>
          </div>
        </div>
        <div class="btn-group">
          <div class="btn" @click="changeMode('edit')" title="选择模式">
            <i class="iconfont icon-xuanze"></i>
          </div>
          <div class="btn" @click="changeMode('drag')" title="拖拽模式">
            <i class="iconfont icon-tuozhuai"></i>
          </div>
        </div>
        <div class="btn-group">
          <div class="btn" @click="del" style="margin-top: 5px;" title="删除">
            <i class="el-icon-delete"></i>
          </div>
          <div class="btn" @click="save" title="保存">
            <i class="iconfont icon-baocun"></i>
          </div>
        </div>
        <div class="btn-group">
          <el-input size="mini" v-model="workflowName" placeholder="请输入流图名称..."></el-input>
        </div>
      </div>
      <!-- 右侧节点属性设置 -->
      <div class="info">
        <div class="title">
          <span>{{infoTitle}}属性</span>
        </div>
        <div class="content">
          <el-checkbox v-if="isBlank === true" v-model="checked">网格对齐</el-checkbox>
          <el-form v-else label-position="left" label-width="60px">
            <el-form-item v-if="isNode !== true" label="动作">
              <el-select v-model="action" size="mini" filterable placeholder="绑定动作" value="">
                <el-option
                  v-for="item in actionList"
                  :key="item.id"
                  :label="item.label"
                  :value="item.id">
                </el-option>
              </el-select>
            </el-form-item>   <!-- 线-->
            <el-form-item v-if="isNode === true" label="名称">
              <el-input size="mini" v-model="name"></el-input>
            </el-form-item>
            <!-- <el-form-item v-if="isNode === true" label="类型">
              <el-select v-model="nodeType" size="mini" filterable placeholder="请选择类型" value="">
                <el-option
                  v-for="item in nodeTypeList"
                  :key="item.id"
                  :label="item.label"
                  :value="item.id">
                </el-option>
              </el-select>
            </el-form-item> -->
            <el-form-item label="颜色">
              <el-color-picker v-model="color"></el-color-picker>
            </el-form-item>
          </el-form>
        </div>
      </div>
    </div>
  </template>
   
  <script>
   import G6 from '@antv/g6';
    export default {
      mounted() {
        this.initG6();
      },
      props: {
        actionList: {
          type: Array, default: []
        },
        nodeTypeList: {
          type: Array, default: () => {
            return [
              {id: '001', label: '普通节点'},
              {id: '002', label: '开始节点'},
            ]
          }
        }
      },
      data() {
        return {
          action: '',
          name: '',
          nodeType: 0,
          color: '',
          net: '',
          Util: '',
          workflowName: '',
          activation: '', //当前激活的节点
          isNode: false, //当前是节点
          isBlank: true,   //当前是空白区
          checked: true,  //网格对齐
          infoTitle: '画布',//属性标题
          oldColor: '',    //获取节点本身颜色
          type: '',        //有值为编辑状态
        }
      },
      methods: {
        //初始化
        initG6() {
          let self = this;
          self.Util = G6.Util;
          let grid;
          if (self.checked) {
            grid = {
              forceAlign: true, // 是否支持网格对齐
              cell: 25,         // 网格大小
            };
          } else {
            grid = null;
          }
          self.net = new G6.Net({
            id: 'flowChart',      // 容器ID
            mode: 'edit',
            grid: grid,
            /*width: 500,    // 画布宽*/
            height: 800    // 画布高
          });
   
          /**
           *点击空白处
           */
          self.net.on('click', (ev) => {
            if (!self.Util.isNull(ev.item)) {
              self.isBlank = false
            } else {
              self.isBlank = true;
              self.infoTitle = '画布'
            }
          });
          /**
           *点击节点
           */
          self.net.on('itemclick', function (ev) {
            self.isNode = self.Util.isNode(ev.item);   //是否为Node
            self.activation = ev.item;
            if (self.isNode) {
              /* 激活节点后节点名称input聚焦*/
              self.$nextTick(()=>{
                self.$refs.inputFocus.$el.querySelector('input').focus();
              });
              self.infoTitle = '节点';
              self.name = ev.item.get('model').label;
              self.nodeType = ev.item.get('model').nodeType;
            } else {
              self.infoTitle = '边';
              self.action = ev.item.get('model').action;
            }
            self.color = self.oldColor;
          });
          /**
           * 鼠标移入移出事件改变颜色
           */
          self.net.on('itemmouseenter', ev => {
            const item = ev.item;
            self.oldColor = item.get('model').color;     //获取节点颜色
            self.net.update(item, {
              color: '#108EE9',
            });
            self.net.refresh();
          });
          self.net.on('itemmouseleave', ev => {
            const item = ev.item;
            self.net.update(item, {
              color: self.oldColor
            });
            self.net.refresh();
          });
          /*self.net.source(self.nodes, self.edges);*/  //加载资源数据
          self.net.render();
        },
        //添加起始节点
        addCircle() {
          this.net.beginAdd('node', {
            shape: 'circle',
            nodeType: 0
          })
        },
        //添加常规节点
        addRect() {
          this.net.beginAdd('node', {
            shape: 'rect',
            nodeType: 0
          })
        },
        //添加条件节点
        addRhombus() {
          this.net.beginAdd('node', {
            shape: 'rhombus',
            nodeType: 0
          })
        }, 
        //添加直线
        addLine() {
          this.net.beginAdd('edge', {
            shape: 'line'
          });
        }, 
        //添加曲线
        addSmooth() {
          this.net.beginAdd('edge', {
            shape: 'smooth'
          })
        }, 
        //添加箭头曲线
        addArrowSmooth() {
          this.net.beginAdd('edge', {
            shape: 'smoothArrow'
          })
        }, 
        //添加箭头直线
        addArrowLine() {
          this.net.beginAdd('edge', {
            shape: 'arrow'
          });
        }, 
        //添加折线
        addPolyLine() {
          this.net.beginAdd('edge', {
            shape: 'polyLineFlow'
          });
        }, 
        //拖拽与编辑模式的切换
        changeMode(mode) {
          this.net.changeMode(mode)
        }, 
        //删除节点
        del() {
          this.net.del()
        },
        //保存流程图
        save() {
          /* 验证流图名称*/
          if (this.workflowName !== '') {
            let data = this.net.save();
            if (data.source.nodes.length === 0) {
              this.$message({type: 'error', message: '流图内容不能为空'});
              return false
            }
            /* 验证节点名称*/
            for (let item of data.source.nodes) {
              if (item.label === '' || item.label === null || item.label === undefined) {
                this.$message({type: 'error', message: '节点名称不能为空'});
                return false
              }
            }
            data.source['name'] = this.workflowName;
            /*let json = JSON.stringify(data, null, 2);*/
            this.$emit('saveData', data.source, this.type);
          } else {
            this.$message({type: 'error', message: '流图名称不能为空'})
          }
          /*console.log(saveData, json);*/
        },
        //更新节点
        update() {
          if (this.activation.get('type') === 'node') {
            this.net.update(this.activation, {
              label: this.name,
              nodeType: this.nodeType,
              color: this.color
            });
          } else {
            /* 根据ID取出label*/
            let label = this.actionList.map(item => {
              if (item.id === this.action) {
                return item.label
              }
            }).join('');
            this.net.update(this.activation, {
              label: label,
              color: this.color,
              action: this.action
            });
          }
        },
        //清空视图,重置画布
        clearView() {
          this.type = '';
          this.workflowName = '';
          this.net.changeData()
        },
        //渲染流程数据
        source(nodes, edges, name, type) {
          this.type = type;
          this.workflowName = name;
          this.net.changeData(nodes, edges)
        },  
      },
      watch: {
        /**
         * 监听输入框
         */
        action: function () {
          this.update()
        },
        name: function () {
          this.update()
        },
        nodeType: function () {
          this.update()
        },
        color: function () {
          this.update()
        },
        /**
         * 网格切换
         */
        checked: function () {
          let _saveData = this.net.save();
          this.net.destroy();  //销毁画布
          this.initG6();
          this.net.read(_saveData);
          this.net.render()
        }
      }
    }
  </script>
   
  <style  lang="less" scoped>
    #flowChart {
      border: 1px solid #cdcdcd;
      border-radius: 5px;
      position: relative;
      overflow: hidden;
      width: 80%;
      box-sizing: border-box;
      height: 100%;
    }
   
    .operating {
      position: absolute;
      z-index: 99;
      background-color: #ffffff;
      padding: 20px 10px;
      box-shadow: 1px 1px 4px 0 #0a0a0a2e;
    }
   
    .info {
      position: absolute;
      height: 100%;
      right: 0;
      z-index: 99;
      box-shadow: 1px 1px 4px 0 #0a0a0a2e;
      .title {
        height: 40px;
        padding-left: 10px;
        border-top: 1px solid #DCE3E8;
        border-bottom: 1px solid #DCE3E8;
        border-left: 1px solid #DCE3E8;
        background: rgb(235, 238, 242);
        line-height: 40px;
        span {
          font-size: 14px;
        }
      }
      .content {
        background: rgba(247, 249, 251, 0.45);
        width: 220px;
        height: 800px;
        border-left: 1px solid #E6E9ED;
        padding: 10px;
      }
    }
   
    .btn-group {
      border-right: 1px solid #efefef;
      display: inline-block;
      padding-left: 10px;
      padding-right: 14px;
      &:last-of-type {
        border-right: 0;
      }
      .btn {
        display: inline-block;
        margin: 2px;
        width: 30px;
        height: 30px;
        line-height: 30px;
        text-align: center;
        cursor: pointer;
        border: 1px solid rgba(233, 233, 233, 0);
        i {
          font-size: 20px;
        }
        &:hover {
          border: 1px solid #E9E9E9;
          color: #767A85;
          border-radius: 2px;
          background: #FAFAFE;
        }
      }
      .el-form-item {
        margin-bottom: 0 !important;
      }
    }
  </style>

使用流程组件

<template>
    <div class="flow_content">
      
      <div class="table_content">
         <el-button size="small" type="primary" style="margin: 10px 0;" @click="newAdd">新建流程</el-button>
        . <el-table
            :data="tableData"
            border
            highlight-current-row=true
            style="width: 100%">
            <el-table-column
              prop="name"
              align="center"
              label="名称"
              >
            </el-table-column>
            <el-table-column
              label="操作"
              align="center"
              width="100">
              <template slot-scope="scope">
                <el-button type="text" size="small"
                @click.native.prevent="viewFlow(scope.row)">
                  查看
                </el-button>
                <el-button type="text" size="small"
                @click.native.prevent="deleteRow(scope.$index, scope.row)">
                  移除
                </el-button>
               
              </template>
            </el-table-column>
          </el-table>
      </div>
      <flowChart
      ref="flow"
      :actionList="actionList"
       @saveData="saveData"
      ></flowChart>
    </div>
  </template>
   
  <script>
    import flowChart from './components/flow.vue'
    import { v4 as uuidv4 } from "uuid";

    export default {
      data(){
        return {
          actionList:[
            {id:'001',label:'拒绝'},
            {id:'002',label:'通过'},
            {id:'003',label:'下发'}
          ],
          tableData:[],
          clickName:"",//当前点击渲染的流程图

        }
      },
     components:{
      flowChart
     },
     mounted(){
      //初始化获取列表数据,如果有数据,画布就展示第一个流程,如果没有数据就为空集合
      var tables = localStorage.getItem('flowTable')
      if(tables){
        this.tableData = JSON.parse(tables)
        const {nodes, edges, name, type} = this.tableData[0]
        this.$refs.flow.source(nodes, edges, name, type)
      }else{
        this.tableData = []
      }
      
     },
     methods:{
      
      saveData(source,type){
        var isHave = false
        var indexNum 
        var filterTableData = this.tableData.filter( item => {
            return item.type !== type
        })
        filterTableData.forEach( (item,index) => {
            if(item.name == source.name){
              isHave = true
              indexNum = index
            }
        })
        if(type){
            //type有值,编辑
            if(isHave){
              //编辑的名称已存在
                this.$message({
                  message: '该名称已存在!',
                  type: 'warning'
                });
            }else{
              var obj = source
              obj.type = type
              this.tableData.splice(indexNum, 1,obj);
                this.$message({
                  message: '保存成功!',
                  type: 'success'
                });
            }
        }else{
          if(!isHave){
            //type无值,新建
            let uid = uuidv4()
            var obj = source
            obj.type = uid
            this.tableData.push(source)
            this.$message({
              message: '保存成功!',
              type: 'success'
            });
          }else{
            this.$message({
              message: '该名称已存在!',
              type: 'warning'
            });
          }
         
        }
        var tables = JSON.stringify(this.tableData)
        localStorage.setItem('flowTable',tables)
      },
      //点击查看流程图
      viewFlow(row){
         this.clickName = row.name
         this.$refs.flow.clearView()
         const {nodes, edges, name, type} = row
        this.$refs.flow.source(nodes, edges, name, type)
      },
       //新建流程图,清空画布
      newAdd(){
        this.$refs.flow.clearView()
      },
      //删除单个列表并清空当前删除的画布
      deleteRow(index, row) {
        var leng = this.tableData.length
        if(this.clickName == row.name || index == leng -1 || leng == 0){
          this.$refs.flow.clearView()
        }
        this.tableData.splice(index, 1);
        var tables = JSON.stringify(this.tableData)
        localStorage.setItem('flowTable',tables)
      }
     }
    }
  </script>
   
  <style  lang="less" scoped>
   .flow_content{
      height: 100%;
      display: flex;
      justify-content: space-around;
      .table_content{
        width: 19%;
        box-sizing: border-box;
        border: 1px solid #cdcdcd;
        border-radius: 5px;
        padding: 0 10px;
      }
   }
  </style>

以上就是上面效果图实现的全部代码,没有什么技术含量,就是简单的记录下来,分享给有需要的人;

;