vue2使用antv/g6-editor实现可拖拽流程图

依赖下载

照着这个引入就好,然后npm install 

源码

<template>

  <div id="vue-g6-editor">

    <el-row>
      <el-col :span="24">

      </el-col>
    </el-row>
    <!-- 工具栏 -->
    <el-row>
      <el-col :span="24">
        <div id="toolbar">
          <i data-command="save" class="command fa fa-floppy-o" title="保存"></i>
          <i class="fa fa-history" title="历史数据" @click="readHistoryData"></i>
          <i class="fa fa-hdd-o" title="上传数据" @click="readUploadData"></i>
          <i class="fa fa-download" title="另存为文件" @click="saveAsFile"></i>
          <i class="fa fa-picture-o" title="另存为图片" @click="openSaveAsImageDialog"></i>
          <i data-command="undo" class="command fa fa-undo" title="撤销"></i>
          <i data-command="redo" class="command fa fa-repeat" title="重做"></i>
          <i data-command="delete" class="command fa fa-trash-o" title="删除"></i>
          <i data-command="zoomOut" class="command fa fa-search-minus" title="缩小"></i>
          <i data-command="zoomIn" class="command fa fa-search-plus" title="放大"></i>
          <i data-command="clear" class="command fa fa-eraser" title="清除画布"></i>
          <i data-command="toFront" class="command fa fa-arrow-up" title="提升层级"></i>
          <i data-command="toBack" class="command fa fa-arrow-down" title="下降层级"></i>
          <i data-command="selectAll" class="command fa fa-check-square-o" title="全选"></i>
          <i data-command="copy" class="command fa fa-files-o" title="复制"></i>
          <i data-command="paste" class="command fa fa-clipboard" title="粘贴"></i>
          <i data-command="autoZoom" class="command fa fa-expand" title="实际大小"></i>
          <i data-command="resetZoom" class="command fa fa-compress" title="适应页面"></i>
          <i data-command="addGroup" class="command fa fa-object-group" title="组合"></i>
          <i data-command="unGroup" class="command fa fa-object-ungroup" title="取消组合"></i>
          <i data-command="multiSelect" class="command fa fa fa-crop" title="多选"></i>
        </div>
      </el-col>
    </el-row>
    <!-- 元素面板 + 画布 + 属性栏 -->
    <el-row>
      <!-- 元素面板 -->
      <el-col :span="2">
        <div id="itempannel">
          <!-- 开始节点 -->
          <div id="startNode" class="getItem" data-type="node" data-shape="flow-circle" data-size="72*72"
            data-label="开始节点" data-color="#FA8C16" data-nodeType="startNode">
            <img draggable="false" :src="startNodeSVGUrl" alt srcset />
          </div>

          <!-- 常规节点 -->
          <div id="regularNode" class="getItem" data-type="node" data-size="100*50" data-label="常规节点"
            data-color="#1890ff">
            <img draggable="false" :src="regularNodeSVGUrl" alt srcset />
          </div>

          <!-- 条件节点 -->
          <div id="judgeNode" class="getItem" data-type="node" data-shape="flow-rhombus" data-size="80*80"
            data-label="条件节点" data-color="#13C2C2">
            <img draggable="false" :src="conditionNodeSVGUrl" />
          </div>

          <!-- 结束节点 -->
          <div id="endNode" class="getItem" data-type="node" data-shape="flow-circle" data-size="80*80"
            data-label="结束节点" data-color="#FA8C16" data-nodeType="endNode">
            <img draggable="false" :src="endNodeSVGUrl" />
          </div>
        </div>
      </el-col>
      <!-- 画布 -->
      <el-col :span="18">
        <el-col :span="24">
          <div id="page">
            <div class="controltab">ab</div>
          </div>
        </el-col>
      </el-col>
      <!-- 属性栏 -->
      <el-col :span="4">
        <section class="right-part">
          <div id="detailpannel">
            <!-- 节点属性栏 -->
            <div id="nodeAttributeBar" class="pannel" data-status="node-selected">
              <div class="title">节点属性</div>
              <div class="main">
                <el-form :model="nodeAttributeForm" label-position="top" label-width="80px">
                  <el-form-item label="节点文本">
                    <el-input v-model="nodeAttributeForm.label" @change="saveNodeAttribute"></el-input>
                  </el-form-item>
                  <el-form-item label="宽度">
                    <el-input v-model="nodeAttributeForm.width" @change="saveNodeAttribute"></el-input>
                  </el-form-item>
                  <el-form-item label="高度">
                    <el-input v-model="nodeAttributeForm.height" @change="saveNodeAttribute"></el-input>
                  </el-form-item>
                  <el-form-item label="颜色">
                    <el-color-picker v-model="nodeAttributeForm.color" @change="saveNodeAttribute"></el-color-picker>
                  </el-form-item>
                </el-form>
              </div>
            </div>
            <!-- 边属性栏 -->
            <div id="edgeAttributeBar" class="pannel" data-status="edge-selected">
              <div class="title">边属性</div>
              <div class="main">
                <el-form :model="edgeAttributeForm" label-position="top" label-width="80px">
                  <el-form-item label="边文本">
                    <el-input v-model="edgeAttributeForm.label" @change="saveEdgeAttribute"></el-input>
                  </el-form-item>
                  <el-form-item label="边文本">
                    <el-select v-model="edgeAttributeForm.shape" @change="saveEdgeAttribute">
                      <el-option label="流程图折线" value="flow-polyline"></el-option>
                      <el-option label="流程图圆⻆折线" value="flow-polyline-round"></el-option>
                      <el-option label="流程图曲线" value="flow-smooth"></el-option>
                    </el-select>
                  </el-form-item>
                </el-form>
              </div>
            </div>
            <div id="groupAttributeBar" class="pannel" data-status="group-selected">
              <div class="title">群组属性栏</div>
            </div>
            <div id="canvasAttributeBar" class="pannel" data-status="canvas-selected">
              <div class="title">画布属性栏</div>
              <div class="main">
                <el-form label-width="80px" label-position="right">
                  <el-form-item label="网格对齐">
                    <el-checkbox v-model="canvasAttributeForm.grid" @change="toggleGridShowStatus"></el-checkbox>
                  </el-form-item>
                </el-form>
              </div>
            </div>
            <div id="multiAttributeBar" class="pannel" data-status="multi-selected">
              <div class="title">多选时属性栏</div>
            </div>
          </div>
          <!-- 缩略图 -->
          <div id="minimap">
            <div class="title">缩略图</div>
          </div>
        </section>
      </el-col>
    </el-row>
    <!-- 弹窗 -->
    <article>
      <!-- 下载图片 -->
      <section class="save-as-image-dialog">
        <el-dialog title="下载图片" :visible.sync="saveAsImageDialogVisible" width="360px">
          <el-form label-width="100px" label-position="top">
            <el-form-item label="选择图片格式">
              <el-select v-model="saveAsImageFormat">
                <el-option label="jpg" value="jpg">
                  <span style="float: left;">jpg</span>
                  <span style="float: right; color: #8492a6; font-size: 13px;">白色背景</span>
                </el-option>
                <el-option label="png" value="png">
                  <span style="float: left;">png</span>
                  <span style="float: right; color: #8492a6; font-size: 13px;">透明背景</span>
                </el-option>
              </el-select>
            </el-form-item>
          </el-form>
          <span slot="footer">
            <el-button @click="saveAsImageDialogVisible = false">取 消</el-button>
            <el-button type="primary" @click="saveAsImage">确 定</el-button>
          </span>
        </el-dialog>
      </section>
    </article>
    <!-- 右键菜单 -->
    <section>
      <div id="contextmenu">
        <div data-status="node-selected" class="menu">
          <el-button data-command="copy" class="command">复制</el-button>
          <el-button data-command="paste" class="command">粘贴</el-button>
          <el-button data-command="delete" class="command">删除</el-button>
        </div>
        <div data-status="edge-selected" class="menu">
          <el-button data-command="delete" class="command">删除</el-button>
        </div>
        <div data-status="group-selected" class="menu">
          <el-button data-command="copy" class="command">复制</el-button>
          <el-button data-command="paste" class="command">粘贴</el-button>
          <el-button data-command="unGroup" class="command">取消组合</el-button>
          <el-button data-command="delete" class="command">删除</el-button>
        </div>
        <div data-status="canvas-selected" class="menu">
          <el-button data-command="undo" class="command">撤销</el-button>
          <el-button data-command="redo" class="command disable">重做</el-button>
        </div>
        <div data-status="multi-selected" class="menu">
          <el-button data-command="copy" class="command">复制</el-button>
          <el-button data-command="paste" class="command">粘贴</el-button>
          <el-button data-command="addGroup" class="command">组合</el-button>
        </div>
      </div>
    </section>
  </div>

