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

news2024/11/18 9:24:26

实现效果图

在这里插入图片描述

这个是自己写着玩的,利用@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>

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/694862.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ModaHub魔搭社区:向量数据库MIlvus服务端配置(二)

目录 storage 区域 wal 区域 cache 区域 storage 区域 | 参数 | 说明 | 类型 | 默认值 | | ——————————————- | —————————————————————————————— | ——————————————————————————————- | ————…

解决 mac 系统报zsh: command not found: npm 问题

文章目录 1、报错zsh: command not found: npm2、解决办法 1、报错zsh: command not found: npm 根据提示&#xff1a;zsh: command not found: npm。说明没有找到 npm 命令&#xff0c;这说明有两种情况&#xff1a; 一是&#xff1a;你根本就没有安装 nodejs 的环境&#xf…

git add 时报错 warning: in the working copy of ‘package-lock.json‘, LF will...

问题&#xff1a; 执行git add . 时报错 原因&#xff1a; 换行符的问题&#xff0c; Windows下换行符和Unix下的换行符不一样&#xff0c;git会自动转换。 解决办法&#xff1a; 执行命令&#xff1a; git config --global core.autocrlf false 问题解决&#xff1a;

Windows网络服务综测刷题

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 前言 本章将会讲解Windows网络服务的刷题 一.刷题 在Windows Server 2016系统中&#xf…

【openpyxl】总结最近使用到的openpyxl方法

这里写自定义目录标题 单元格嵌入图片调节行高和列宽合并单元格自动换行按列插入数据tableWidget数据导出到excel 单元格嵌入图片 from openpyxl.drawing.image import Image from openpyxl.drawing.spreadsheet_drawing import AnchorMarker, TwoCellAnchor from openpyxl.wor…

Excel / PPT 办公技巧

Excel sum求和 我们先选中某个单元格单元格&#xff0c;并在编辑栏输入&#xff1a;SUM&#xff08;&#xff09;&#xff0c;然后输入函数参数&#xff1a;B2&#xff1a;B9&#xff0c;最后按回车键结束确认&#xff0c;即可计算出销量总和&#xff1b; PPT 组合 选中需要…

2023-01-09 DBeaver链接postgresql.md

DBeaver连接postgresql 驱动不存在 Maven artifact maven:/net.postgis:postgis-jdbc:RELEASE not found 解决方法&#xff1a;窗口--->首选项--->驱动--->Maven http://maven.aliyun.com/nexus/content/groups/public/

【ORA-01745: invalid host/bind variable name】

mybatis插入sql错误&#xff0c;一开始没注意他的sql 根据错误信息 ORA-01745: 无效的主机/绑定变量名一查 全是 什么批量插入数据过多导致的 实际上我这个不是

LeetCode刷题 | 70. 爬楼梯、322. 零钱兑换、279. 完全平方数

70. 爬楼梯 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1. 1 阶 1 阶 2. 2 阶 示…

怎么用云猫转码工具在线转换视频格式

我们平时在剪辑视频的时候&#xff0c;经常需要处理各种各样的视频格式&#xff0c;大家平时可以通过将不同的视频格式进行转换&#xff0c;就可以获得到更多的视频格式类型&#xff0c;那么有什么好用的工具可以快速转换视频格式的么&#xff1f; 推荐使用云猫转码工具在线转…

八大排序算法——(万字图文详解)

本篇文章是我对之前写过的八个排序算法的总结&#xff0c;感兴趣的小伙伴可以去我的八大排序算法专栏浏览&#xff0c;也可以点击下方标题跳转。 提示&#xff1a;本篇博客篇幅较长&#xff0c;建议小伙伴们查看目录&#xff0c;按需浏览 目录 正文 1 直接插入排序 2 希尔…

JAVA关于异常的处理

1.throw和throws public static int score(int math,int chinese) throws Exception {if(math < 0 || chinese < 0){throw new Exception();}return (mathchinese)/2;}从这里看&#xff0c; throw是在方法体中处理异常的&#xff0c;抛出&#xff0c;这个时候&#xff0…

centos7 解决 IP自动变化的问题

进入&#xff1a;cd /etc/sysconfig/network-scripts/ 找到&#xff1a;ircfg-ens33 文件 vi 打开 BOOTPROTO“static” IPADDR192.168.98.130 //设置的本机ip&#xff0c;需和网关在同一网段 192.168.234.xxx NETMASK255.255.255.0 子网掩码 设置好后 :wq systemctl restart…

Linux下Redis 6.2.6安装和部署详细图文步骤

目录 1、下载redis6.2.6安装包2、解压压缩文件3、重命名和移动文件4、redis编译安装1&#xff09;redis编译&#xff0c;执行make命令。2&#xff09;redis安装执行make install命令。 5、redis启动6、设置后台启动redis7、设置redis密码8、设置redis服务远程访问1、防火墙添加…

windows10关闭WPS广告及清理备份与设置升级

1、清理备份 备份开启可以预防断电等情况意外关闭文档&#xff0c;导致内容丢失&#xff0c;建议开启备份 2、WPS广告&#xff0c;恶意已久&#xff0c;不解释 3、路径 C:\Users\UserHostname\AppData\Local\Kingsoft\WPS Office\11.1.0.14309\office6 ksomisc.exe 程序。 …

微信小程序页面跳转的区别

①wx.navigateTo&#xff1a;跳转到新页面&#xff0c;可以返回上一层页面 ②wx.redirectTo&#xff1a;跳转到新页面并且关闭当前页面&#xff0c;不可以返回上一层页面(跳转到指定的非TabBar页面) ③wx.reLaunch&#xff1a;跳转到新页面并且关闭当前所有页面&#xff0c;不可…

怎么保护电脑重要文件夹?

当我们在电脑文件夹中存放重要数据时&#xff0c;为了避免数据泄露&#xff0c;就必须使用相应的方式保护文件夹的数据安全。那么我们该怎么保护电脑重要文件夹呢&#xff1f;下面我们就一起来了解一下吧。 文件夹隐藏 我们在文件夹上单击鼠标右键选择“属性”&#xff0c;随后…

Galaxybase-convert实践:两步实现Neo4j数据迁移

前言 Galaxybase是创邻科技自主研发的国内首款超大规模分布式并行原生图平台产品&#xff0c;拥有优异的数据读写查询性能、强⼤的可视化分析能力、丰富的可编程接口和开箱即用的图算法引擎&#xff0c;是集存储、计算、分析于一体的图数据全⽣命周期⼀站式管理平台&#xff0…

ProtoBuf—2

文章目录 1、字段规则2、消息类型的定义和使用3、enum的使用4、any类型2、oneof类型3、map类型 1、字段规则 消息的字段可以用以下几种规则来修饰&#xff1a; singular&#xff1a;消息中可以包含该字段零次或一次(不超过一次)。proto3语法中&#xff0c;字段默认使用该规则…

【XML技术】web杂谈(3)之深入理解什么是XML、XML的语法详解

涉及知识点 什么是 XML&#xff0c;XML的特征&#xff0c;XML的基本语法及应用&#xff0c;应用程序接口&#xff08;DOM&SAX&#xff09;&#xff0c;XML的文档的显示&#xff0c;深入了解XML技术。 原创于&#xff1a;CSDN博主-《拄杖盲学轻声码》&#xff0c;更多内容可…