增加工具栏提示。
This commit is contained in:
parent
989abcae89
commit
9e35ac490e
@ -3,18 +3,18 @@ import { useParams, useNavigate } from 'react-router-dom';
|
|||||||
import { Button, Space, Card, Row, Col, message } from 'antd';
|
import { Button, Space, Card, Row, Col, message } from 'antd';
|
||||||
import { ArrowLeftOutlined, SaveOutlined, PlayCircleOutlined } from '@ant-design/icons';
|
import { ArrowLeftOutlined, SaveOutlined, PlayCircleOutlined } from '@ant-design/icons';
|
||||||
import { Graph, Cell } from '@antv/x6';
|
import { Graph, Cell } from '@antv/x6';
|
||||||
import dagre from 'dagre';
|
|
||||||
import { getDefinitionDetail, saveDefinition } from '../service';
|
import { getDefinitionDetail, saveDefinition } from '../service';
|
||||||
import NodePanel from './components/NodePanel';
|
import NodePanel from './components/NodePanel';
|
||||||
import NodeConfigDrawer from './components/NodeConfigModal';
|
import NodeConfigDrawer from './components/NodeConfigModal';
|
||||||
import { NodeDefinition } from './types';
|
import { NodeDefinition } from './types';
|
||||||
import { validateWorkflow } from './utils/validator';
|
import { validateWorkflow } from './utils/validator';
|
||||||
|
import { addNodeToGraph } from './utils/nodeUtils';
|
||||||
|
import { applyAutoLayout } from './utils/layoutUtils';
|
||||||
import {
|
import {
|
||||||
GRID_CONFIG,
|
GRID_CONFIG,
|
||||||
CONNECTING_CONFIG,
|
CONNECTING_CONFIG,
|
||||||
HIGHLIGHTING_CONFIG,
|
HIGHLIGHTING_CONFIG,
|
||||||
DEFAULT_STYLES,
|
DEFAULT_STYLES,
|
||||||
convertPortConfig
|
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
|
||||||
const WorkflowDesign: React.FC = () => {
|
const WorkflowDesign: React.FC = () => {
|
||||||
@ -96,60 +96,16 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
const loadDefinitionDetail = async (graphInstance: Graph, definitionId: number) => {
|
const loadDefinitionDetail = async (graphInstance: Graph, definitionId: number) => {
|
||||||
try {
|
try {
|
||||||
const response = await getDefinitionDetail(definitionId);
|
const response = await getDefinitionDetail(definitionId);
|
||||||
console.log('加载到的工作流数据:', response);
|
|
||||||
setTitle(`工作流设计 - ${response.name}`);
|
setTitle(`工作流设计 - ${response.name}`);
|
||||||
setDefinitionData(response);
|
setDefinitionData(response);
|
||||||
|
|
||||||
// 清空画布
|
// 清空画布
|
||||||
graphInstance.clearCells();
|
graphInstance.clearCells();
|
||||||
|
|
||||||
const nodeMap = new Map();
|
const nodeMap = new Map();
|
||||||
|
|
||||||
// 动态注册节点类型
|
// 创建节点
|
||||||
response.graph.nodes.forEach((nodeData: any) => {
|
response.graph.nodes.forEach((nodeData: any) => {
|
||||||
// 根据节点类型动态注册
|
const node = addNodeToGraph(graphInstance, nodeData);
|
||||||
const nodeConfig = {
|
|
||||||
inherit: nodeData.graph.shape === 'circle' ? 'circle' :
|
|
||||||
nodeData.graph.shape === 'polygon' ? 'polygon' :
|
|
||||||
nodeData.graph.shape === 'diamond' ? 'polygon' : 'rect',
|
|
||||||
width: nodeData.graph.size.width,
|
|
||||||
height: nodeData.graph.size.height,
|
|
||||||
attrs: {
|
|
||||||
body: {
|
|
||||||
...nodeData.graph.style,
|
|
||||||
// 如果是菱形,添加 refPoints
|
|
||||||
...(nodeData.graph.shape === 'diamond' ? {
|
|
||||||
refPoints: '50,0 100,50 50,100 0,50'
|
|
||||||
} : {})
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
text: nodeData.name,
|
|
||||||
fill: '#000000',
|
|
||||||
fontSize: 12,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ports: convertPortConfig(nodeData.graph.ports)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 注册节点类型
|
|
||||||
Graph.registerNode(nodeData.id, nodeConfig, true);
|
|
||||||
|
|
||||||
// 创建节点
|
|
||||||
const node = graphInstance.addNode({
|
|
||||||
id: nodeData.id,
|
|
||||||
shape: nodeData.id,
|
|
||||||
position: nodeData.graph.position || { x: 100, y: 100 },
|
|
||||||
attrs: {
|
|
||||||
body: nodeData.graph.style,
|
|
||||||
label: {
|
|
||||||
text: nodeData.name,
|
|
||||||
fill: '#000000',
|
|
||||||
fontSize: 12,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 保存完整的节点信息用于后续操作
|
|
||||||
nodeDefinition: nodeData,
|
|
||||||
});
|
|
||||||
nodeMap.set(nodeData.id, node);
|
nodeMap.set(nodeData.id, node);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -161,62 +117,16 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
graphInstance.addEdge({
|
graphInstance.addEdge({
|
||||||
source: { cell: sourceNode.id },
|
source: { cell: sourceNode.id },
|
||||||
target: { cell: targetNode.id },
|
target: { cell: targetNode.id },
|
||||||
attrs: {
|
attrs: { line: DEFAULT_STYLES.edge },
|
||||||
line: DEFAULT_STYLES.edge
|
labels: [{
|
||||||
},
|
attrs: { label: { text: edge.name || '' } }
|
||||||
labels: [
|
}]
|
||||||
{
|
|
||||||
attrs: {
|
|
||||||
label: {
|
|
||||||
text: edge.name || '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 自动布局
|
// 应用自动布局
|
||||||
const g = new dagre.graphlib.Graph();
|
applyAutoLayout(graphInstance);
|
||||||
g.setGraph({
|
|
||||||
rankdir: 'LR',
|
|
||||||
align: 'UL',
|
|
||||||
ranksep: 80,
|
|
||||||
nodesep: 50,
|
|
||||||
marginx: 20,
|
|
||||||
marginy: 20,
|
|
||||||
});
|
|
||||||
g.setDefaultEdgeLabel(() => ({}));
|
|
||||||
|
|
||||||
// 添加节点
|
|
||||||
const nodes = graphInstance.getNodes();
|
|
||||||
nodes.forEach(node => {
|
|
||||||
g.setNode(node.id, {
|
|
||||||
width: node.getSize().width,
|
|
||||||
height: node.getSize().height,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加边
|
|
||||||
const edges = graphInstance.getEdges();
|
|
||||||
edges.forEach(edge => {
|
|
||||||
g.setEdge(edge.getSourceCellId(), edge.getTargetCellId());
|
|
||||||
});
|
|
||||||
|
|
||||||
// 执行布局
|
|
||||||
dagre.layout(g);
|
|
||||||
|
|
||||||
// 应用布局结果
|
|
||||||
nodes.forEach(node => {
|
|
||||||
const nodeWithPosition = g.node(node.id);
|
|
||||||
node.position(
|
|
||||||
nodeWithPosition.x - nodeWithPosition.width / 2,
|
|
||||||
nodeWithPosition.y - nodeWithPosition.height / 2
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
graphInstance.centerContent();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载工作流定义失败:', error);
|
console.error('加载工作流定义失败:', error);
|
||||||
message.error('加载工作流定义失败');
|
message.error('加载工作流定义失败');
|
||||||
@ -229,55 +139,20 @@ const WorkflowDesign: React.FC = () => {
|
|||||||
|
|
||||||
const handleDrop = (e: React.DragEvent) => {
|
const handleDrop = (e: React.DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (graph) {
|
if (!graph) return;
|
||||||
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 });
|
|
||||||
console.log('Node Config:', node);
|
|
||||||
console.log('Ports Config:', node.graphConfig.uiSchema.ports);
|
|
||||||
console.log('Converted Ports:', convertPortConfig(node.graphConfig.uiSchema.ports));
|
|
||||||
const nodeConfig = {
|
|
||||||
inherit: node.graphConfig.uiSchema.shape === 'circle' ? 'circle' :
|
|
||||||
node.graphConfig.uiSchema.shape === 'polygon' ? 'polygon' :
|
|
||||||
node.graphConfig.uiSchema.shape === 'diamond' ? 'polygon' : 'rect',
|
|
||||||
width: node.graphConfig.uiSchema.size.width,
|
|
||||||
height: node.graphConfig.uiSchema.size.height,
|
|
||||||
attrs: {
|
|
||||||
body: {
|
|
||||||
...node.graphConfig.uiSchema.style,
|
|
||||||
// 如果是菱形,添加 refPoints
|
|
||||||
...(node.graphConfig.uiSchema.shape === 'diamond' ? {
|
|
||||||
refPoints: '50,0 100,50 50,100 0,50'
|
|
||||||
} : {})
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
text: node.name,
|
|
||||||
fill: '#000000',
|
|
||||||
fontSize: 12,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// ports: convertPortConfig(node.graphConfig.uiSchema.ports)
|
|
||||||
};
|
|
||||||
// 注册节点类型
|
|
||||||
Graph.registerNode(node.code, nodeConfig, true);
|
|
||||||
|
|
||||||
// 创建节点时设置完整的属性
|
try {
|
||||||
graph.addNode({
|
const nodeData = e.dataTransfer.getData('node');
|
||||||
...nodeConfig,
|
if (!nodeData) return;
|
||||||
shape: node.graphConfig.uiSchema.shape,
|
|
||||||
x: point.x,
|
const node = JSON.parse(nodeData);
|
||||||
y: point.y,
|
const { clientX, clientY } = e;
|
||||||
// 保存完整的节点信息
|
const point = graph.clientToLocal({ x: clientX, y: clientY });
|
||||||
type: node.type,
|
|
||||||
code: node.code,
|
addNodeToGraph(graph, node, point);
|
||||||
// ports: nodeConfig.ports,
|
} catch (error) {
|
||||||
ports: convertPortConfig(node.graphConfig.uiSchema.ports),
|
console.error('创建节点失败:', error);
|
||||||
// 保存节点定义,用于后续编辑
|
message.error('创建节点失败');
|
||||||
nodeDefinition: node,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
import { Graph } from '@antv/x6';
|
||||||
|
import dagre from 'dagre';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用自动布局
|
||||||
|
* @param graph 图形实例
|
||||||
|
*/
|
||||||
|
export const applyAutoLayout = (graph: Graph) => {
|
||||||
|
const g = new dagre.graphlib.Graph();
|
||||||
|
g.setGraph({
|
||||||
|
rankdir: 'LR',
|
||||||
|
align: 'UL',
|
||||||
|
ranksep: 80,
|
||||||
|
nodesep: 50,
|
||||||
|
marginx: 20,
|
||||||
|
marginy: 20,
|
||||||
|
});
|
||||||
|
g.setDefaultEdgeLabel(() => ({}));
|
||||||
|
|
||||||
|
// 添加节点
|
||||||
|
const nodes = graph.getNodes();
|
||||||
|
nodes.forEach(node => {
|
||||||
|
g.setNode(node.id, {
|
||||||
|
width: node.getSize().width,
|
||||||
|
height: node.getSize().height,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加边
|
||||||
|
const edges = graph.getEdges();
|
||||||
|
edges.forEach(edge => {
|
||||||
|
g.setEdge(edge.getSourceCellId(), edge.getTargetCellId());
|
||||||
|
});
|
||||||
|
|
||||||
|
// 执行布局
|
||||||
|
dagre.layout(g);
|
||||||
|
|
||||||
|
// 应用布局结果
|
||||||
|
nodes.forEach(node => {
|
||||||
|
const nodeWithPosition = g.node(node.id);
|
||||||
|
node.position(
|
||||||
|
nodeWithPosition.x - nodeWithPosition.width / 2,
|
||||||
|
nodeWithPosition.y - nodeWithPosition.height / 2
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.centerContent();
|
||||||
|
};
|
||||||
103
frontend/src/pages/Workflow/Definition/Design/utils/nodeUtils.ts
Normal file
103
frontend/src/pages/Workflow/Definition/Design/utils/nodeUtils.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { Graph } from '@antv/x6';
|
||||||
|
import { convertPortConfig } from '../constants';
|
||||||
|
|
||||||
|
interface NodeStyle {
|
||||||
|
shape: string;
|
||||||
|
style: any;
|
||||||
|
size: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
ports: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一获取节点样式配置
|
||||||
|
* @param node 节点数据
|
||||||
|
* @returns 统一的节点样式配置
|
||||||
|
*/
|
||||||
|
const getNodeStyle = (node: any): NodeStyle => {
|
||||||
|
// 如果是从已保存的定义加载的节点
|
||||||
|
if (node.graph) {
|
||||||
|
return {
|
||||||
|
shape: node.graph.shape,
|
||||||
|
style: node.graph.style,
|
||||||
|
size: node.graph.size,
|
||||||
|
ports: node.graph.ports
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 如果是从面板拖拽创建的新节点
|
||||||
|
return {
|
||||||
|
shape: node.graphConfig.uiSchema.shape,
|
||||||
|
style: node.graphConfig.uiSchema.style,
|
||||||
|
size: node.graphConfig.uiSchema.size,
|
||||||
|
ports: node.graphConfig.uiSchema.ports
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建节点配置
|
||||||
|
*/
|
||||||
|
const createNodeConfig = (node: any) => {
|
||||||
|
const style = getNodeStyle(node);
|
||||||
|
|
||||||
|
return {
|
||||||
|
inherit: 'rect', // 默认使用矩形
|
||||||
|
width: style.size.width,
|
||||||
|
height: style.size.height,
|
||||||
|
attrs: {
|
||||||
|
body: {
|
||||||
|
...style.style,
|
||||||
|
// 如果是菱形,添加多边形的点
|
||||||
|
...(style.shape === 'diamond' ? {
|
||||||
|
refPoints: '0,10 10,0 20,10 10,20',
|
||||||
|
} : {})
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
text: node.name,
|
||||||
|
fill: '#000000',
|
||||||
|
fontSize: 12,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ports: convertPortConfig(style.ports)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加节点到图形
|
||||||
|
* @param graph X6 Graph实例
|
||||||
|
* @param node 节点数据
|
||||||
|
* @param position 节点位置(可选)
|
||||||
|
* @returns 创建的节点实例
|
||||||
|
*/
|
||||||
|
export const addNodeToGraph = (
|
||||||
|
graph: Graph,
|
||||||
|
node: any,
|
||||||
|
position?: { x: number, y: number }
|
||||||
|
) => {
|
||||||
|
const style = getNodeStyle(node);
|
||||||
|
const nodeConfig = createNodeConfig(node);
|
||||||
|
|
||||||
|
// 根据形状类型设置正确的 shape
|
||||||
|
let shape = 'rect'; // 默认使用矩形
|
||||||
|
if (style.shape === 'circle') {
|
||||||
|
shape = 'circle';
|
||||||
|
} else if (style.shape === 'diamond') {
|
||||||
|
shape = 'polygon';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建节点
|
||||||
|
const newNode = graph.addNode({
|
||||||
|
...nodeConfig,
|
||||||
|
shape, // 使用映射后的形状
|
||||||
|
...(position && { x: position.x, y: position.y }),
|
||||||
|
// 如果是已保存的节点,使用其ID和位置
|
||||||
|
...(node.id && { id: node.id }),
|
||||||
|
...(node.graph?.position && { position: node.graph.position }),
|
||||||
|
type: node.type,
|
||||||
|
code: node.code,
|
||||||
|
nodeDefinition: node,
|
||||||
|
});
|
||||||
|
|
||||||
|
return newNode;
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user