import React, {useEffect, useState, useRef} from 'react'; import {createRoot} from 'react-dom/client'; import {useParams, useNavigate} from 'react-router-dom'; import {Button, Space, Card, Row, Col, message, Modal, Collapse, Tooltip, Dropdown} from 'antd'; import { ArrowLeftOutlined, SaveOutlined, PlayCircleOutlined, CopyOutlined, DeleteOutlined, UndoOutlined, RedoOutlined, ScissorOutlined, SnippetsOutlined, SelectOutlined, ZoomInOutlined, ZoomOutOutlined, } from '@ant-design/icons'; import {Graph, Cell} from '@antv/x6'; import '@antv/x6-plugin-snapline'; import '@antv/x6-plugin-selection'; import '@antv/x6-plugin-keyboard'; import '@antv/x6-plugin-history'; import '@antv/x6-plugin-clipboard'; import '@antv/x6-plugin-transform'; import {Selection} from '@antv/x6-plugin-selection'; import {MiniMap} from '@antv/x6-plugin-minimap'; import {Clipboard} from '@antv/x6-plugin-clipboard'; import {History} from '@antv/x6-plugin-history'; import {Transform} from '@antv/x6-plugin-transform'; import {getDefinitionDetail, saveDefinition} from '../service'; import {getNodeDefinitionList} 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, } from './constants'; import './index.less'; import {NodeDefinitionResponse, NodeDesignDataResponse} from "@/pages/Workflow/NodeDesign/types"; const WorkflowDesign: React.FC = () => { const {id} = useParams<{ id: string }>(); const navigate = useNavigate(); const [title, setTitle] = useState('工作流设计'); const graphContainerRef = useRef(null); const minimapContainerRef = useRef(null); const [graph, setGraph] = useState(null); const [selectedNode, setSelectedNode] = useState(null); const [selectedNodeDefinition, setSelectedNodeDefinition] = useState(null); const [configModalVisible, setConfigModalVisible] = useState(false); const [definitionData, setDefinitionData] = useState(null); const [nodeDefinitions, setNodeDefinitions] = useState([]); const [isNodeDefinitionsLoaded, setIsNodeDefinitionsLoaded] = useState(false); const [forceUpdate, setForceUpdate] = useState(false); const [scale, setScale] = useState(1); // 初始化图形 const initGraph = () => { if (!graphContainerRef.current) return; // 获取主画布容器尺寸 const container = graphContainerRef.current; const containerWidth = container.clientWidth; const containerHeight = container.clientHeight; // 计算主画布和小地图的尺寸比例 const MINIMAP_BASE_WIDTH = 200; const minimapWidth = MINIMAP_BASE_WIDTH; const minimapHeight = Math.round((MINIMAP_BASE_WIDTH * containerHeight) / containerWidth); // 计算缩放比例 const scale = minimapWidth / containerWidth; // 将尺寸信息保存到样式变量中 document.documentElement.style.setProperty('--minimap-width', `${minimapWidth}px`); document.documentElement.style.setProperty('--minimap-height', `${minimapHeight}px`); const graph = new Graph({ container: graphContainerRef.current, grid: { size: 10, visible: true, type: 'dot', args: { color: '#a0a0a0', thickness: 1, }, }, connecting: { snap: true, // 连线时自动吸附 allowBlank: false, // 禁止连线到空白位置 allowLoop: false, // 禁止自环 allowNode: false, // 禁止连接到节点(只允许连接到连接桩) allowEdge: false, // 禁止边连接到边 connector: { name: 'rounded', args: { radius: 8 } }, router: { name: 'manhattan', args: { padding: 1 } }, validateConnection({sourceCell, targetCell, sourceMagnet, targetMagnet}) { if (sourceCell === targetCell) { return false; } if (!sourceMagnet || !targetMagnet) { return false; } // 检查是否已存在相同的连接 const edges = graph.getEdges(); const exists = edges.some(edge => { const source = edge.getSource(); const target = edge.getTarget(); return ( source.cell === sourceCell.id && target.cell === targetCell.id && source.port === sourceMagnet.getAttribute('port') && target.port === targetMagnet.getAttribute('port') ); }); return !exists; }, validateMagnet({magnet}) { return magnet.getAttribute('port-group') !== 'top'; }, validateEdge({edge}) { return true; } }, highlighting: { magnetAvailable: { name: 'stroke', args: { padding: 4, attrs: { strokeWidth: 4, stroke: '#52c41a', }, }, }, }, clipboard: { enabled: true, }, selecting: { enabled: true, multiple: true, rubberband: true, movable: true, showNodeSelectionBox: false, // 禁用节点选择框 showEdgeSelectionBox: false, // 禁用边选择框 selectNodeOnMoved: false, selectEdgeOnMoved: false, }, snapline: true, keyboard: { enabled: true, }, panning: { enabled: true, eventTypes: ['rightMouseDown'], // 右键按下时启用画布拖拽 }, mousewheel: { enabled: true, modifiers: ['ctrl', 'meta'], minScale: 0.2, maxScale: 2, }, }); // 注册插件 const history = new History({ enabled: true, beforeAddCommand(event: any, args: any) { return true; }, afterExecuteCommand: () => { // 强制更新组件状态 setForceUpdate(prev => !prev); }, afterUndo: () => { // 强制更新组件状态 setForceUpdate(prev => !prev); }, afterRedo: () => { // 强制更新组件状态 setForceUpdate(prev => !prev); }, }); // 初始化Selection插件 const selection = new Selection({ enabled: true, multiple: true, rubberband: true, movable: true, showNodeSelectionBox: false, // 禁用节点选择框 showEdgeSelectionBox: false, // 禁用边选择框 selectNodeOnMoved: false, selectEdgeOnMoved: false, multipleSelectionModifiers: ['ctrl', 'meta'], showEdgeSelectionBox: false, showAnchorSelectionBox: false, pointerEvents: 'auto' }); graph.use(selection); graph.use(new MiniMap({ container: minimapContainerRef.current!, width: minimapWidth, height: minimapHeight, padding: 5, scalable: false, minScale: scale * 0.8, // 稍微缩小一点以显示更多内容 maxScale: scale * 1.2, graphOptions: { connecting: { connector: 'rounded', connectionPoint: 'anchor', router: { name: 'manhattan', }, }, async: true, frozen: true, interacting: false, grid: false }, viewport: { padding: 0, fitToContent: false, // 禁用自动适配内容 initialPosition: { // 初始位置与主画布保持一致 x: 0, y: 0 }, initialScale: scale } })); graph.use(new Clipboard()); graph.use(history); graph.use( new Transform({ resizing: false, rotating: false, }) ); // 扩展 graph 对象,添加 history 属性 (graph as any).history = history; // 监听图形变化 graph.on('cell:added', ({cell}) => { const canUndo = history.canUndo(); const canRedo = history.canRedo(); // 强制更新组件状态 setForceUpdate(prev => !prev); }); graph.on('cell:removed', ({cell}) => { const canUndo = history.canUndo(); const canRedo = history.canRedo(); console.log('Cell removed:', { cell, canUndo, canRedo, stackSize: history.stackSize, }); // 强制更新组件状态 setForceUpdate(prev => !prev); }); graph.on('cell:changed', ({cell, options}) => { const canUndo = history.canUndo(); const canRedo = history.canRedo(); // 强制更新组件状态 setForceUpdate(prev => !prev); }); // 监听主画布变化,同步更新小地图 graph.on('transform', () => { const minimap = graph.getPlugin('minimap') as MiniMap; if (minimap) { const mainViewport = graph.getView().getVisibleArea(); minimap.viewport.scale = scale; minimap.viewport.center(mainViewport.center); } }); // 处理连线重新连接 graph.on('edge:moved', ({edge, terminal, previous}) => { if (!edge || !terminal) return; const isSource = terminal.type === 'source'; const source = isSource ? terminal : edge.getSource(); const target = isSource ? edge.getTarget() : terminal; if (source && target) { // 移除旧的连线 edge.remove(); // 创建新的连线 graph.addEdge({ source: { cell: source.cell, port: source.port, }, target: { cell: target.cell, port: target.port, }, attrs: { line: { stroke: '#5F95FF', strokeWidth: 2, targetMarker: { name: 'classic', size: 7, }, }, }, }); } }); // 处理连线更改 graph.on('edge:change:source edge:change:target', ({edge, current, previous}) => { if (edge && current) { edge.setAttrs({ line: { stroke: '#5F95FF', strokeWidth: 2, targetMarker: { name: 'classic', size: 7, }, }, }); } }); registerEventHandlers(graph); return graph; }; // 处理撤销操作 const handleUndo = () => { if (!graph) return; const history = (graph as any).history; if (!history) { console.error('History plugin not initialized'); return; } const beforeState = { canUndo: history.canUndo(), canRedo: history.canRedo(), stackSize: history.stackSize, }; if (history.canUndo()) { history.undo(); const afterState = { canUndo: history.canUndo(), canRedo: history.canRedo(), stackSize: history.stackSize, }; console.log('Undo operation:', { before: beforeState, after: afterState, }); message.success('已撤销'); } else { message.info('没有可撤销的操作'); } }; // 处理重做操作 const handleRedo = () => { if (!graph) return; const history = (graph as any).history; if (!history) { console.error('History plugin not initialized'); return; } const beforeState = { canUndo: history.canUndo(), canRedo: history.canRedo(), stackSize: history.stackSize, }; if (history.canRedo()) { history.redo(); const afterState = { canUndo: history.canUndo(), canRedo: history.canRedo(), stackSize: history.stackSize, }; console.log('Redo operation:', { before: beforeState, after: afterState, }); message.success('已重做'); } else { message.info('没有可重做的操作'); } }; // 处理缩放 const handleZoom = (delta: number) => { if (!graph) return; const currentScale = graph.scale(); const newScale = Math.max(0.2, Math.min(2, currentScale.sx + delta)); graph.scale(newScale, newScale); setScale(newScale); }; // 放大 const handleZoomIn = () => { handleZoom(0.1); }; // 缩小 const handleZoomOut = () => { handleZoom(-0.1); }; // 注册事件处理器 const registerEventHandlers = (graph: Graph) => { // 定义悬停样式 const hoverStyle = { strokeWidth: 2, stroke: '#52c41a' // 绿色 }; // 保存节点的原始样式 const saveNodeOriginalStyle = (node: any) => { const data = node.getData(); if (!data?.originalStyle) { const originalStyle = { stroke: node.getAttrByPath('body/stroke') || '#5F95FF', strokeWidth: node.getAttrByPath('body/strokeWidth') || 1 }; node.setData({ ...data, originalStyle }); } }; // 获取节点的原始样式 const getNodeOriginalStyle = (node: any) => { const data = node.getData(); return data?.originalStyle || { stroke: '#5F95FF', strokeWidth: 1 }; }; // 恢复节点的原始样式 const resetNodeStyle = (node: any) => { const originalStyle = getNodeOriginalStyle(node); node.setAttrByPath('body/stroke', originalStyle.stroke); node.setAttrByPath('body/strokeWidth', originalStyle.strokeWidth); }; // 节点悬停事件 graph.on('node:mouseenter', ({node}) => { // 保存原始样式 saveNodeOriginalStyle(node); // 显示连接桩 const ports = document.querySelectorAll(`[data-cell-id="${node.id}"] .x6-port-body`); ports.forEach((port) => { port.setAttribute('style', 'visibility: visible; fill: #fff; stroke: #85ca6d;'); }); // 显示悬停样式 node.setAttrByPath('body/stroke', hoverStyle.stroke); node.setAttrByPath('body/strokeWidth', hoverStyle.strokeWidth); }); 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'); }); // 恢复原始样式 resetNodeStyle(node); }); // 节点拖动开始时记录状态 graph.on('node:drag:start', ({node}) => { // 保存原始样式,以防还没保存过 saveNodeOriginalStyle(node); }); // 节点拖动结束后恢复样式 graph.on('node:moved', ({node}) => { resetNodeStyle(node); // 隐藏连接桩 const ports = document.querySelectorAll(`[data-cell-id="${node.id}"] .x6-port-body`); ports.forEach((port) => { port.setAttribute('style', 'visibility: hidden'); }); }); // 节点点击事件 graph.on('node:click', ({node}) => { // 获取当前选中的节点 const selectedNode = graph.getSelectedCells()[0]; // 如果有其他节点被选中,恢复其样式 if (selectedNode && selectedNode.isNode() && selectedNode.id !== node.id) { resetNodeStyle(selectedNode); } // 更新选中状态 graph.resetSelection(); graph.select(node); }); // 点击空白处事件 graph.on('blank:click', () => { // 获取当前选中的节点 const selectedNode = graph.getSelectedCells()[0]; // 如果有节点被选中,恢复其样式 if (selectedNode && selectedNode.isNode()) { resetNodeStyle(selectedNode); } // 清除选中状态 graph.resetSelection(); }); // 节点双击事件 graph.on('node:dblclick', ({node}) => { const nodeType = node.getProp('type'); // 从节点定义列表中找到对应的定义 const nodeDefinition = nodeDefinitions.find(def => def.nodeType === nodeType); if (nodeDefinition) { setSelectedNode(node); setSelectedNodeDefinition(nodeDefinition); setConfigModalVisible(true); } }); // 添加右键菜单 graph.on('node:contextmenu', ({cell, view, e}) => { e.preventDefault(); graph.cleanSelection(); graph.select(cell); const dropdownContainer = document.createElement('div'); dropdownContainer.style.position = 'absolute'; dropdownContainer.style.left = `${e.clientX}px`; dropdownContainer.style.top = `${e.clientY}px`; document.body.appendChild(dropdownContainer); const root = createRoot(dropdownContainer); let isOpen = true; const closeMenu = () => { isOpen = false; root.render( { if (!open) { setTimeout(() => { root.unmount(); document.body.removeChild(dropdownContainer); }, 100); } }}>
); }; const items = [ { key: '1', label: '编辑', onClick: () => { closeMenu(); setSelectedNode(cell); setSelectedNodeDefinition(nodeDefinitions.find(def => def.type === cell.getProp('type'))); setConfigModalVisible(true); } }, { key: '2', label: '删除', onClick: () => { closeMenu(); Modal.confirm({ title: '确认删除', content: '确定要删除该节点吗?', onOk: () => { cell.remove(); } }); } } ]; root.render(
); const handleClickOutside = (e: MouseEvent) => { if (!dropdownContainer.contains(e.target as Node)) { closeMenu(); document.removeEventListener('click', handleClickOutside); } }; document.addEventListener('click', handleClickOutside); }); // 添加边的右键菜单 graph.on('edge:contextmenu', ({cell, view, e}) => { e.preventDefault(); graph.cleanSelection(); graph.select(cell); const dropdownContainer = document.createElement('div'); dropdownContainer.style.position = 'absolute'; dropdownContainer.style.left = `${e.clientX}px`; dropdownContainer.style.top = `${e.clientY}px`; document.body.appendChild(dropdownContainer); const root = createRoot(dropdownContainer); let isOpen = true; const closeMenu = () => { isOpen = false; root.render( { if (!open) { setTimeout(() => { root.unmount(); document.body.removeChild(dropdownContainer); }, 100); } }}>
); }; const items = [ { key: '1', label: '删除', onClick: () => { closeMenu(); Modal.confirm({ title: '确认删除', content: '确定要删除该连接线吗?', onOk: () => { cell.remove(); } }); } } ]; root.render(
); const handleClickOutside = (e: MouseEvent) => { if (!dropdownContainer.contains(e.target as Node)) { closeMenu(); document.removeEventListener('click', handleClickOutside); } }; document.addEventListener('click', handleClickOutside); }); // 禁用默认的右键菜单 graph.on('blank:contextmenu', ({e}) => { e.preventDefault(); }); // 禁用节点的右键菜单 graph.on('node:contextmenu', ({e}) => { e.preventDefault(); }); // 禁用边的右键菜单 graph.on('edge:contextmenu', ({e}) => { e.preventDefault(); }); // 连接桩显示/隐藏 graph.on('node:mouseenter', ({node}) => { // 保存原始样式 saveNodeOriginalStyle(node); // 显示连接桩 const ports = document.querySelectorAll(`[data-cell-id="${node.id}"] .x6-port-body`); ports.forEach((port) => { port.setAttribute('style', 'visibility: visible; fill: #fff; stroke: #85ca6d;'); }); // 显示悬停样式 node.setAttrByPath('body/stroke', hoverStyle.stroke); node.setAttrByPath('body/strokeWidth', hoverStyle.strokeWidth); }); // 连线开始时的处理 graph.on('edge:connected', ({edge}) => { // 设置连线样式 edge.setAttrs({ line: { stroke: '#5F95FF', strokeWidth: 2, targetMarker: { name: 'classic', size: 7, }, }, }); }); // 连线悬停效果 graph.on('edge:mouseenter', ({edge}) => { edge.setAttrs({ line: { stroke: '#52c41a', strokeWidth: 3, }, }); }); graph.on('edge:mouseleave', ({edge}) => { edge.setAttrs({ line: { stroke: '#5F95FF', strokeWidth: 2, }, }); }); // 允许拖动连线中间的点和端点 graph.on('edge:selected', ({edge}) => { edge.addTools([ { name: 'source-arrowhead', args: { attrs: { fill: '#fff', stroke: '#5F95FF', strokeWidth: 1, d: 'M 0 -6 L -8 0 L 0 6 Z', }, }, }, { name: 'target-arrowhead', args: { attrs: { fill: '#fff', stroke: '#5F95FF', strokeWidth: 1, d: 'M 0 -6 L 8 0 L 0 6 Z', }, }, }, { name: 'vertices', args: { padding: 2, attrs: { fill: '#fff', stroke: '#5F95FF', strokeWidth: 1, r: 4 }, }, }, { name: 'segments', args: { padding: 2, attrs: { fill: '#fff', stroke: '#5F95FF', strokeWidth: 1, r: 4 }, }, }, ]); }); // 连线工具移除 graph.on('edge:unselected', ({edge}) => { edge.removeTools(); }); // 连线移动到其他连接桩时的样式 graph.on('edge:connected', ({edge}) => { edge.setAttrs({ line: { stroke: '#5F95FF', strokeWidth: 2, targetMarker: { name: 'classic', size: 7, }, }, }); }); // 连线悬停在连接桩上时的样式 graph.on('edge:mouseenter', ({edge}) => { const ports = document.querySelectorAll('.x6-port-body'); ports.forEach((port) => { const portGroup = port.getAttribute('port-group'); if (portGroup !== 'top') { port.setAttribute('style', 'visibility: visible; fill: #fff; stroke: #85ca6d;'); } }); }); graph.on('edge:mouseleave', ({edge}) => { const ports = document.querySelectorAll('.x6-port-body'); ports.forEach((port) => { port.setAttribute('style', 'visibility: hidden'); }); }); }; // 处理复制操作 const handleCopy = () => { if (!graph) return; const cells = graph.getSelectedCells(); if (cells.length === 0) { message.info('请先选择要复制的节点'); return; } graph.copy(cells); message.success('已复制'); }; // 处理剪切操作 const handleCut = () => { if (!graph) return; const cells = graph.getSelectedCells(); if (cells.length === 0) { message.info('请先选择要剪切的节点'); return; } graph.cut(cells); message.success('已剪切'); }; // 处理粘贴操作 const handlePaste = () => { if (!graph) return; if (graph.isClipboardEmpty()) { message.info('剪贴板为空'); return; } const cells = graph.paste({offset: 32}); graph.cleanSelection(); graph.select(cells); message.success('已粘贴'); }; // 首先加载节点定义列表 useEffect(() => { const loadNodeDefinitions = async () => { try { const data = await getNodeDefinitionList(); setNodeDefinitions(data); setIsNodeDefinitionsLoaded(true); } catch (error) { console.error('加载节点定义失败:', error); message.error('加载节点定义失败'); } }; loadNodeDefinitions(); }, []); // 加载工作流定义详情 const loadDefinitionDetail = async (graphInstance: Graph, definitionId: string) => { try { const response = await getDefinitionDetail(Number(definitionId)); setTitle(`工作流设计 - ${response.name}`); setDefinitionData(response); if (!graphInstance) { console.error('Graph instance is not initialized'); return; } // 清空画布 graphInstance.clearCells(); const nodeMap = new Map(); // 创建节点 response.graph?.nodes?.forEach((workflowDefinitionNode: any) => { const node = addNodeToGraph(false, graphInstance, workflowDefinitionNode, nodeDefinitions); // 保存节点配置 node.setProp('workflowDefinitionNode', workflowDefinitionNode); nodeMap.set(workflowDefinitionNode.id, node); }); // 创建边 response.graph?.edges?.forEach((edge: any) => { const sourceNode = nodeMap.get(edge.from); const targetNode = nodeMap.get(edge.to); if (sourceNode && targetNode) { // 根据节点类型获取正确的端口组 const getPortByGroup = (node: any, group: string) => { const ports = node.getPorts(); const port = ports.find((p: any) => p.group === group); return port?.id; }; // 获取源节点的输出端口(一定是out组) const sourcePort = getPortByGroup(sourceNode, 'out'); // 获取目标节点的输入端口(一定是in组) const targetPort = getPortByGroup(targetNode, 'in'); if (!sourcePort || !targetPort) { console.error('无法找到正确的端口:', edge); return; } graphInstance.addEdge({ source: { cell: sourceNode.id, port: sourcePort, }, target: { cell: targetNode.id, port: targetPort, }, attrs: { line: { stroke: '#5F95FF', strokeWidth: 2, targetMarker: { name: 'classic', size: 7, }, }, }, labels: [{ attrs: {label: {text: edge.name || ''}} }] }); } }); // 传入节点数据,只在没有位置信息时应用自动布局 applyAutoLayout(graphInstance, response.graph?.nodes || []); } catch (error) { console.error('加载工作流定义失败:', error); message.error('加载工作流定义失败'); } }; // 初始化图形和加载数据 useEffect(() => { if (!graphContainerRef.current || !isNodeDefinitionsLoaded) { return; } const newGraph = initGraph(); if (newGraph) { setGraph(newGraph); // 在图形初始化完成后加载数据 if (id) { loadDefinitionDetail(newGraph, id); } } return () => { graph?.dispose(); }; }, [graphContainerRef, id, nodeDefinitions, isNodeDefinitionsLoaded]); // 处理节点拖拽开始 const handleNodeDragStart = (node: NodeDefinitionResponse, e: React.DragEvent) => { e.dataTransfer.setData('node', JSON.stringify(node)); }; // 处理节点拖拽结束 const handleDrop = (e: React.DragEvent) => { e.preventDefault(); if (!graph) return; 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}); console.log("dasdasd", graph, node, nodeDefinitions, point); addNodeToGraph(true, graph, node, nodeDefinitions, point); } catch (error) { console.error('创建节点失败:', error); message.error('创建节点失败'); } }; // 处理拖拽过程中 const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); }; // 处理节点配置更新 const handleNodeConfigUpdate = (values: any) => { if (!selectedNode) return; // 更新节点配置 console.log("// 更新节点配置", values); selectedNode.setProp('config', values); // 更新节点显示名称 if (values.name) { selectedNode.attr('label/text', values.name); } setConfigModalVisible(false); message.success('节点配置已更新'); }; // 处理保存工作流 const handleSaveWorkflow = async () => { if (!graph || !definitionData) return; try { // 校验流程图 const validationResult = validateWorkflow(graph); if (!validationResult.valid) { message.error(validationResult.message); return; } // 获取所有节点和边的数据 const nodes = graph.getNodes().map(node => { const nodeDefinition = node.getProp('nodeDefinition'); const nodeType = node.getProp('type'); const position = node.getPosition(); return { id: node.id, code: nodeType, type: nodeType, name: node.attr('label/text'), graph: { // shape: nodeDefinition?.graphConfig.uiSchema.shape, // size: { // width: node.size().width, // height: node.size().height // }, // style: nodeDefinition?.graphConfig.uiSchema.style, // ports: nodeDefinition?.graphConfig.uiSchema.ports, // position: { // x: position.x, // y: position.y // } uiVariables: nodeDefinition.uiVariables }, config: node.getProp('config') || {} }; }); const edges = graph.getEdges().map(edge => ({ id: edge.id, from: edge.getSourceCellId(), to: edge.getTargetCellId(), name: edge.getLabels()?.[0]?.attrs?.label?.text || '', config: { type: 'sequence' } })); // 构建保存数据 const saveData = { ...definitionData, graph: { nodes, edges } }; // 调用保存接口 await saveDefinition(saveData); // 使用 Modal.confirm 显示操作选择 Modal.confirm({ title: '保存成功', content: '流程设计已保存成功,请选择下一步操作', okText: '继续设计', cancelText: '返回列表', onOk: () => { // 重新加载设计数据 if (id) { loadDefinitionDetail(graph, id); } }, onCancel: () => { // 返回列表页 navigate('/workflow/definition'); } }); } catch (error) { console.error('保存流程失败:', error); message.error('保存流程失败'); } }; return (
{configModalVisible && selectedNode && selectedNodeDefinition && ( setConfigModalVisible(false)} onOk={handleNodeConfigUpdate} /> )}
); }; export default WorkflowDesign;