AntV X6 流程图编排

数据准备javaClass

package com.xxxx.xxxx.domain;

import lombok.Data;

/**
 * @Description: 流程图编排
 * @Author zhou
 * @Date 2024/3/4 - 14:43
 */
@Data
public class CronSceneNode {
    // id
    private String sceneNodeId;
    // 节点名称
    private String sceneNodeName;

    // 节点类型:
    // 开始 结束 判断
    // 采集工具 文件数统计
    private String sceneNodeType;
    // 节点x轴
    private int nodeX;
    // 节点y轴
    private int nodeY;
    // 下级id
    private String subSceneNodeId;
    // 执行类型
    private String execType;
    // 地址(主机/数据库)
    private int paramId;
    // 执行内容
    private Object content;
    // 判断条件
    private String judgeCondition;
    // 执行错误后执行id
    private String judgeErrNodeId;
    // 连接桩id
    private String portErrId;
    // 判断错误连接桩id
    private String portId;

}

js代码

<el-col :span="showMap? 24: 0">
      <el-row>
        <el-col style="margin-bottom: 10px;">
          <el-button size="mini" plain type="primary" @click="showMap = false" style="margin-bottom: 10px;">返回</el-button>
          <el-button size="mini" type="primary" @click="saveNodeData" style="margin-bottom: 10px;">保存</el-button>
        </el-col>
        <el-col :span="drawerShow? 18: 24">
          <div id="container" class="container">
            <div id="stencil" class="stencil"></div>
            <div id="graph-container" class="graph-container"></div>
          </div>
        </el-col>
        <el-col :span="drawerShow? 6: 0">
          <el-row>
            <el-col>
              <el-form ref="form" :model="form" label-width="80px" size="mini">
                <el-form-item label="节点名称">
                  <el-input v-model="form.sceneNodeName"></el-input>
                </el-form-item>
                <el-form-item label="判断条件" v-show="form.sceneNodeType === 'judgment'">
                  <el-input v-model="form.judgeCondition"></el-input>
                </el-form-item>
                <el-form-item label="执行方式">
                  <el-select v-model="form.execType" placeholder="请选择执行方式" @change="form.paramId === ''" style="width: 100%;">
                    <el-option label="shell" value="shell"></el-option>
                    <el-option label="sql" value="DB"></el-option>
                  </el-select>
                </el-form-item>
                <el-form-item label="选择集群">
                  <el-select v-model="form.paramId" placeholder="请选择" @change="" style="width: 100%;">
                    <el-option v-for="item in getCluster()" :key="item.PARAM_ID" :label="item.PARAM_NAME" :value="item.PARAM_VALUE"></el-option>
                  </el-select>
                </el-form-item>
                <el-form-item label="执行内容">
                  <el-input type="textarea" :rows="6" placeholder="请输入内容" v-model="form.content"></el-input>
                </el-form-item>
              </el-form>
            </el-col>
            <el-col style="text-align: right;">
<!--              <el-button size="mini" type="danger" @click="drawerShow = false">删除</el-button>-->
              <el-button size="mini" type="primary" @click="saveNode()">确定</el-button>
              <el-button size="mini" @click="drawerShow = false">取消</el-button>
            </el-col>
          </el-row>
        </el-col>
      </el-row>
    </el-col>
import {Graph, Shape} from '@antv/x6'
// 插件
import { Stencil } from '@antv/x6-plugin-stencil'
import { Snapline } from '@antv/x6-plugin-snapline'

