增加工具栏提示。

This commit is contained in:
dengqichen 2024-12-13 13:14:54 +08:00
parent 989abcae89
commit 9e35ac490e
3 changed files with 174 additions and 148 deletions

View File

@ -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,
});
}
} }
}; };

View File

@ -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();
};

View 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;
};