antv x6使用(支持节点排序、新增节点、编辑节点、删除节点、选中节点)

发布于:2025-04-14 ⋅ 阅读:(19) ⋅ 点赞:(0)

项目需要实现如下效果流程图,功能包括节点排序、新增节点、编辑节点、删除节点、选中节点等

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>

项目地址


网站公告

今日签到

点亮在社区的每一天
去签到