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;