数据准备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
},