</template>
<script>
import G6Editor from '@antv/g6-editor'
import mixin from '../mixin'
import { construct } from 'netflix-conductor-json-tree/dist/index'
export default {
  name: 'VueG6Editor',
  mixins: [mixin],
  data() {
    return {
      // 节点属性表单
      nodeAttributeForm: {
        label: '',
        width: '',
        height: ''
      },
      // 节点属性表单
      edgeAttributeForm: {
        label: ''
      },
      // 画布属性栏表单
      canvasAttributeForm: {
        grid: true,
        cell: 20
      },
      // SVG节点图片URL地址
      startNodeSVGUrl: require('../../../assets/start-node.svg'),
      endNodeSVGUrl: require('../../../assets/end-node.svg'),
      regularNodeSVGUrl: require('../../../assets/regular-node.svg'),
      conditionNodeSVGUrl: require('../../../assets/condition-node.svg'),
      modelNodeSVGUrl: 'https://gw.alipayobjects.com/zos/rmsportal/rQMUhHHSqwYsPwjXxcfP.svg',
      // 编辑器
      editor: null,
      saveAsImageDialogVisible: false,
      saveAsImageFormat: 'jpg'
    }
  },
  mounted() {
    this.initG6Editor()
  },
  methods: {
    // 初始化
    initG6Editor() {
      const _this = this
      const editor = new G6Editor()
      this.editor = editor
      G6Editor.track(false)
      const Command = G6Editor.Command
      // 注册新命令save
      Command.registerCommand('save', {
        // 禁止保存命令进入队列
        queue: false,
        // 命令是否可用
        enable: (editor) => {
          return true
        },
        // 正向命令
        execute(editor) {
          const needSaveData = editor.getCurrentPage().save()
          console.log(needSaveData)
          localStorage.setItem('flowData', JSON.stringify(needSaveData))
          _this.save(needSaveData)
          _this.$message.success('数据已保存')
        },
        // 反向命令
        back(editor) {
          console.log('反向命令')
          console.log(editor)
        },
        // 快捷键:Ctrl + S
        shortcutCodes: [
          ['metaKey', 's'],
          ['ctrlKey', 's']
        ]
      })
      // 画布
      const flow = new G6Editor.Flow({
        graph: {
          container: 'page'
        },
        align: {
          line: {
            // 对齐线颜色
            stroke: '#FA8C16',
            // 对齐线粗细
            lineWidth: 1
          },
          // 开启全方位对齐
          item: true,
          // 网格对齐
          grid: true
        },
        grid: {
          // 网孔尺寸
          cell: 18
        },
        shortcut: {
          // 开启自定义命令保存的快捷键
          save: true
        }
      })
      window.flow = flow

      // 设置边
      flow.getGraph().edge({
        shape: 'flow-polyline'
      })

      // 元素面板栏
      const itempannel = new G6Editor.Itempannel({
        container: 'itempannel'
      })
      // 工具栏
      const toolbar = new G6Editor.Toolbar({
        container: 'toolbar'
      })
      // 属性栏
      const detailpannel = new G6Editor.Detailpannel({
        container: 'detailpannel'
      })
      // 缩略图
      let minimapWidth = getComputedStyle(document.querySelector('.right-part')).width
      minimapWidth = Number(minimapWidth.replace(/px$/, ''))
      const minimap = new G6Editor.Minimap({
        container: 'minimap',
        width: minimapWidth,
        height: 200
      })
      // 右键菜单
      const contextmenu = new G6Editor.Contextmenu({
        container: 'contextmenu'
      })
      // 挂载以上组件到Editor
      editor.add(flow)
      editor.add(itempannel)
      editor.add(toolbar)
      editor.add(detailpannel)
      editor.add(minimap)
      editor.add(contextmenu)
      // 挂载到window,方便调试
      window.editor = editor

      // 获取当前画布
      const currentPage = editor.getCurrentPage()
      currentPage.on('afterchange', (e) => {
        if (e.action === 'add') {
          if (e.model.nodetype === 'startNode' || e.model.nodetype === 'endNode') {
            const nodes = this.editor.getCurrentPage().getNodes()
            for (const item of nodes) {
              if (item.model.nodetype === e.model.nodetype && item.model.id !== e.model.id) {
                this.editor.getCurrentPage().remove(e.item)
                this.$message.warning('只能有一个开始节点或结束节点')
              }
            }
          }
        }
      })
      // 监听(选择对象后)事件
      currentPage.on('afteritemselected', (ev) => {
        console.log('打印所选对象属性', ev.item)
        console.log('打印所选对象数据模型', ev.item.model)
        const selectedItemDataModel = ev.item.model
        // 如果选择的对象是节点
        if (ev.item.isNode) {
          this.nodeAttributeForm.label = selectedItemDataModel.label
          this.nodeAttributeForm.width = selectedItemDataModel.size.split('*')[0]
          this.nodeAttributeForm.height = selectedItemDataModel.size.split('*')[1]
          this.nodeAttributeForm.color = selectedItemDataModel.color
        }
        // 如果选择的对象是边
        if (ev.item.isEdge) {
          ev.item.graph.edge({
            shape: 'flow-polyline-round'
          })
          this.edgeAttributeForm.label = selectedItemDataModel.label
          this.edgeAttributeForm.shape = selectedItemDataModel.shape
        }
      })
      // 监听(删除后)事件
      currentPage.on('afterdelete', (ev) => { })
    },
    // 打开保存为图片弹窗
    openSaveAsImageDialog() {
      this.saveAsImageDialogVisible = true
    },
    // 开启/关闭网格对齐
    toggleGridShowStatus(value) {
      if (value) {
        this.editor.getCurrentPage().showGrid()
      } else {
        this.editor.getCurrentPage().hideGrid()
      }
    },
    // 保存为图片
    saveAsImage() {
      let newCanvas
      if (this.saveAsImageFormat === 'jpg') {
        const canvas = this.editor.getCurrentPage().saveImage()
        newCanvas = document.createElement('canvas')
        newCanvas.width = canvas.width
        newCanvas.height = canvas.height
        const newContext = newCanvas.getContext('2d')
        newContext.fillStyle = '#fff'
        newContext.fillRect(0, 0, newCanvas.width, newCanvas.height)
        newContext.drawImage(canvas, 0, 0)
      }
      if (this.saveAsImageFormat === 'png') {
        newCanvas = this.editor.getCurrentPage().saveImage()
      }
      const imageDataURL = newCanvas.toDataURL()
      const downloadLink = document.createElement('a')
      downloadLink.download = '图片.jpg'
      downloadLink.href = imageDataURL
      document.body.appendChild(downloadLink)
      downloadLink.click()
      document.body.removeChild(downloadLink)
      this.saveAsImageDialogVisible = false
    },
    // 保存为文件
    saveAsFile() {
      const jsonString = JSON.stringify(this.editor.getCurrentPage().save())
      const blob = new Blob([jsonString])
      const blobURL = URL.createObjectURL(blob)
      const downloadLink = document.createElement('a')
      downloadLink.download = '数据.json'
      downloadLink.href = blobURL
      document.body.appendChild(downloadLink)
      downloadLink.click()
      URL.revokeObjectURL(blobURL)
      document.body.removeChild(downloadLink)
    },
    // 读取历史数据
    readHistoryData() {
      const stringData = localStorage.getItem('flowData')
      if (stringData === '' || stringData === '{}' || stringData === null) {
        this.$message.warning('无历史数据')
        return
      }
      const jsonData = JSON.parse(stringData)
      this.editor.getCurrentPage().read(jsonData)
    },
    // 读取上传数据
    readUploadData() {
      const uploadButton = document.createElement('input')
      uploadButton.setAttribute('type', 'file')
      uploadButton.setAttribute('accept', '.json')
      uploadButton.addEventListener('change', (e) => {
        console.dir(uploadButton)
        const file = uploadButton.files[0]
        const fileReader = new FileReader()
        fileReader.onload = (event) => {
          console.log(event)
          const text = JSON.parse(event.target.result)
          console.log(text)
          this.editor.getCurrentPage().read(text)
        }
        fileReader.readAsText(file)
      })
      uploadButton.click()
    },
    //
    save(source) {
      const edges = source.edges
      const nodes = source.nodes
      console.log(construct)
      const res = construct(source)
      console.log(JSON.stringify(res, null, 2))
    }
  }
}
</script>

