From 9e35ac490ebacc559596dd0c5de3660201699aa4 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Fri, 13 Dec 2024 13:14:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=B7=A5=E5=85=B7=E6=A0=8F?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Workflow/Definition/Design/index.tsx | 171 +++--------------- .../Definition/Design/utils/layoutUtils.ts | 48 +++++ .../Definition/Design/utils/nodeUtils.ts | 103 +++++++++++ 3 files changed, 174 insertions(+), 148 deletions(-) create mode 100644 frontend/src/pages/Workflow/Definition/Design/utils/layoutUtils.ts create mode 100644 frontend/src/pages/Workflow/Definition/Design/utils/nodeUtils.ts diff --git a/frontend/src/pages/Workflow/Definition/Design/index.tsx b/frontend/src/pages/Workflow/Definition/Design/index.tsx index 1899e59f..47df284e 100644 --- a/frontend/src/pages/Workflow/Definition/Design/index.tsx +++ b/frontend/src/pages/Workflow/Definition/Design/index.tsx @@ -3,18 +3,18 @@ import { useParams, useNavigate } from 'react-router-dom'; import { Button, Space, Card, Row, Col, message } from 'antd'; import { ArrowLeftOutlined, SaveOutlined, PlayCircleOutlined } from '@ant-design/icons'; import { Graph, Cell } from '@antv/x6'; -import dagre from 'dagre'; import { getDefinitionDetail, saveDefinition } from '../service'; import NodePanel from './components/NodePanel'; import NodeConfigDrawer from './components/NodeConfigModal'; import { NodeDefinition } from './types'; import { validateWorkflow } from './utils/validator'; +import { addNodeToGraph } from './utils/nodeUtils'; +import { applyAutoLayout } from './utils/layoutUtils'; import { GRID_CONFIG, CONNECTING_CONFIG, HIGHLIGHTING_CONFIG, DEFAULT_STYLES, - convertPortConfig } from './constants'; const WorkflowDesign: React.FC = () => { @@ -96,60 +96,16 @@ const WorkflowDesign: React.FC = () => { const loadDefinitionDetail = async (graphInstance: Graph, definitionId: number) => { try { const response = await getDefinitionDetail(definitionId); - console.log('加载到的工作流数据:', response); setTitle(`工作流设计 - ${response.name}`); setDefinitionData(response); // 清空画布 graphInstance.clearCells(); - const nodeMap = new Map(); - // 动态注册节点类型 + // 创建节点 response.graph.nodes.forEach((nodeData: any) => { - // 根据节点类型动态注册 - 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, - }); + const node = addNodeToGraph(graphInstance, nodeData); nodeMap.set(nodeData.id, node); }); @@ -161,62 +117,16 @@ const WorkflowDesign: React.FC = () => { graphInstance.addEdge({ source: { cell: sourceNode.id }, target: { cell: targetNode.id }, - attrs: { - line: DEFAULT_STYLES.edge - }, - labels: [ - { - attrs: { - label: { - text: edge.name || '', - }, - }, - }, - ], + attrs: { line: DEFAULT_STYLES.edge }, + labels: [{ + attrs: { label: { text: edge.name || '' } } + }] }); } }); - // 自动布局 - const g = new dagre.graphlib.Graph(); - 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(); + // 应用自动布局 + applyAutoLayout(graphInstance); } catch (error) { console.error('加载工作流定义失败:', error); message.error('加载工作流定义失败'); @@ -229,55 +139,20 @@ const WorkflowDesign: React.FC = () => { 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 }); - 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); + if (!graph) return; - // 创建节点时设置完整的属性 - graph.addNode({ - ...nodeConfig, - shape: node.graphConfig.uiSchema.shape, - x: point.x, - y: point.y, - // 保存完整的节点信息 - type: node.type, - code: node.code, - // ports: nodeConfig.ports, - ports: convertPortConfig(node.graphConfig.uiSchema.ports), - // 保存节点定义,用于后续编辑 - nodeDefinition: node, - }); - } + try { + const nodeData = e.dataTransfer.getData('node'); + if (!nodeData) return; + + const node = JSON.parse(nodeData); + const { clientX, clientY } = e; + const point = graph.clientToLocal({ x: clientX, y: clientY }); + + addNodeToGraph(graph, node, point); + } catch (error) { + console.error('创建节点失败:', error); + message.error('创建节点失败'); } }; diff --git a/frontend/src/pages/Workflow/Definition/Design/utils/layoutUtils.ts b/frontend/src/pages/Workflow/Definition/Design/utils/layoutUtils.ts new file mode 100644 index 00000000..4cf99530 --- /dev/null +++ b/frontend/src/pages/Workflow/Definition/Design/utils/layoutUtils.ts @@ -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(); +}; \ No newline at end of file diff --git a/frontend/src/pages/Workflow/Definition/Design/utils/nodeUtils.ts b/frontend/src/pages/Workflow/Definition/Design/utils/nodeUtils.ts new file mode 100644 index 00000000..6b76fcec --- /dev/null +++ b/frontend/src/pages/Workflow/Definition/Design/utils/nodeUtils.ts @@ -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; +}; \ No newline at end of file