增加工具栏提示。

This commit is contained in:
dengqichen 2024-12-12 18:21:15 +08:00
parent 93d47eab40
commit ca749aa7af
2 changed files with 249 additions and 130 deletions

View File

@ -0,0 +1,155 @@
// 节点端口配置
export const PORT_GROUPS = ['top', 'right', 'bottom', 'left'] as const;
// 默认样式配置
export const DEFAULT_STYLES = {
node: {
stroke: '#5F95FF',
strokeWidth: 2,
fill: '#FFF',
},
label: {
fontSize: 12,
fill: '#000000',
},
port: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
edge: {
stroke: '#1890ff',
strokeWidth: 2,
targetMarker: {
name: 'classic',
size: 8,
},
},
} as const;
// 默认节点大小
export const NODE_SIZES = {
circle: {
width: 60,
height: 60,
},
rectangle: {
width: 100,
height: 50,
},
diamond: {
width: 60,
height: 60,
},
} as const;
// 生成端口组配置
export const generatePortGroups = () => {
const groups: Record<string, any> = {};
PORT_GROUPS.forEach(position => {
groups[position] = {
position,
attrs: {
circle: { ...DEFAULT_STYLES.port },
},
};
});
return groups;
};
// 生成端口项配置
export const generatePortItems = () =>
PORT_GROUPS.map(group => ({ group }));
// 网格配置
export const GRID_CONFIG = {
size: 10,
visible: true,
type: 'dot',
args: {
color: '#a0a0a0',
thickness: 1,
},
} as const;
// 连接配置
export const CONNECTING_CONFIG = {
router: 'manhattan',
connector: {
name: 'rounded',
args: {
radius: 8,
},
},
anchor: 'center',
connectionPoint: 'anchor',
allowBlank: false,
snap: true,
} as const;
// 高亮配置
export const HIGHLIGHTING_CONFIG = {
magnetAvailable: {
name: 'stroke',
args: {
padding: 4,
attrs: {
strokeWidth: 4,
stroke: '#52c41a',
},
},
},
} as const;
// 节点注册配置
export const NODE_REGISTRY_CONFIG = {
circle: {
inherit: 'circle',
width: NODE_SIZES.circle.width,
height: NODE_SIZES.circle.height,
attrs: {
body: DEFAULT_STYLES.node,
label: DEFAULT_STYLES.label,
},
ports: {
groups: generatePortGroups(),
items: generatePortItems(),
},
},
rectangle: {
inherit: 'rect',
width: NODE_SIZES.rectangle.width,
height: NODE_SIZES.rectangle.height,
attrs: {
body: DEFAULT_STYLES.node,
label: DEFAULT_STYLES.label,
},
ports: {
groups: generatePortGroups(),
items: generatePortItems(),
},
},
diamond: {
inherit: 'polygon',
width: NODE_SIZES.diamond.width,
height: NODE_SIZES.diamond.height,
attrs: {
body: {
...DEFAULT_STYLES.node,
refPoints: '0,10 10,0 20,10 10,20',
},
label: DEFAULT_STYLES.label,
},
ports: {
groups: generatePortGroups(),
items: generatePortItems(),
},
},
} as const;

View File