<style lang="less">
@import url("./index.less");
</style>

index.less

@backgroundColor: #fbfbfb;
@borderColor: #dadce0;

@itempannelAndPageBorder: 1px solid #ccc;
@pageHeight: calc(100vh - 41px - 37px);

body {
  margin: 0;

}

.all {
  width: 100%;
  height: 100%;


  .main {
    overflow-y: auto;

    #vue-g6-editor {

      width: 100%;
      height: 100%;

      overflow-x: hidden;
      background-color: white;

      // transform: scale(0.5);
      .showcontent {
        height: 700px;

        .showface {
          height: 700px;
        }

        .showcanvas {
          height: 700px;
        }

        .shownode {
          height: 700px;
        }
      }


      .showtab {
        width: 100%;
        height: 700px;

      }



      // 主画布
      #page {
        height: 700px;
        position: relative;
        display: flex;

        .graph-container {
          height: 700px;
        }


        .controltab {
          width: 100%;
          height: 200px;
          background-color: white;
          position: absolute;

          bottom: 0;
          z-index: 999;
          /* 子盒子底部与父盒子底部对齐 */
          display: flex;
          flex-direction: column;
          /* 垂直方向排列子元素 */
          opacity: 0.5;
          background-color: white;

          /* 设置盒子的透明度为 0.5,即 50% 不透明 */
          .controlbt {
            opacity: 1;
            width: 100%;
            height: 50px;
            display: flex;
            font-size: 50px;
            justify-content: center;
            align-items: center;


          }

        }

        .activeshow {
          width: 90%;

          opacity: 1;
          flex-grow: 1;
          /* 第二个子盒子沾满剩余空间 */



          .header {
            width: 100%;
            color: black;
            font-weight: 550;
            font-size: 20px;
            padding-left: 10px;
            padding-top: 10px;
          }

          .showmain {
            display: flex;

            .objectshow {
              flex: 1;
              color: black;
              font-weight: 550;
            }
          }

          .staticshow {
            width: 60%;
            height: 110px;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;

            .admi {

              color: black;
              font-weight: 550;
              margin-bottom: 15px;
            }

            .setps {
              color: black;
              font-weight: 550;
              margin-bottom: 15px;
            }

            .atomname {
              color: black;
              font-weight: 550;
            }
          }


        }



      }


      header:nth-of-type(1) {
        background: @backgroundColor;
        line-height: 40px;
        padding-left: 20px;
        border-bottom: 1px solid @borderColor;
        box-sizing: border-box;
      }
    }
  }



  // 工具栏
  #toolbar {
    display: flex;
    justify-content: center;
    background: @backgroundColor;
    border-bottom: 1px solid @borderColor;
    padding: 4px 14px;

    i {
      font-size: 18px;
      padding: 4px;
      margin-right: 8px;
      color: #999999;

      &:hover {
        cursor: pointer;
        background-color: #eeeeee;
        color: #5cb6ff;
      }
    }
  }

  // 元素面板
  #itempannel {
    box-sizing: border-box;
    background-color: @backgroundColor;
    border-right: 1px solid @borderColor;
    height: @pageHeight;
    padding-top: 10px;
    overflow: hidden;
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: space-around;
    align-content: flex-start;

    .getItem {
      cursor: move;
      width: 80px;
      height: 80px;
      margin-bottom: 20px;
      display: flex;
      justify-content: center;
      align-items: center;

      img {
        width: 100%;
      }
    }
  }



  // 右侧部分(属性栏 + 缩略图)
  .right-part {
    height: @pageHeight;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
  }

  // 属性栏
  #detailpannel {
    flex-grow: 1;

    background-color: @backgroundColor;
    border-left: 1px solid @borderColor;
    overflow-y: scroll;

    #nodeAttributeBar,
    #edgeAttributeBar,
    #groupAttributeBar,
    #canvasAttributeBar,
    #multiAttributeBar {
      .title {
        height: 34px;
        line-height: 34px;
        text-align: center;
        box-sizing: border-box;
        font-weight: bold;
        font-size: 13px;
        border-width: 0 0 1px 0;
        border-style: solid;
        border-color: @borderColor;
      }

      .main {
        padding: 10px;
      }
    }
  }

  // 缩略图
  #minimap {
    background-color: @backgroundColor;
    border-top: 1px solid #ccc;
    border-left: 1px solid #ccc;

    .title {
      height: 34px;
      line-height: 34px;
      text-align: center;
      box-sizing: border-box;
      font-weight: bold;
      font-size: 13px;
      border-width: 0 0 1px 0;
      border-style: solid;
      border-color: @borderColor;
    }
  }

  // 右键菜单
  #contextmenu {
    display: none;

    .menu {
      /deep/ .el-button {
        width: 100%;
        display: block;
        margin-left: 0;
        border-radius: 0 !important;
        border-bottom: none;

        &:nth-last-of-type(1) {
          border-bottom: 1px solid #dcdfe6;
        }
      }
    }
  }

  // 下载图片弹窗
  .save-as-image-dialog {
    /deep/ .el-select {
      display: block;
    }
  }
}

