antv x6使用(支持节点排序、新增节点、编辑节点、删除节点、选中节点)
项目需要实现如下效果流程图,功能包括节点排序、新增节点、编辑节点、删除节点、选中节点等
html部分如下:
<template><div class="MindMapContent"><el-button size="small" @click="addNode">新增节点</el-button><el-button size="small" @click="updateNode">编辑节点</el-button><el-button size="small" type="danger" plain @click="removeNode">删除节点</el-button><div id="mindContent" style="height: 300px"><div id="container"></div></div><el-dialog v-model="visible" :title="pageType == 'edit' ? '编辑' : '新增'"><div><el-form class="search-form" ref="formData" size="small" label-width="120px" :model="formData"><el-form-item label="节点名称" prop="label" :rules="[{required: true, message: '请输入节点名称',trigger: 'blur'}]"><el-input v-model="formData.label" style="width: 60%"></el-input></el-form-item></el-form></div><span slot="footer" class="dialog-footer"><el-button @click="cancelDialog">返回</el-button><el-button type="primary" @click="submitData">提交</el-button></span></el-dialog></div>
</template>
需要后后端返回的数据格式如下:
mindData: {edgeList: [{source: '50',target: '54'},{source: '50',target: '61'},{source: '54',target: '66'},{source: '61',target: '67'},{source: '67',target: '69'},{source: '50',target: '71'},],nodeList: [{id: '50', label: '根节点'},{id: '54', label: '111'},{id: '61', label: '222'},{id: '66', label: '333'},{id: '67', label: '444'},{id: '69', label: '555'},{id: '71', label: '666'},]
}
获取后端返回数据后, 需要为节点和边设置样式,所以需要对数据进行处理。x6图最好只加载一次,后续再进行操作时只需要更新数据即可。因为在项目中可以为某个节点绑定其它属性id,绑定后仍保持选中状态,所以设置selectNodeId,当有selectNodeId时,需要选中node.id为selectNodeId的节点
//获取节点数据getNodeData(bool,selectNodeId){this.objData.nodes = (this.mindData.nodeList || []).map(item => {return {id: item.id, // String,可选,节点的唯一标识width: 120, // Number,可选,节点大小的 width 值height: 30, // Number,可选,节点大小的 height 值label: item.label, // String,节点标签data: {portalId: item.portalId || '',},attrs: {body: {stroke: 'rgba(238, 238, 238, 1)',strokeWidth: 1,rx: 5,ry: 5,style: {filter: 'drop-shadow(0px 0px 8px rgba(0,0,0,0.07))'}},label: {fontSize: 12,textWrap: {ellipsis: true,width: 105}}}}})this.objData.edges = (this.mindData.edgeList || []).map(item => {return{source: item.source, // String,必须,起始节点 idtarget: item.target, // String,必须,目标节点 idrouter: {name: 'manhattan',args: {startDirections: ['right'],endDirections: ['left']}},attrs: {line: {stroke: '#1d6ee4'}}}})//初始化加载mind,更新数据时不初始化mindif(bool){this.initGraph()}else {this.graph.cleanSelection()this.nowData = {}//更新节点信息后重新布局 let gridLayout = new DagreLayout({type: 'dagre',rankdir: 'LR',align: undefined,ranksep: 45,nodesep: 5,})this.graph.fromJSON(gridLayout.layout(this.objData))//如果有selectNodeId,则选中node.id为selectNodeId的节点if (selectNodeId) {const node = this.graph.getCellById(selectNodeId)if (node) {this.graph.resetSelection(node)this.nowData = {id: node.id,label: node.label,portalId: node.data.portalId || ''}//返回选中的数据 this.$emit('getData', this.nowData)}}}},
初始化画布
// 初始化流程图画布initGraph() {let container = document.getElementById('container')this.graph = nullthis.graph = new Graph({container,width: '100%',height: '100%',//最大最小缩放比例scaling: {min: 0.7,max: 1.2},autoResize: true,panning: true,mousewheel: true,background: {color: '#ffffff', // 设置画布背景颜色},})//使用布局插件自动布局 let gridLayout = new DagreLayout({type: 'dagre',rankdir: 'LR',align: undefined,ranksep: 45,nodesep: 5,})//渲染布局数据this.graph.fromJSON(gridLayout.layout(this.objData))//使用x6选中插件this.graph.use(new Selection({enabled: true,multiple: false,movable: false,rubberband: false,showNodeSelectionBox: true,clearSelectionOnBlank: false}))//节点点击选中 this.graph.on('node:click', ({ e,node }) => {e.stopPropagation()tooltip.style.display = 'none'this.graph.resetSelection(node)this.nowData = {id: node.id,label: node.label,portalId: node.data.portalId || ''}this.$emit('getData',this.nowData)})//点击节点外清空点击数据 this.graph.on('blank:click', ({ e,node }) => {this.graph.cleanSelection()this.nowData = {}})//node节点有宽度限制,label超过宽度时显示...,但是需要tooltip显示完整的label const tooltip = document.createElement('div')tooltip.className = 'x6-tooltip'tooltip.style.position = "absolute"tooltip.style.display = 'none'tooltip.style.padding = '6px'tooltip.style.borderRadius = '5px'tooltip.style.backgroundColor = '#303133'tooltip.style.color = '#ffffff'tooltip.style.fontSize = '12px'let mindContent = document.getElementById('mindContent')mindContent.appendChild(tooltip)this.graph.on('node:mouseenter', ({ node }) => {if(node.label){const position = this.graph.localToGraph(node.getBBox().getCenter())tooltip.style.display = 'block'tooltip.style.left = `${position.x - 60}px`tooltip.style.top = `${position.y - 50}px`tooltip.textContent = node.label}})this.graph.on('node:mouseleave', ({ node }) => {tooltip.style.display = 'none'})},
节点操作
//删除节点removeNode(){if(!this.nowData.id){this.$message.error('请选择需要删除的节点')}else{this.mindData.nodeList = this.mindData.nodeList.filter(item => item.id != this.nowData.id)this.mindData.edgeList = this.mindData.edgeList.filter(item => item.target != this.nowData.id)this.getNodeData(false)}},//新增节点addNode(){this.formData = {}this.pageType = 'add'if (this.objData.nodes.length == 0){this.visible = true} else{if(!this.nowData.id){this.$message.error('请选择父节点')}else{this.visible = true}}},//编辑节点updateNode(){this.formData = {}this.pageType = 'edit'if(!this.nowData.id){this.$message.error('请选择编辑的节点')}else{this.formData = this.nowDatathis.visible = true}},
新增节点和编辑节点弹窗操作
//cancelDialogcancelDialog(){this.visible = false},submitData(){// 新增的时候,formData就是新增本身,nowData就是父节点// 编辑的时候,获取到nowData,赋值给formDatathis.$refs.formData.validate(valid => {if(valid){if (this.pageType == 'edit'){let obj = this.mindData.nodeList.find(item => item.id == this.formData.id)obj.label = this.formData.labelthis.visible = falsethis.getNodeData(false)}else{let id = Math.random().toString(36).substring(2, 4)this.mindData.nodeList.push({id, label: this.formData.label,})this.mindData.edgeList.push({target: id, source: this.nowData.id,})this.visible = falsethis.getNodeData(false)}}})},
涉及的样式
<style scoped>
.MindMapContent{padding: 10px 25px;height: 350px;background-color: #ffffff;
}
#mindContent{position: relative;
}
</style>
项目地址