@ -2,10 +2,17 @@ import React, { useEffect, useState, useRef } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Button, Space, Card, Row, Col } from 'antd';
import { ArrowLeftOutlined, SaveOutlined, PlayCircleOutlined } from '@ant-design/icons';
import { Graph } from '@antv/x6';
import { Graph, Shape } from '@antv/x6';
import { getDefinitionDetail } from '../service';
import NodePanel from './components/NodePanel';
import { NodeDefinition } from './types';
import {
NODE_REGISTRY_CONFIG,
GRID_CONFIG,
CONNECTING_CONFIG,
HIGHLIGHTING_CONFIG,
DEFAULT_STYLES, generatePortGroups, generatePortItems,
} from './constants';
const WorkflowDesign: React.FC = () => {
const { id } = useParams<{ id: string }>();
@ -22,52 +29,47 @@ const WorkflowDesign: React.FC = () => {
useEffect(() => {
if (graphContainerRef.current) {
// 注册自定义节点
Object.entries(NODE_REGISTRY_CONFIG).forEach(([name, config]) => {
Graph.registerNode(name, config, true);
});
const graph = new Graph({
container: graphContainerRef.current,
grid: {
size: 10,
visible: true,
type: 'dot',
args: {
color: '#a0a0a0',
thickness: 1,
},
},
grid: GRID_CONFIG,
connecting: {
router: 'manhattan',
connector: {
name: 'rounded',
args: {
radius: 8,
...CONNECTING_CONFIG,
validateConnection({ sourceView, targetView, sourceMagnet, targetMagnet }) {
if (!sourceMagnet || !targetMagnet) {
return false;
}
if (sourceView === targetView) {
return false;
}
return true;
},
validateMagnet({ magnet }) {
const portGroup = magnet?.getAttribute('port-group');
return !!portGroup;
},
anchor: 'center',
connectionPoint: 'anchor',
allowBlank: false,
snap: true,
createEdge() {
return this.createEdge({
attrs: {
line: {
stroke: '#1890ff',
strokeWidth: 2,
targetMarker: {
name: 'classic',
size: 8,
},
},
line: DEFAULT_STYLES.edge,
},
zIndex: -1,
});
},
},
highlighting: {
magnetAvailable: {
...HIGHLIGHTING_CONFIG,
magnetAdsorbed: {
name: 'stroke',
args: {
padding: 4,
attrs: {
fill: '#fff',
stroke: '#31d0c6',
strokeWidth: 4,
stroke: '#52c41a',
},
},
},
@ -82,6 +84,21 @@ const WorkflowDesign: React.FC = () => {
},
});
// 显示/隐藏连接桩
graph.on('node:mouseenter', ({ node }) => {
const ports = document.querySelectorAll(`[data-cell-id="${node.id}"] .x6-port-body`);
ports.forEach((port) => {
port.setAttribute('style', 'visibility: visible');
});
});
graph.on('node:mouseleave', ({ node }) => {
const ports = document.querySelectorAll(`[data-cell-id="${node.id}"] .x6-port-body`);
ports.forEach((port) => {
port.setAttribute('style', 'visibility: hidden');
});
});
setGraph(graph);
return () => {
@ -93,16 +110,22 @@ const WorkflowDesign: React.FC = () => {
const loadDefinitionDetail = async () => {
try {
const response = await getDefinitionDetail(id);
if (response.success) {
setTitle(`工作流设计 - ${response.data.name}`);
}
setTitle(`工作流设计 - ${response.name}`);
} catch (error) {
console.error('Failed to load workflow definition:', error);
}
};
const handleNodeDragStart = (node: NodeDefinition, e: React.DragEvent) => {
e.dataTransfer.setData('node', JSON.stringify(node));
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
if (graph) {
const nodeData = e.dataTransfer.getData('node');
if (nodeData) {
const node = JSON.parse(nodeData);
const { clientX, clientY } = e;
const point = graph.clientToLocal({ x: clientX, y: clientY });
@ -123,74 +146,8 @@ const WorkflowDesign: React.FC = () => {
},
},
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: [
{ group: 'top' },
{ group: 'right' },
{ group: 'bottom' },
{ group: 'left' },
],
groups: generatePortGroups(),
items: generatePortItems(),
},
};
@ -200,6 +157,11 @@ const WorkflowDesign: React.FC = () => {
y: point.y,
});
}
}
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
};
return (
@ -253,6 +215,8 @@ const WorkflowDesign: React.FC = () => {
width: '100%',
height: '100%',
}}
onDrop={handleDrop}
onDragOver={handleDragOver}
/>
</Card>
</Col>