import React, { useState, useCallback, useRef, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { message } from 'antd'; import { ReactFlowProvider, useReactFlow } from '@xyflow/react'; import WorkflowToolbar from './components/WorkflowToolbar'; import NodePanel from './components/NodePanel'; import FlowCanvas from './components/FlowCanvas'; import NodeConfigModal from './components/NodeConfigModal'; import type { FlowNode, FlowEdge, FlowNodeData } from './types'; import type { WorkflowNodeDefinition } from './nodes/types'; import { NodeType } from './types'; import { useWorkflowSave } from './hooks/useWorkflowSave'; import { useWorkflowLoad } from './hooks/useWorkflowLoad'; // 样式 import '@xyflow/react/dist/style.css'; import './index.less'; const WorkflowDesignInner: React.FC = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const { getNodes, setNodes, getEdges, setEdges, screenToFlowPosition, fitView, zoomIn, zoomOut, getZoom } = useReactFlow(); const [workflowTitle, setWorkflowTitle] = useState('新建工作流'); const reactFlowWrapper = useRef(null); // 当前工作流ID const currentWorkflowId = id ? parseInt(id) : undefined; // 节点配置模态框状态 const [configModalVisible, setConfigModalVisible] = useState(false); const [configNode, setConfigNode] = useState(null); // 保存和加载hooks const { hasUnsavedChanges, saveWorkflow, markUnsaved } = useWorkflowSave(); const { workflowDefinition, loadWorkflow } = useWorkflowLoad(); // 加载工作流数据 useEffect(() => { const loadData = async () => { if (currentWorkflowId) { const data = await loadWorkflow(currentWorkflowId); if (data) { setNodes(data.nodes); setEdges(data.edges); setWorkflowTitle(data.definition.name); } } }; loadData(); }, [currentWorkflowId, loadWorkflow, setNodes, setEdges]); // 自动适应视图 useEffect(() => { // 延迟执行fitView以确保节点已渲染 const timer = setTimeout(() => { fitView({ padding: 0.1, duration: 800, minZoom: 1.0, // 最小缩放100% maxZoom: 1.0 // 最大缩放100%,确保默认100% }); }, 100); return () => clearTimeout(timer); }, [fitView]); // 初始化示例节点 - 优化位置和布局 const initialNodes: FlowNode[] = [ { id: '1', type: 'START_EVENT', position: { x: 250, y: 50 }, data: { label: '开始', nodeType: NodeType.START_EVENT, category: 'EVENT' as any, icon: '▶️', color: '#10b981' } }, { id: '2', type: 'USER_TASK', position: { x: 200, y: 150 }, data: { label: '用户审批', nodeType: NodeType.USER_TASK, category: 'TASK' as any, icon: '👤', color: '#6366f1' } }, { id: '3', type: 'END_EVENT', position: { x: 250, y: 250 }, data: { label: '结束', nodeType: NodeType.END_EVENT, category: 'EVENT' as any, icon: '⏹️', color: '#ef4444' } } ]; const initialEdges: FlowEdge[] = [ { id: 'e1-2', source: '1', target: '2', type: 'default', animated: true, data: { label: '提交', condition: { type: 'DEFAULT', priority: 0 } } }, { id: 'e2-3', source: '2', target: '3', type: 'default', animated: true, data: { label: '通过', condition: { type: 'DEFAULT', priority: 0 } } } ]; // 工具栏事件处理 const handleSave = useCallback(async () => { const nodes = getNodes() as FlowNode[]; const edges = getEdges() as FlowEdge[]; const success = await saveWorkflow({ nodes, edges, workflowId: currentWorkflowId, name: workflowTitle, description: workflowDefinition?.description || '' }); if (success) { console.log('保存工作流成功:', { nodes, edges }); } }, [getNodes, getEdges, saveWorkflow, currentWorkflowId, workflowTitle, workflowDefinition]); const handleBack = useCallback(() => { navigate('/workflow2/definition'); }, [navigate]); const handleUndo = useCallback(() => { console.log('撤销操作'); message.info('撤销功能开发中...'); }, []); const handleRedo = useCallback(() => { console.log('重做操作'); message.info('重做功能开发中...'); }, []); const handleCopy = useCallback(() => { const selectedNodes = getNodes().filter(node => node.selected); if (selectedNodes.length > 0) { console.log('复制节点:', selectedNodes); message.success(`已复制 ${selectedNodes.length} 个节点`); } else { message.warning('请先选择要复制的节点'); } }, [getNodes]); const handleDelete = useCallback(() => { const selectedNodes = getNodes().filter(node => node.selected); const selectedEdges = getEdges().filter(edge => edge.selected); if (selectedNodes.length > 0 || selectedEdges.length > 0) { setNodes(nodes => nodes.filter(node => !node.selected)); setEdges(edges => edges.filter(edge => !edge.selected)); message.success(`已删除 ${selectedNodes.length} 个节点和 ${selectedEdges.length} 条连接`); } else { message.warning('请先选择要删除的元素'); } }, [getNodes, getEdges, setNodes, setEdges]); const handleSelectAll = useCallback(() => { setNodes(nodes => nodes.map(node => ({ ...node, selected: true }))); setEdges(edges => edges.map(edge => ({ ...edge, selected: true }))); message.info('已全选所有元素'); }, [setNodes, setEdges]); const handleFitView = useCallback(() => { fitView({ padding: 0.2, duration: 800 }); }, [fitView]); const handleZoomIn = useCallback(() => { zoomIn({ duration: 300 }); }, [zoomIn]); const handleZoomOut = useCallback(() => { zoomOut({ duration: 300 }); }, [zoomOut]); // 处理节点拖拽放置 - 使用官方推荐的screenToFlowPosition方法 const handleDrop = useCallback((event: React.DragEvent) => { event.preventDefault(); const dragData = event.dataTransfer.getData('application/reactflow'); if (!dragData) return; try { const { nodeType, nodeDefinition }: { nodeType: string; nodeDefinition: WorkflowNodeDefinition } = JSON.parse(dragData); // 根据React Flow官方文档,screenToFlowPosition会自动处理所有边界计算 // 不需要手动减去容器边界! const position = screenToFlowPosition({ x: event.clientX, y: event.clientY, }); const newNode: FlowNode = { id: `${nodeType}-${Date.now()}`, type: nodeType, position, data: { label: nodeDefinition.nodeName, nodeType: nodeDefinition.nodeType, category: nodeDefinition.category, icon: nodeDefinition.uiConfig.style.icon, color: nodeDefinition.uiConfig.style.fill, // 保存原始节点定义引用,用于配置 nodeDefinition, // 初始化配置数据 configs: { nodeName: nodeDefinition.nodeName, nodeCode: nodeDefinition.nodeCode, description: nodeDefinition.description }, inputMapping: {}, outputMapping: {} } }; setNodes(nodes => [...nodes, newNode]); message.success(`已添加 ${nodeDefinition.nodeName} 节点`); } catch (error) { console.error('解析拖拽数据失败:', error); message.error('添加节点失败'); } }, [screenToFlowPosition, setNodes]); // 处理节点双击 - 打开配置面板 const handleNodeClick = useCallback((event: React.MouseEvent, node: FlowNode) => { // 只处理双击事件 if (event.detail === 2) { console.log('双击节点,打开配置:', node); setConfigNode(node); setConfigModalVisible(true); } }, []); // 处理边双击 - 暂时只记录日志 const handleEdgeClick = useCallback((event: React.MouseEvent, edge: FlowEdge) => { if (event.detail === 2) { console.log('双击边:', edge); message.info(`双击了连接: ${edge.data?.label || '连接'},配置功能待实现`); } }, []); // 处理节点配置更新 const handleNodeConfigUpdate = useCallback((nodeId: string, updatedData: Partial) => { setNodes((nodes) => nodes.map((node) => node.id === nodeId ? { ...node, data: { ...node.data, ...updatedData, }, } : node ) ); markUnsaved(); // 标记有未保存更改 }, [setNodes, markUnsaved]); // 关闭配置模态框 const handleCloseConfigModal = useCallback(() => { setConfigModalVisible(false); setConfigNode(null); }, []); return (
{/* 工具栏 */} {/* 主要内容区域 */}
{/* 节点面板 */} {/* 画布区域 */}
{/* 节点配置弹窗 */}
); }; const WorkflowDesign: React.FC = () => { return ( ); }; export default WorkflowDesign;