1
This commit is contained in:
parent
3b9ef50244
commit
3038debaef
@ -18,12 +18,16 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import {Graph, Cell} from '@antv/x6';
|
||||
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-clipboard';
|
||||
import '@antv/x6-plugin-transform';
|
||||
import { Selection } from '@antv/x6-plugin-selection';
|
||||
import { MiniMap } from '@antv/x6-plugin-minimap';
|
||||
import { Clipboard } from '@antv/x6-plugin-clipboard';
|
||||
import { History } from '@antv/x6-plugin-history';
|
||||
import { Transform } from '@antv/x6-plugin-transform';
|
||||
import {getDefinitionDetail, saveDefinition} from '../service';
|
||||
import {getNodeDefinitionList} from './service';
|
||||
import NodePanel from './components/NodePanel';
|
||||
@ -79,9 +83,73 @@ const WorkflowDesign: React.FC = () => {
|
||||
|
||||
const graph = new Graph({
|
||||
container: graphContainerRef.current,
|
||||
grid: GRID_CONFIG,
|
||||
connecting: CONNECTING_CONFIG,
|
||||
highlighting: HIGHLIGHTING_CONFIG,
|
||||
grid: {
|
||||
size: 10,
|
||||
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: {
|
||||
enabled: true,
|
||||
},
|
||||
@ -90,23 +158,24 @@ const WorkflowDesign: React.FC = () => {
|
||||
multiple: true,
|
||||
rubberband: true,
|
||||
movable: true,
|
||||
showNodeSelectionBox: true,
|
||||
strict: true,
|
||||
selectCellOnMoved: true,
|
||||
selectNodeOnMoved: true,
|
||||
selectEdgeOnMoved: true,
|
||||
multipleSelectionModifiers: ['ctrl', 'meta'],
|
||||
showEdgeSelectionBox: true,
|
||||
showAnchorSelectionBox: true,
|
||||
pointerEvents: 'auto'
|
||||
showNodeSelectionBox: false, // 禁用节点选择框
|
||||
showEdgeSelectionBox: false, // 禁用边选择框
|
||||
selectNodeOnMoved: false,
|
||||
selectEdgeOnMoved: false,
|
||||
},
|
||||
snapline: true,
|
||||
keyboard: {
|
||||
enabled: true,
|
||||
},
|
||||
panning: {
|
||||
enabled: true,
|
||||
eventTypes: ['rightMouseDown'], // 右键按下时启用画布拖拽
|
||||
},
|
||||
mousewheel: {
|
||||
enabled: true,
|
||||
modifiers: ['ctrl', 'meta'],
|
||||
minScale: 0.2,
|
||||
maxScale: 2,
|
||||
},
|
||||
});
|
||||
|
||||
@ -137,14 +206,13 @@ const WorkflowDesign: React.FC = () => {
|
||||
multiple: true,
|
||||
rubberband: true,
|
||||
movable: true,
|
||||
showNodeSelectionBox: true,
|
||||
strict: true,
|
||||
selectCellOnMoved: true,
|
||||
selectNodeOnMoved: true,
|
||||
selectEdgeOnMoved: true,
|
||||
showNodeSelectionBox: false, // 禁用节点选择框
|
||||
showEdgeSelectionBox: false, // 禁用边选择框
|
||||
selectNodeOnMoved: false,
|
||||
selectEdgeOnMoved: false,
|
||||
multipleSelectionModifiers: ['ctrl', 'meta'],
|
||||
showEdgeSelectionBox: true,
|
||||
showAnchorSelectionBox: true,
|
||||
showEdgeSelectionBox: false,
|
||||
showAnchorSelectionBox: false,
|
||||
pointerEvents: 'auto'
|
||||
});
|
||||
console.log('Initializing Selection plugin:', selection);
|
||||
@ -188,6 +256,12 @@ const WorkflowDesign: React.FC = () => {
|
||||
}));
|
||||
graph.use(new Clipboard());
|
||||
graph.use(history);
|
||||
graph.use(
|
||||
new Transform({
|
||||
resizing: false,
|
||||
rotating: false,
|
||||
})
|
||||
);
|
||||
|
||||
// 扩展 graph 对象,添加 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);
|
||||
|
||||
return graph;
|
||||
@ -378,7 +504,7 @@ const WorkflowDesign: React.FC = () => {
|
||||
// 显示连接桩
|
||||
const ports = document.querySelectorAll(`[data-cell-id="${node.id}"] .x6-port-body`);
|
||||
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);
|
||||
});
|
||||
|
||||
// 禁用默认的右键菜单
|
||||
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);
|
||||
if (sourceNode && targetNode) {
|
||||
graphInstance.addEdge({
|
||||
source: {cell: sourceNode.id},
|
||||
target: {cell: targetNode.id},
|
||||
attrs: {line: DEFAULT_STYLES.edge},
|
||||
source: {
|
||||
cell: sourceNode.id,
|
||||
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: [{
|
||||
attrs: {label: {text: edge.name || ''}}
|
||||
}]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user