antV G6流程图在Vue中的使用

原文首发于我的博客,欢迎点击查看获得更好的阅读体验~

更新内容

最近我司项目中需要加入流程图制作功能,于是乎百度各种找可视化绘制拓扑图的轮子,大部分都是国外的,看文档太吃力,不过好在最终让我发现了AntV G6流程图图表库,最新版为2.0,不过编辑器在2.0版本还没有进行开源,所以只能退而求其次,使用了1.2.8版本。希望2.0版本的编辑器尽早开源,在交互方面1.2.8版本还是差了一些。

该组件并非开箱即食,需要根据自己的业务进行修改,右侧属性表单部分如果有时间考虑改为插槽形式,方便以后复用~

技术栈

效果图

工作流图.gif

引入

index.html中进行了全局引用


<script src="./static/plugin/g6.min.js"></script>

实例代码

仿照2.0版本的编辑器将G6作为了一个组件使用,代码:

<template>
  <div id="flowChart">
    <div class="operating">
      <div class="btn-group">
        <div class="btn" @click="addCircle" title="起始节点">
          <i class="iconfont icon-circle-oeps"></i>
        </div>
        <div class="btn" @click="addRect" title="常规节点">
          <i class="iconfont icon-square-oeps"></i>
        </div>
        <div class="btn" @click="addRhombus" title="条件节点">
          <i class="iconfont icon-square-ling"></i>
        </div>
      </div>
      <div class="btn-group">
        <div class="btn" @click="addLine" title="直线">
          <i class="iconfont icon-zhixian"></i>
        </div>
        <div class="btn" @click="addSmooth" title="曲线">
          <i class="iconfont icon-quxian"></i>
        </div>
        <div class="btn" @click="addArrowLine" title="箭头直线">
          <i class="iconfont icon-jiantouzhixian"></i>
        </div>
        <div class="btn" @click="addArrowSmooth" title="箭头曲线">
          <i class="iconfont icon-jiantouquxian"></i>
        </div>
      </div>
      <div class="btn-group">
        <div class="btn" @click="changeMode('edit')" title="选择模式">
          <i class="iconfont icon-chose"></i>
        </div>
        <div class="btn" @click="changeMode('drag')" title="拖拽模式">
          <i class="iconfont icon-move"></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="func" size="mini" filterable placeholder="绑定功能" value="">
              <el-option
                v-for="item in funcList"
                :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-select v-model="account" size="mini" filterable multiple
                       collapse-tags placeholder="绑定账号" value="">
              <el-option
                v-for="item in accountList"
                :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-select v-model="workflow" size="mini" filterable clearable placeholder="绑定流图" value="">
              <el-option
                v-for="item in workflowList"
                :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-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>

  export default {
    name: "index",
    components: {},
    mounted() {
      this.initG6();
    },
    props: {
      actionList: {
        type: Array, default: []
      },
      funcList: {
        type: Array, default: []
      },
      accountList: {
        type: Array, default: []
      },
      workflowList: {
        type: Array, default: []
      },
      nodeTypeList: {
        type: Array, default: () => {
          return [
            {id: 0, label: '普通节点'},
            {id: 1, label: '入口节点'},
            {id: 2, label: '出口节点'}
          ]
        }
      }
    },
    data() {
      return {
        action: '',
        name: '',
        func: '',
        account: '',
        workflow: '',
        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.tooltip({
          title: '信息', // @type {String} 标题
          split: ':',  // @type {String} 分割符号
          dx: 0,       // @type {Number} 水平偏移
          dy: 0        // @type {Number} 竖直偏移
        });*/

        /**
         *点击空白处
         */
        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.func = ev.item.get('model').func;
            self.account = ev.item.get('model').account || [];
            self.workflow = ev.item.get('model').workflow;
            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.node().tooltip(['label', 'func', 'role', 'color']);
        self.net.edge().tooltip(['label', 'color']);*/
        /**
         * 渲染
         */
        /*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,
            func: this.func,
            account: this.account,
            workflow: this.workflow,
            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()
      },
      func: function () {
        this.update()
      },
      account: function () {
        this.update()
      },
      workflow: 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 rel="stylesheet/scss" lang="scss" scoped>
  #flowChart {
    border: 1px solid #ebeef5;
    position: relative;
    overflow: hidden;
  }

  .operating {
    position: absolute;
    z-index: 99;
    background-color: #ffffff;
    padding: 20px 10px;
    box-shadow: 1px 1px 4px 0 #0a0a0a2e;
  }

  .info {
    position: absolute;
    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: 200px;
      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>

流图属性

参数 说明 类型 可选值 默认值
actionList 动作数据 Array —— []
funcList 功能数据 Array —— []
accountList 账号数据 Array —— []
workflowList 流图数据 Array —— []
nodeTypeList 节点类型数据 Array —— [{id: 0, label: '普通节点'},{id: 1, label: '入口节点'},{id: 2, label: '出口节点'}]

所有属性接收的数据格式需要与nodeTypeList的默认值相同

流图事件

事件名 说明 参数
saveData 当用户手动点击保存触发事件 source,type

参数type可为空,在此项目中主要用来区分新建编辑

流图方法

方法名 说明 参数
clearView 清空当前视图 ——
source 渲染数据 nodes, edges, name, type

参数type与事件中相同,参数name的作用是用来取流图名

参考文档

使用 G6关系图类库 开发流程图工具

旧版本G6 1.x API 文档

新版本G6 2.x API 文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342