data() {
return: {
graph: null,
      formList: [], // 真实数据
      form: { // 参数
        sceneNodeId: '', // id
        sceneId: '', // 场景id
        sceneNodeName: '', // 节点名称
        sceneNodeType:'', // 节点类型
        nodeX: '', // 节点x轴
        nodeY: '', // 节点y轴
        subSceneNodeId: '', // 下级id
        execType: '', // 执行类型
        paramId: '', // 地址(主机/数据库)
        content: '', // 执行内容
        judgeCondition: '', // 判断条件
        judgeErrNodeId: '', // 执行错误后执行id
        portId: '', // 连接桩id
        portErrId: '', // 判断错误连接桩id
      },
      formInit: null,
      nodeGroup1: [{
        type: 'start',
        name: '开始',
        execType: ''
      },{
        type: 'end',
        name: '结束',
        execType: ''
      },{
        type: 'judgment',
        name: '判断',
        execType: ''
      }],
      nodeGroup2: [{
        type: 'collection-tools',
        name: '采集工具',
        execType: ''
      },{
        type: 'file-count-statistics',
        name: '文件数统计',
        execType: 'shell'
      },{
        type: 'file-cleanup',
        name: '文件清理',
        execType: 'shell'
      },{
        type: 'database-backup-cleaning',
        name: '数据库备份清理',
        execType: 'DB'
      },{
        type: 'database-table-statistics',
        name: '数据库表统计',
        execType: 'DB'
      }],
}
}

