项目需要实现如下效果流程图,功能包括节点排序、新增节点、编辑节点、删除节点、选中节点等
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,必须,起始节点 id
target: item.target, // String,必须,目标节点 id
router: {
name: 'manhattan',
args: {
startDirections: ['right'],
endDirections: ['left']
}
},
attrs: {
line: {
stroke: '#1d6ee4'
}
}
}
})
//初始化加载mind,更新数据时不初始化mind
if(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 = null
this.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.nowData
this.visible = true
}
},
新增节点和编辑节点弹窗操作
//cancelDialog
cancelDialog(){
this.visible = false
},
submitData(){
// 新增的时候,formData就是新增本身,nowData就是父节点
// 编辑的时候,获取到nowData,赋值给formData
this.$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.label
this.visible = false
this.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 = false
this.getNodeData(false)
}
}
})
},
涉及的样式
<style scoped>
.MindMapContent{
padding: 10px 25px;
height: 350px;
background-color: #ffffff;
}
#mindContent{
position: relative;
}
</style>