import React, { useState, useCallback, useRef, useEffect } from 'react'; import { ReactFlow, applyNodeChanges, applyEdgeChanges, addEdge, reconnectEdge, Background, Controls, Node, Edge, ReactFlowProvider, useReactFlow, EdgeTypes, SelectionMode, useStoreApi, Panel } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; import { nodeTypeMap, nodeTypes, registerNodeType } from './node'; import SideBar from './sideBar/sideBar'; import { convertFlowData } from '@/utils/convertFlowData'; import { exampleFlowData } from '@/pages/flowEditor/test/exampleFlowData'; import LocalNode from '@/pages/flowEditor/node/localNode/LocalNode'; import CustomEdge from './components/customEdge'; import NodeContextMenu from './components/nodeContextMenu'; import EdgeContextMenu from './components/edgeContextMenu'; import NodeEditModal from './components/nodeEditModal'; const edgeTypes: EdgeTypes = { custom: CustomEdge }; const FlowEditorWithProvider: React.FC = () => { return (
); }; const FlowEditor: React.FC = () => { const [nodes, setNodes] = useState([]); const [edges, setEdges] = useState([]); const reactFlowInstance = useReactFlow(); const reactFlowWrapper = useRef(null); const [menu, setMenu] = useState<{ id: string; type: 'node' | 'edge'; top: number; left: number; } | null>(null); const store = useStoreApi(); // 添加编辑弹窗相关状态 const [editingNode, setEditingNode] = useState(null); const [isEditModalOpen, setIsEditModalOpen] = useState(false); const onNodesChange = useCallback( (changes: any) => { const newNodes = applyNodeChanges(changes, nodes); setNodes(newNodes); // 如果需要在节点变化时执行某些操作,可以在这里添加 }, [nodes] ); const onEdgesChange = useCallback( (changes: any) => { const newEdges = applyEdgeChanges(changes, edges); setEdges(newEdges); // 如果需要在边变化时执行某些操作,可以在这里添加 }, [edges] ); const onConnect = useCallback( (params: any) => setEdges((edgesSnapshot) => addEdge({ ...params, type: 'custom' }, edgesSnapshot)), [] ); // 边重新连接处理 const onReconnect = useCallback( (oldEdge: Edge, newConnection: any) => setEdges((els) => reconnectEdge(oldEdge, newConnection, els)), [] ); const onDragOver = useCallback((event: React.DragEvent) => { event.preventDefault(); event.dataTransfer.dropEffect = 'move'; }, []); // 侧边栏节点实例 const onDrop = useCallback( (event: React.DragEvent) => { event.preventDefault(); if (!reactFlowInstance) return; const callBack = event.dataTransfer.getData('application/reactflow'); const nodeData = JSON.parse(callBack); if (typeof nodeData.nodeType === 'undefined' || !nodeData.nodeType) { return; } const position = reactFlowInstance.screenToFlowPosition({ x: event.clientX, y: event.clientY }); const newNode = { id: `${nodeData.nodeType}-${Date.now()}`, type: nodeData.nodeType, position, data: { ...nodeData.data, title: nodeData.nodeName, type: nodeData.nodeType } }; // 将未定义的节点动态追加进nodeTypes const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); if (!nodeMap.includes(nodeData.nodeType)) registerNodeType(nodeData.nodeType, LocalNode, nodeData.nodeName); setNodes((nds) => nds.concat(newNode)); }, [reactFlowInstance] ); useEffect(() => { const { nodes: convertedNodes, edges: convertedEdges } = convertFlowData(exampleFlowData); // 为所有边添加类型 const initialEdges: Edge[] = convertedEdges.map(edge => ({ ...edge, type: 'custom' })); setNodes(convertedNodes); setEdges(initialEdges); }, []); // 节点右键菜单处理 const onNodeContextMenu = useCallback( (event: React.MouseEvent, node: Node) => { event.preventDefault(); const pane = reactFlowWrapper.current?.getBoundingClientRect(); if (!pane) return; setMenu({ id: node.id, type: 'node', top: event.clientY - pane.top, left: event.clientX - pane.left }); }, [setMenu] ); // 节点双击处理 const onNodeDoubleClick = useCallback( (event: React.MouseEvent, node: Node) => { console.log('双击'); setEditingNode(node); setIsEditModalOpen(true); }, [] ); // 边右键菜单处理 const onEdgeContextMenu = useCallback( (event: React.MouseEvent, edge: Edge) => { event.preventDefault(); const pane = reactFlowWrapper.current?.getBoundingClientRect(); if (!pane) return; setMenu({ id: edge.id, type: 'edge', top: event.clientY - pane.top, left: event.clientX - pane.left }); }, [setMenu] ); // 点击画布其他区域关闭菜单 const onPaneClick = useCallback(() => setMenu(null), [setMenu]); // 关闭编辑弹窗 const closeEditModal = useCallback(() => { setIsEditModalOpen(false); setEditingNode(null); }, []); // 保存节点编辑 const saveNodeEdit = useCallback((updatedData: any) => { const updatedNodes = nodes.map((node) => { if (node.id === editingNode?.id) { return { ...node, data: { ...node.data, ...updatedData } }; } return node; }); setNodes(updatedNodes); closeEditModal(); // TODO 如果需要在节点编辑后立即保存到服务器,可以调用保存函数 // saveFlowDataToServer(); }, [nodes, editingNode, closeEditModal]); // 删除节点 const deleteNode = useCallback((node: Node) => { setNodes((nds) => nds.filter((n) => n.id !== node.id)); setEdges((eds) => eds.filter((e) => e.source !== node.id && e.target !== node.id)); setMenu(null); }, []); // 删除边 const deleteEdge = useCallback((edge: Edge) => { setEdges((eds) => eds.filter((e) => e.id !== edge.id)); setMenu(null); }, []); // 编辑节点 const editNode = useCallback((node: Node) => { setMenu(null); setEditingNode(node); setIsEditModalOpen(true); }, []); // 编辑边 const editEdge = useCallback((edge: Edge) => { // 这里可以实现边编辑逻辑 console.log('编辑边:', edge); setMenu(null); }, []); // 复制节点 const copyNode = useCallback((node: Node) => { // 这里可以实现节点复制逻辑 console.log('复制节点:', node); setMenu(null); }, []); // 保存所有节点和边数据到服务器 const saveFlowDataToServer = useCallback(async () => { try { // 准备要发送到服务器的数据 const flowData = { nodes: nodes, edges: edges }; // TODO 接口对接后修改后续的更新操作 console.log('flowData:', flowData); return; // 发送到服务器的示例代码(需要根据您的实际API进行调整) const response = await fetch('/api/flow/save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(flowData) }); if (response.ok) { console.log('Flow data saved successfully'); // 可以添加成功提示 } else { console.error('Failed to save flow data'); // 可以添加失败提示 } } catch (error) { console.error('Error saving flow data:', error); // 可以添加错误提示 } }, [nodes, edges]); return (
从左侧拖拽节点到画布中
{/*节点右键上下文*/} {menu && menu.type === 'node' && (
n.id === menu.id)!} onDelete={deleteNode} onEdit={editNode} onCopy={copyNode} />
)} {/*边右键上下文*/} {menu && menu.type === 'edge' && (
e.id === menu.id)!} onDelete={deleteEdge} onEdit={editEdge} />
)} {/*节点双击/节点编辑上下文*/}
); }; export default FlowEditorWithProvider;