mixin.js

export default {
  methods: {
    // 保存节点属性
    saveNodeAttribute() {
      this.editor.executeCommand(() => {
        // 获取画布
        const page = this.editor.getCurrentPage();
        // 获取所选对象
        const selectedItem = page.getSelected()[0];
        page.update(selectedItem.id, {
          label: this.nodeAttributeForm.label,
          size: this.nodeAttributeForm.width + "*" + this.nodeAttributeForm.height,
          color: this.nodeAttributeForm.color
        });
      });
    },
    // 保存边属性
    saveEdgeAttribute() {
      this.editor.executeCommand(() => {
        // 获取画布
        const page = this.editor.getCurrentPage();
        // 获取所选对象
        const selectedItem = page.getSelected()[0];
        console.log(this.edgeAttributeForm);
        page.update(selectedItem.id, {
          label: this.edgeAttributeForm.label,
          shape: this.edgeAttributeForm.shape
        });
      });
    }
  }
};

相关推荐

  1. vue2 + antvx6 实现流程图功能

    2024-06-08 05:52:02       9 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-08 05:52:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-08 05:52:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-08 05:52:02       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-08 05:52:02       20 阅读

热门阅读

  1. 【Vue】自定义指令

    2024-06-08 05:52:02       8 阅读
  2. nginx-变量

    2024-06-08 05:52:02       9 阅读
  3. GPT-4o能力评价与个人感受

    2024-06-08 05:52:02       12 阅读
  4. Flutter 中的 TableCell 小部件:全面指南

    2024-06-08 05:52:02       12 阅读
  5. Python | R 潜在混合模型

    2024-06-08 05:52:02       5 阅读
  6. HTML label 标签的作用和应用场景

    2024-06-08 05:52:02       9 阅读
  7. docker 启动

    2024-06-08 05:52:02       6 阅读