1
This commit is contained in:
parent
3b9ef50244
commit
3038debaef
@ -18,12 +18,16 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import {Graph, Cell} from '@antv/x6';
|
import {Graph, Cell} from '@antv/x6';
|
||||||
import '@antv/x6-plugin-snapline';
|
import '@antv/x6-plugin-snapline';
|
||||||
import '@antv/x6-plugin-clipboard';
|
import '@antv/x6-plugin-selection';
|
||||||
|
import '@antv/x6-plugin-keyboard';
|
||||||
import '@antv/x6-plugin-history';
|
import '@antv/x6-plugin-history';
|
||||||
|
import '@antv/x6-plugin-clipboard';
|
||||||
|
import '@antv/x6-plugin-transform';
|
||||||
import { Selection } from '@antv/x6-plugin-selection';
|
import { Selection } from '@antv/x6-plugin-selection';
|
||||||
import { MiniMap } from '@antv/x6-plugin-minimap';
|
import { MiniMap } from '@antv/x6-plugin-minimap';
|
||||||
import { Clipboard } from '@antv/x6-plugin-clipboard';
|
import { Clipboard } from '@antv/x6-plugin-clipboard';
|
||||||
import { History } from '@antv/x6-plugin-history';
|
import { History } from '@antv/x6-plugin-history';
|
||||||
|
import { Transform } from '@antv/x6-plugin-transform';
|
||||||
import {getDefinitionDetail, saveDefinition} from '../service';
|
import {getDefinitionDetail, saveDefinition} from '../service';
|
||||||
import {getNodeDefinitionList} from './service';
|
import {getNodeDefinitionList} from './service';
|
||||||
import NodePanel from './components/NodePanel';
|
import NodePanel from './components/NodePanel';
|
||||||
@ -79,9 +83,73 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
|
|
||||||
const graph = new Graph({
|
const graph = new Graph({
|
||||||
container: graphContainerRef.current,
|
container: graphContainerRef.current,
|
||||||
grid: GRID_CONFIG,
|
grid: {
|
||||||
connecting: CONNECTING_CONFIG,
|
size: 10,
|
||||||
highlighting: HIGHLIGHTING_CONFIG,
|
visible: true,
|
||||||
|
type: 'dot',
|
||||||
|
args: {
|
||||||
|
color: '#a0a0a0',
|
||||||
|
thickness: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connecting: {
|
||||||
|
snap: true, // 连线时自动吸附
|
||||||
|
allowBlank: false, // 禁止连线到空白位置
|
||||||
|
allowLoop: false, // 禁止自环
|
||||||
|
allowNode: false, // 禁止连接到节点(只允许连接到连接桩)
|
||||||
|
allowEdge: false, // 禁止边连接到边
|
||||||
|
connector: {
|
||||||
|
name: 'rounded',
|
||||||
|
args: {
|
||||||
|
radius: 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
router: {
|
||||||
|
name: 'manhattan',
|
||||||
|
args: {
|
||||||
|
padding: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validateConnection({sourceCell, targetCell, sourceMagnet, targetMagnet}) {
|
||||||
|
if (sourceCell === targetCell) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!sourceMagnet || !targetMagnet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 检查是否已存在相同的连接
|
||||||
|
const edges = graph.getEdges();
|
||||||
|
const exists = edges.some(edge => {
|
||||||
|
const source = edge.getSource();
|
||||||
|
const target = edge.getTarget();
|
||||||
|
return (
|
||||||
|
source.cell === sourceCell.id &&
|
||||||
|
target.cell === targetCell.id &&
|
||||||
|
source.port === sourceMagnet.getAttribute('port') &&
|
||||||
|
target.port === targetMagnet.getAttribute('port')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return !exists;
|
||||||
|
},
|
||||||
|
validateMagnet({ magnet }) {
|
||||||
|
return magnet.getAttribute('port-group') !== 'top';
|
||||||
|
},
|
||||||
|
validateEdge({ edge }) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
highlighting: {
|
||||||
|
magnetAvailable: {
|
||||||
|
name: 'stroke',
|
||||||
|
args: {
|
||||||
|
padding: 4,
|
||||||
|
attrs: {
|
||||||
|
strokeWidth: 4,
|
||||||
|
stroke: '#52c41a',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
clipboard: {
|
clipboard: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
@ -90,23 +158,24 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
multiple: true,
|
multiple: true,
|
||||||
rubberband: true,
|
rubberband: true,
|
||||||
movable: true,
|
movable: true,
|
||||||
showNodeSelectionBox: true,
|
showNodeSelectionBox: false, // 禁用节点选择框
|
||||||
strict: true,
|
showEdgeSelectionBox: false, // 禁用边选择框
|
||||||
selectCellOnMoved: true,
|
selectNodeOnMoved: false,
|
||||||
selectNodeOnMoved: true,
|
selectEdgeOnMoved: false,
|
||||||
selectEdgeOnMoved: true,
|
|
||||||
multipleSelectionModifiers: ['ctrl', 'meta'],
|
|
||||||
showEdgeSelectionBox: true,
|
|
||||||
showAnchorSelectionBox: true,
|
|
||||||
pointerEvents: 'auto'
|
|
||||||
},
|
},
|
||||||
snapline: true,
|
snapline: true,
|
||||||
keyboard: {
|
keyboard: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
panning: {
|
||||||
|
enabled: true,
|
||||||
|
eventTypes: ['rightMouseDown'], // 右键按下时启用画布拖拽
|
||||||
|
},
|
||||||
mousewheel: {
|
mousewheel: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
modifiers: ['ctrl', 'meta'],
|
modifiers: ['ctrl', 'meta'],
|
||||||
|
minScale: 0.2,
|
||||||
|
maxScale: 2,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -137,14 +206,13 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
multiple: true,
|
multiple: true,
|
||||||
rubberband: true,
|
rubberband: true,
|
||||||
movable: true,
|
movable: true,
|
||||||
showNodeSelectionBox: true,
|
showNodeSelectionBox: false, // 禁用节点选择框
|
||||||
strict: true,
|
showEdgeSelectionBox: false, // 禁用边选择框
|
||||||
selectCellOnMoved: true,
|
selectNodeOnMoved: false,
|
||||||
selectNodeOnMoved: true,
|
selectEdgeOnMoved: false,
|
||||||
selectEdgeOnMoved: true,
|
|
||||||
multipleSelectionModifiers: ['ctrl', 'meta'],
|
multipleSelectionModifiers: ['ctrl', 'meta'],
|
||||||
showEdgeSelectionBox: true,
|
showEdgeSelectionBox: false,
|
||||||
showAnchorSelectionBox: true,
|
showAnchorSelectionBox: false,
|
||||||
pointerEvents: 'auto'
|
pointerEvents: 'auto'
|
||||||
});
|
});
|
||||||
console.log('Initializing Selection plugin:', selection);
|
console.log('Initializing Selection plugin:', selection);
|
||||||
@ -188,6 +256,12 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
}));
|
}));
|
||||||
graph.use(new Clipboard());
|
graph.use(new Clipboard());
|
||||||
graph.use(history);
|
graph.use(history);
|
||||||
|
graph.use(
|
||||||
|
new Transform({
|
||||||
|
resizing: false,
|
||||||
|
rotating: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// 扩展 graph 对象,添加 history 属性
|
// 扩展 graph 对象,添加 history 属性
|
||||||
(graph as any).history = history;
|
(graph as any).history = history;
|
||||||
@ -243,6 +317,58 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 处理连线重新连接
|
||||||
|
graph.on('edge:moved', ({ edge, terminal, previous }) => {
|
||||||
|
if (!edge || !terminal) return;
|
||||||
|
|
||||||
|
const isSource = terminal.type === 'source';
|
||||||
|
const source = isSource ? terminal : edge.getSource();
|
||||||
|
const target = isSource ? edge.getTarget() : terminal;
|
||||||
|
|
||||||
|
if (source && target) {
|
||||||
|
// 移除旧的连线
|
||||||
|
edge.remove();
|
||||||
|
|
||||||
|
// 创建新的连线
|
||||||
|
graph.addEdge({
|
||||||
|
source: {
|
||||||
|
cell: source.cell,
|
||||||
|
port: source.port,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
cell: target.cell,
|
||||||
|
port: target.port,
|
||||||
|
},
|
||||||
|
attrs: {
|
||||||
|
line: {
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
strokeWidth: 2,
|
||||||
|
targetMarker: {
|
||||||
|
name: 'classic',
|
||||||
|
size: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理连线更改
|
||||||
|
graph.on('edge:change:source edge:change:target', ({ edge, current, previous }) => {
|
||||||
|
if (edge && current) {
|
||||||
|
edge.setAttrs({
|
||||||
|
line: {
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
strokeWidth: 2,
|
||||||
|
targetMarker: {
|
||||||
|
name: 'classic',
|
||||||
|
size: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
registerEventHandlers(graph);
|
registerEventHandlers(graph);
|
||||||
|
|
||||||
return graph;
|
return graph;
|
||||||
@ -378,7 +504,7 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
// 显示连接桩
|
// 显示连接桩
|
||||||
const ports = document.querySelectorAll(`[data-cell-id="${node.id}"] .x6-port-body`);
|
const ports = document.querySelectorAll(`[data-cell-id="${node.id}"] .x6-port-body`);
|
||||||
ports.forEach((port) => {
|
ports.forEach((port) => {
|
||||||
port.setAttribute('style', 'visibility: visible');
|
port.setAttribute('style', 'visibility: visible; fill: #fff; stroke: #85ca6d;');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 显示悬停样式
|
// 显示悬停样式
|
||||||
@ -590,6 +716,160 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
};
|
};
|
||||||
document.addEventListener('click', handleClickOutside);
|
document.addEventListener('click', handleClickOutside);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 禁用默认的右键菜单
|
||||||
|
graph.on('blank:contextmenu', ({ e }) => {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 禁用节点的右键菜单
|
||||||
|
graph.on('node:contextmenu', ({ e }) => {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 禁用边的右键菜单
|
||||||
|
graph.on('edge:contextmenu', ({ e }) => {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 连接桩显示/隐藏
|
||||||
|
graph.on('node:mouseenter', ({node}) => {
|
||||||
|
// 保存原始样式
|
||||||
|
saveNodeOriginalStyle(node);
|
||||||
|
|
||||||
|
// 显示连接桩
|
||||||
|
const ports = document.querySelectorAll(`[data-cell-id="${node.id}"] .x6-port-body`);
|
||||||
|
ports.forEach((port) => {
|
||||||
|
port.setAttribute('style', 'visibility: visible; fill: #fff; stroke: #85ca6d;');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示悬停样式
|
||||||
|
node.setAttrByPath('body/stroke', hoverStyle.stroke);
|
||||||
|
node.setAttrByPath('body/strokeWidth', hoverStyle.strokeWidth);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 连线开始时的处理
|
||||||
|
graph.on('edge:connected', ({ edge }) => {
|
||||||
|
// 设置连线样式
|
||||||
|
edge.setAttrs({
|
||||||
|
line: {
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
strokeWidth: 2,
|
||||||
|
targetMarker: {
|
||||||
|
name: 'classic',
|
||||||
|
size: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 连线悬停效果
|
||||||
|
graph.on('edge:mouseenter', ({ edge }) => {
|
||||||
|
edge.setAttrs({
|
||||||
|
line: {
|
||||||
|
stroke: '#52c41a',
|
||||||
|
strokeWidth: 3,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.on('edge:mouseleave', ({ edge }) => {
|
||||||
|
edge.setAttrs({
|
||||||
|
line: {
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
strokeWidth: 2,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 允许拖动连线中间的点和端点
|
||||||
|
graph.on('edge:selected', ({ edge }) => {
|
||||||
|
edge.addTools([
|
||||||
|
{
|
||||||
|
name: 'source-arrowhead',
|
||||||
|
args: {
|
||||||
|
attrs: {
|
||||||
|
fill: '#fff',
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
strokeWidth: 1,
|
||||||
|
d: 'M 0 -6 L -8 0 L 0 6 Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'target-arrowhead',
|
||||||
|
args: {
|
||||||
|
attrs: {
|
||||||
|
fill: '#fff',
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
strokeWidth: 1,
|
||||||
|
d: 'M 0 -6 L 8 0 L 0 6 Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vertices',
|
||||||
|
args: {
|
||||||
|
padding: 2,
|
||||||
|
attrs: {
|
||||||
|
fill: '#fff',
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
strokeWidth: 1,
|
||||||
|
r: 4
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'segments',
|
||||||
|
args: {
|
||||||
|
padding: 2,
|
||||||
|
attrs: {
|
||||||
|
fill: '#fff',
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
strokeWidth: 1,
|
||||||
|
r: 4
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 连线工具移除
|
||||||
|
graph.on('edge:unselected', ({ edge }) => {
|
||||||
|
edge.removeTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 连线移动到其他连接桩时的样式
|
||||||
|
graph.on('edge:connected', ({ edge }) => {
|
||||||
|
edge.setAttrs({
|
||||||
|
line: {
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
strokeWidth: 2,
|
||||||
|
targetMarker: {
|
||||||
|
name: 'classic',
|
||||||
|
size: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 连线悬停在连接桩上时的样式
|
||||||
|
graph.on('edge:mouseenter', ({ edge }) => {
|
||||||
|
const ports = document.querySelectorAll('.x6-port-body');
|
||||||
|
ports.forEach((port) => {
|
||||||
|
const portGroup = port.getAttribute('port-group');
|
||||||
|
if (portGroup !== 'top') {
|
||||||
|
port.setAttribute('style', 'visibility: visible; fill: #fff; stroke: #85ca6d;');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.on('edge:mouseleave', ({ edge }) => {
|
||||||
|
const ports = document.querySelectorAll('.x6-port-body');
|
||||||
|
ports.forEach((port) => {
|
||||||
|
port.setAttribute('style', 'visibility: hidden');
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理复制操作
|
// 处理复制操作
|
||||||
@ -674,9 +954,60 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
const targetNode = nodeMap.get(edge.to);
|
const targetNode = nodeMap.get(edge.to);
|
||||||
if (sourceNode && targetNode) {
|
if (sourceNode && targetNode) {
|
||||||
graphInstance.addEdge({
|
graphInstance.addEdge({
|
||||||
source: {cell: sourceNode.id},
|
source: {
|
||||||
target: {cell: targetNode.id},
|
cell: sourceNode.id,
|
||||||
attrs: {line: DEFAULT_STYLES.edge},
|
port: edge.fromPort || sourceNode.getPorts()[0].id, // 使用指定的端口或默认第一个端口
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
cell: targetNode.id,
|
||||||
|
port: edge.toPort || targetNode.getPorts()[0].id, // 使用指定的端口或默认第一个端口
|
||||||
|
},
|
||||||
|
attrs: {
|
||||||
|
line: {
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
strokeWidth: 2,
|
||||||
|
targetMarker: {
|
||||||
|
name: 'classic',
|
||||||
|
size: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tools: [
|
||||||
|
{
|
||||||
|
name: 'source-arrowhead',
|
||||||
|
args: {
|
||||||
|
attrs: {
|
||||||
|
fill: '#fff',
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
strokeWidth: 1,
|
||||||
|
d: 'M 0 -6 L -8 0 L 0 6 Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'target-arrowhead',
|
||||||
|
args: {
|
||||||
|
attrs: {
|
||||||
|
fill: '#fff',
|
||||||
|
stroke: '#5F95FF',
|
||||||
|
strokeWidth: 1,
|
||||||
|
d: 'M 0 -6 L 8 0 L 0 6 Z',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
connector: {
|
||||||
|
name: 'rounded',
|
||||||
|
args: {
|
||||||
|
radius: 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
router: {
|
||||||
|
name: 'manhattan',
|
||||||
|
args: {
|
||||||
|
padding: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
labels: [{
|
labels: [{
|
||||||
attrs: {label: {text: edge.name || ''}}
|
attrs: {label: {text: edge.name || ''}}
|
||||||
}]
|
}]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user