// 二级展示部分 ===============================
    // 初始化
    initNode() {
      // 定义画布与流程图 ------
      const container = document.getElementById('container')
      const stencilContainer = document.getElementById('stencil')
      const graphContainer = document.getElementById('graph-container')
      const width = graphContainer.style.width
      const height = graphContainer.style.height
      // 画布
      this.graph = new Graph({
        container: graphContainer,
        width,height,
        grid: true,
        panning: true,
        background: {
          color: '#E6E6E6',
        },
        mousewheel: {
          enabled: true,
          zoomAtMousePosition: true,
          modifiers: 'ctrl',
          minScale: 0.5,
          maxScale: 3,
        },
        connecting: {
          connector: {
            name: 'rounded',
            args: {
              radius: 8,
            },
          },
          anchor: 'center',
          connectionPoint: 'boundary',
          allowBlank: false,
          snap: {
            radius: 20,
          },
          createEdge() { // 连接线
            return new Shape.Edge({
              router: 'manhattan',
              attrs: {
                line: {
                  stroke: '#A2B1C3',
                  strokeWidth: 2,
                  targetMarker: 'classic',
                },
              },
              zIndex: 0,
            })
          },
          validateConnection({ sourceCell, targetCell, sourceMagnet,targetMagnet }) { // 在移动边的时候判断连接是否有效
            if (sourceCell === targetCell) {// 不能连接自身
              return false
            }
            if ( sourceCell.shape === 'custom-rect-end') { // 来源不能是结束
              return false
            }
            if ( targetCell.shape === 'custom-rect-start') { // 目标不能是开始
              return false
            }
            return !!targetMagnet
          },
        },
      })
      // 添加插件
      this.graph.use(new Snapline())
      // 流程图
      const stencil = new Stencil({
        title: '流程图',
        target: this.graph,
        stencilGraphWidth: 200,
        stencilGraphHeight: 0, // 设置成0为自适应
        collapsable: true,
        layoutOptions: {
          columns: 1,
          marginX: 35,
          rowHeight: 55,
        },
        groups: [
          {
            title: '编排控件',
            name: 'group1',
          },
          {
            title: '能力工具',
            name: 'group2',
          },
          {
            title: '业务工具',
            name: 'group3',
          },
        ],
      })
      stencilContainer.appendChild(stencil.container)

      // 快捷键与事件 ------
      const showPorts = (ports, show) => {
        for (let i = 0, len = ports.length; i < len; i += 1) {
          ports[i].style.visibility = show ? 'visible' : 'hidden'
        }
      }
      this.graph.on('node:mouseenter', ({ node }) => {
        // 加连接桩
        const ports = graphContainer.querySelectorAll(
          '.x6-port-body',
        )
        showPorts(ports, true)
        // 加删除
        if (!node.hasTool('button-remove')) {
          if (node.shape === 'custom-polygon') {
            node.addTools([{
              name: 'button-remove',
              args: {
                x: '100%',
                y: 0,
                offset: { x: -20, y: 10 },
              },
            }])
          } else {
            node.addTools([{
              name: 'button-remove',
              args: {
                x: '100%',
                y: 0,
                offset: { x: -10, y: 0 },
              },
            }])
          }
        }
      })
      this.graph.on('node:mouseleave', ({ node }) => {
        // 去连接桩
        const ports = graphContainer.querySelectorAll(
          '.x6-port-body',
        )
        showPorts(ports, false)
        // 去删除
        if (node.hasTool('button-remove')) {
          node.removeTool('button-remove')
        }
      })
      this.graph.on('edge:mouseenter', ({ cell }) => {
        if (!cell.hasTool('button-remove')) {
          cell.addTools([{
            name: 'button-remove',
            args: { distance: 20 },
          }])
        }
      })
      this.graph.on('edge:mouseleave', ({ cell }) => {
        if (cell.hasTool('button-remove')) {
          cell.removeTool('button-remove')
        }
      })
      // 编辑节点: node:click; 新增节点: node:added; 新增边: edge:added; 移出节点: node:removed; 移出边edge:removed
      this.graph.on('node:click', ({ e, x, y, node, view }) => {
        this.updateNode(e, x, y, node, view)
      })
      this.graph.on('node:added', ({ node, index, options }) => {
        this.addNode(node, index, options)
      })
      this.graph.on('node:removed', ({ node, index, options }) => {
        this.removeNode(node, index, options)
      })
      this.graph.on('edge:connected', ({ isNew, edge }) => {
        this.addEdge(isNew, edge)
      })
      this.graph.on('edge:removed', ({ edge, index, options }) => {
        this.removeEdge(edge, index, options)
      })
      this.graph.on('node:moved', ({ e, x, y, node, view }) => {
        this.moveNode(e, x, y, node, view)
      })
      // 初始化图形 ------
      const ports = { // 连接桩
        groups: {
          top: {
            position: 'top',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          right: {
            position: 'right',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          bottom: {
            position: 'bottom',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          left: {
            position: 'left',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
        },
        items: [
          {
            id: 'top',
            group: 'top',
          },
          {
            id: 'right',
            group: 'right',
          },
          {
            id: 'bottom',
            group: 'bottom',
          },
          {
            id: 'left',
            group: 'left',
          },
        ],
      }
      const generalAttrs = {// 通用节点的颜色与字体
        body: {
          strokeWidth: 1,
          stroke: '#5F95FF',
          fill: '#EFF4FF',
        },
        text: {
          fontSize: 12,
          fill: '#262626',
        },
      }
      const abilityAttrs = { // 能力颜色
        body: {
          fill: '#E1D5E7',
          rx: 6,
          ry: 6,
        },
      }
      const businessAttrs0 = { // 能力颜色
        body: {
          fill: '#B3B3B3',
          rx: 6,
          ry: 6,
        },
      }
      const businessAttrs1 = { // 能力颜色
        body: {
          fill: '#FAD7AC',
          rx: 6,
          ry: 6,
        },
      }
      Graph.registerNode( // 开始
        'custom-rect-start',
        {
          inherit: 'rect',
          width: 80,
          height: 36,
          attrs: generalAttrs,
          ports: {
            ...ports,
            items: [
              {
                id: 'bottom',
                group: 'bottom',
              },
            ],
          },
        },
        true,
      )
      Graph.registerNode( // 结束
        'custom-rect-end',
        {
          inherit: 'rect',
          width: 80,
          height: 36,
          attrs: generalAttrs,
          ports: {
            ...ports,
            items: [
              {
                id: 'top',
                group: 'top',
              },
            ],
          },
        },
        true,
      )
      Graph.registerNode( // 判断
        'custom-polygon',
        {
          inherit: 'polygon',
          width: 90,
          height: 36,
          attrs: generalAttrs,
          ports: {
            ...ports,
          },
        },
        true,
      )
      Graph.registerNode( // 通用节点
        'custom-rect',
        {
          inherit: 'rect',
          width: 100,
          height: 36,
          attrs: generalAttrs,
          ports: { ...ports },
        },
        true,
      )
      const start = this.graph.createNode({
        shape: 'custom-rect-start',
        label: '开始',
        attrs: {
          body: {
            rx: 20,
            ry: 26,
          },
        },
      })
      const end = this.graph.createNode({
        shape: 'custom-rect-end',
        label: '结束',
        attrs: {
          body: {
            rx: 20,
            ry: 26,
          },
        },
      })
      const judgment = this.graph.createNode({
        shape: 'custom-polygon',
        attrs: {
          body: {
            refPoints: '0,10 10,0 20,10 10,20',
          },
        },
        label: '判断',
      })

      stencil.load([start, end, judgment], 'group1')
      // 能力工具
      let nodes = []
      for (let i = 0; i < this.nodeGroup2.length; i++) {
        let itemNode = this.nodeGroup2[i]
        nodes.push(this.graph.createNode({
          shape: 'custom-rect',
          label: itemNode.name,
          attrs: abilityAttrs,
        }))
      }
      stencil.load(nodes, 'group2')

      // 业务能力 ===============================================================
      // const b1 = this.graph.createNode({
      //   shape: 'custom-rect',
      //   label: '账单文件入库',
      //   attrs: businessAttrs0,
      // })
      // stencil.load([b1], 'group3')

      // 初始化图
      this.initX6()
    },
    initX6() { // 查询并展示
      listPRANode({sceneId: this.sceneId}).then(res =>{
        this.formList = res.data
        let data = {nodes: [], edges: []}
        const attr1 = {
          body: {
            rx: 20,
            ry: 26,
          },
        }
        const abilityAttrs = { // 能力颜色
          body: {
            fill: '#E1D5E7',
            rx: 6,
            ry: 6,
          },
        }
        const businessAttrs0 = { // 能力颜色
          body: {
            fill: '#B3B3B3',
            rx: 6,
            ry: 6,
          },
        }
        const businessAttrs1 = { // 能力颜色
          body: {
            fill: '#FAD7AC',
            rx: 6,
            ry: 6,
          },
        }
        for (let i = 0; i < this.formList.length; i++) {// 新增节点
          const itemNode = this.formList[i]
          if (itemNode.sceneNodeType === 'start') {
            data.nodes.push({
              shape: 'custom-rect-start',
              id: itemNode.sceneNodeId,
              label: '开始',
              position: {
                x: itemNode.nodeX,
                y: itemNode.nodeY,
              },
              attrs: attr1
            })
          } else if (itemNode.sceneNodeType === 'end') {
            data.nodes.push({
              shape: 'custom-rect-end',
              id: itemNode.sceneNodeId,
              label: '结束',
              position: {
                x: itemNode.nodeX,
                y: itemNode.nodeY,
              },
              attrs: attr1
            })
          } else if (itemNode.sceneNodeType === 'judgment') {
            data.nodes.push({
              shape: 'custom-polygon',
              id: itemNode.sceneNodeId,
              label: itemNode.sceneNodeName,
              position: {
                x: itemNode.nodeX,
                y: itemNode.nodeY,
              },
              attrs: {
                body: {
                  refPoints: '0,10 10,0 20,10 10,20',
                },
              }
            })
          } else if (this.nodeGroup2.some(item => itemNode.sceneNodeType === item.type)) { // 能力工具
            data.nodes.push({
              shape: 'custom-rect',
              id: itemNode.sceneNodeId,
              label: itemNode.sceneNodeName,
              position: {
                x: itemNode.nodeX,
                y: itemNode.nodeY,
              },
              attrs: abilityAttrs
            })
          } else { // 业务能力
            data.nodes.push({
              shape: 'custom-rect',
              id: itemNode.sceneNodeId,
              label: itemNode.sceneNodeName,
              position: {
                x: itemNode.nodeX,
                y: itemNode.nodeY,
              },
              attrs: businessAttrs0
            })
          }
        }
        this.graph.fromJSON(data)
        let edgeAttrs = {
          router: 'manhattan',
          attrs: {
            line: {
              stroke: '#A2B1C3',
              strokeWidth: 2,
              targetMarker: 'classic',
            },
          },
          zIndex: 0
        }
        for (let i = 0; i < this.formList.length; i++) {
          const itemNode = this.formList[i]
          if (itemNode.sceneNodeType === 'judgment') {
            if (itemNode.subSceneNodeId && itemNode.subSceneNodeId !== '0') { // 下级非数字,且不为0
              const port = itemNode.portId.split('|').filter(Boolean)
              if (port.length === 2) {
                this.graph.addEdge({
                  source: { // 源节点
                    cell: itemNode.sceneNodeId,
                    port: port[0]
                  },
                  target:  { // 目标节点
                    cell: itemNode.subSceneNodeId,
                    port: port[1]
                  },
                  label: 'Y',
                  ...edgeAttrs
                })
              }
            }
            if (itemNode.judgeErrNodeId && itemNode.judgeErrNodeId !== '0') { // 下级非数字,且不为0
              const port = itemNode.portErrId.split('|').filter(Boolean)
              if (port.length === 2) {
                this.graph.addEdge({
                  source: { // 源节点
                    cell: itemNode.sceneNodeId,
                    port: port[0]
                  },
                  target:  { // 目标节点
                    cell: itemNode.judgeErrNodeId,
                    port: port[1]
                  },
                  label: 'N',
                  ...edgeAttrs
                })
              }
            }
          } else {
            if (itemNode.subSceneNodeId && itemNode.subSceneNodeId !== '0') { // 下级非数字,且不为0
              const port = itemNode.portId.split('|').filter(Boolean)
              if (port.length === 2) {
                this.graph.addEdge({
                  source: { // 源节点
                    cell: itemNode.sceneNodeId,
                    port: port[0]
                  },
                  target:  { // 目标节点
                    cell: itemNode.subSceneNodeId,
                    port: port[1]
                  },
                  ...edgeAttrs
                })
              }
            }
          }
        }
      })
    },
    // 新增节点: node:added; 新增边: edge:added; 移出节点: node:removed; 移出边edge:removed
    addNode(node, index, options) { // 新增节点
      // 新增节点;只能有一个开始和结束
      if ((node.shape === 'custom-rect-start' && this.formList.find(i => i.sceneNodeType === 'start') !== undefined)
      || (node.shape === 'custom-rect-end' && this.formList.find(i => i.sceneNodeType === 'end') !== undefined)) {
        // 插入的节点为开始节点并且存在开始节点 或者 插入的节点为结束节点并且存在结束节点
        this.graph.removeNode(node.id, false)
        this.$message.warning('开始节点和结束节点只能存在一个!')
        return
      }
      // 新增节点逻辑
      let form = JSON.parse(JSON.stringify(this.formInit))
      form.sceneId = this.sceneId
      form.sceneNodeId = node.id
      form.sceneNodeName = node.label
      let type = this.nodeGroup1.concat(this.nodeGroup2).filter(item => item.name === node.label)
      if (type.length > 0) {
        form.sceneNodeType = type[0].type
        form.execType = type[0].execType
      } else {                                                                                                                                                       // 业务能力
        form.sceneNodeType = 'service'
        form.execType = 'service'
      }

      const {x, y} = node.getPosition()
      form.nodeX = x
      form.nodeY = y

      // 后期不动可以删除
      form.subSceneNodeId = ''
      form.paramId = ''
      form.content = ''
      form.judgeCondition = ''
      form.judgeErrNodeId = ''

      this.formList.push(form) // 将节点记录的数据中
    },
    removeNode(node, index, options) { // 移出节点,且将下级节点置为''
      if (options) {
        for (let i = 0; i < this.formList.length; i++) {
          let item = this.formList[i]
          if(item.subSceneNodeId === node.id) {
            item.subSceneNodeId = ''
          }
        }
        // 除自身
        let formIndex = this.formList.findIndex(i => i.sceneNodeId === node.id)
        this.formList.splice(formIndex, 1)
      }
    },
    addEdge(isNew, edge) { // 新增边: 判断需要特殊处理
      const source = edge.getSourceCell()
      const target = edge.getTargetCell()
      for (let i = 0; i < this.formList.length; i++) {
        let item = this.formList[i]
        if (item.sceneNodeId === source.id) {
          if (item.sceneNodeType !== 'judgment') { // 判断普通节点只能连接一个
            if (item.subSceneNodeId === '') { // 没有下级,存入
              item.subSceneNodeId = target.id
              item.portId = edge.getSourcePortId() + '|' + edge.getTargetPortId()
            } else { // 有下级,不能存入
              this.graph.removeEdge(edge, false)
              this.$message.warning('只能有一个下级节点!')
            }
          } else { // 判断节点
            // 先判断是否有: 都有不加;有一则补填另一项;没有则,先Y;
            if (item.subSceneNodeId !== '' && item.judgeErrNodeId !== '') {// 都有不加
              this.graph.removeEdge(edge, false)
              this.$message.warning('判断节点只能有两个下级节点!')
            } else { // 有一则补填另一项;没有则,先Y; =  有Y补填N,没Y就写Y
              if (item.subSceneNodeId !== '') { // 有Y 补填 N
                if (target.id !== item.subSceneNodeId) { // 保证不是连接到一个节点上
                  item.judgeErrNodeId = target.id
                  item.portErrId = edge.getSourcePortId() + '|' + edge.getTargetPortId()
                  edge.setLabels('N')
                } else {
                  this.graph.removeEdge(edge, false)
                  this.$message.warning('下级节点不能相同!')
                }
              } else { // 没Y就写Y
                if (target.id !== item.judgeErrNodeId) { // 保证不是连接到一个节点上
                  item.subSceneNodeId = target.id
                  item.portId = edge.getSourcePortId() + '|' + edge.getTargetPortId()
                  // 修改节点数据
                  edge.setLabels('Y')
                } else {
                  this.graph.removeEdge(edge, false)
                  this.$message.warning('下级节点不能相同!')
                }
              }
            }
          }
        }
      }
    },
    removeEdge(edge, index, options) { // 删除边: 判断需要特殊处理
      if (options) { // 主动删除的节点才能处理
        const source = edge.getSource()
        const target = edge.getTarget()
        for (let i = 0; i < this.formList.length; i++) {
          let item = this.formList[i]
          if (item.sceneNodeId === source.cell) {
            if (item.sceneNodeType !== 'judgment') {
              item.subSceneNodeId = ''
              item.portId = ''
            } else {
              if (item.subSceneNodeId === target.cell) {
                item.subSceneNodeId = ''
                item.portId = ''
              } else {
                item.judgeErrNodeId = ''
                item.portErrId = ''
              }
            }
          }
        }
      }
    },
    moveNode(e, x1, y1, node, view) { // 移动节点
      for (let i = 0; i < this.formList.length; i++) {
        let item = this.formList[i]
        if(item.sceneNodeId === node.id) {
          let {x, y} = node.getPosition()
          item.nodeX = x
          item.nodeY = y
        }
      }
    },
    updateNode(e, x, y, node, view) { // 修改节点
      let form = this.formList.find(i => i.sceneNodeId === node.id)
      if (form.sceneNodeType !== 'start' && form.sceneNodeType !== 'end') {
        this.form = JSON.parse(JSON.stringify(form))
        this.drawerShow = true
      }
    },
    saveNode() { // 保存节点
      this.drawerShow = false
      // 将节点存储到数据
      let index = this.formList.findIndex(i => i.sceneNodeId === this.form.sceneNodeId)
      this.formList.splice(index, 1, JSON.parse(JSON.stringify(this.form)))
      // 修改节点数据
      let cls = this.graph.getNodes().find(i => i.id === this.form.sceneNodeId)
      cls.label = this.form.sceneNodeName
    },

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值