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 = thisconst editor = new G6Editor()this.editor = editorG6Editor.track(false)const Command = G6Editor.Command// 注册新命令saveCommand.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 + SshortcutCodes: [['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')).widthminimapWidth = Number(minimapWidth.replace(/px$/, ''))const minimap = new G6Editor.Minimap({container: 'minimap',width: minimapWidth,height: 200})// 右键菜单const contextmenu = new G6Editor.Contextmenu({container: 'contextmenu'})// 挂载以上组件到Editoreditor.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.labelthis.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.labelthis.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 newCanvasif (this.saveAsImageFormat === 'jpg') {const canvas = this.editor.getCurrentPage().saveImage()newCanvas = document.createElement('canvas')newCanvas.width = canvas.widthnewCanvas.height = canvas.heightconst 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 = imageDataURLdocument.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 = blobURLdocument.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.edgesconst nodes = source.nodesconsole.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});});}}
};