This commit is contained in:
asp_ly 2024-12-14 16:15:45 +08:00
parent 3b9ef50244
commit 3038debaef

View File

@ -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 || ''}}
}] }]