增加工具栏提示。
This commit is contained in:
parent
93d47eab40
commit
ca749aa7af
155
frontend/src/pages/Workflow/Definition/Design/constants.ts
Normal file
155
frontend/src/pages/Workflow/Definition/Design/constants.ts
Normal 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;
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user