From ff3090522ff7013821867ea066f5314828213567 Mon Sep 17 00:00:00 2001 From: ZLY Date: Tue, 2 Sep 2025 16:23:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(flowEditor):=20=E4=BC=98=E5=8C=96=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E8=BF=9E=E6=8E=A5=E9=80=BB=E8=BE=91=E5=B9=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 getHandleType 函数,用于获取 handle 类型 (api 或 data) - 新增 validateDataType函数,用于验证数据类型是否匹配 - 修改 onConnect 和 onReconnect 回调,增加类型验证逻辑 - 优化节点双击编辑逻辑,增加不可编辑节点类型 - 调整面板位置和样式,提升用户体验 - 新增 defaultNodeTypes 接口,统一节点数据结构 --- src/pages/flowEditor/index.tsx | 155 +++++++++++++++++- .../flowEditor/node/basicNode/BasicNode.tsx | 15 +- src/pages/flowEditor/node/endNode/EndNode.tsx | 15 +- .../flowEditor/node/startNode/StartNode.tsx | 15 +- .../flowEditor/node/types/defaultType.ts | 12 ++ 5 files changed, 164 insertions(+), 48 deletions(-) create mode 100644 src/pages/flowEditor/node/types/defaultType.ts diff --git a/src/pages/flowEditor/index.tsx b/src/pages/flowEditor/index.tsx index 140c085..ed0405d 100644 --- a/src/pages/flowEditor/index.tsx +++ b/src/pages/flowEditor/index.tsx @@ -17,6 +17,7 @@ import { Panel } from '@xyflow/react'; import '@xyflow/react/dist/style.css'; +import { Button } from '@arco-design/web-react'; import { nodeTypeMap, nodeTypes, registerNodeType } from './node'; import SideBar from './sideBar/sideBar'; import { convertFlowData } from '@/utils/convertFlowData'; @@ -26,6 +27,7 @@ import CustomEdge from './components/customEdge'; import NodeContextMenu from './components/nodeContextMenu'; import EdgeContextMenu from './components/edgeContextMenu'; import NodeEditModal from './components/nodeEditModal'; +import { defaultNodeTypes } from '@/pages/flowEditor/node/types/defaultType'; const edgeTypes: EdgeTypes = { custom: CustomEdge @@ -59,11 +61,82 @@ const FlowEditor: React.FC = () => { const [editingNode, setEditingNode] = useState(null); const [isEditModalOpen, setIsEditModalOpen] = useState(false); + // 获取handle类型 (api或data) + const getHandleType = (handleId: string, nodeParams: any) => { + // 检查是否为api类型的handle + const apiOuts = nodeParams.apiOuts || []; + const apiIns = nodeParams.apiIns || []; + + if (apiOuts.some((api: any) => api.name === handleId) || + apiIns.some((api: any) => api.name === handleId)) { + return 'api'; + } + + // 检查是否为data类型的handle + const dataOuts = nodeParams.dataOuts || []; + const dataIns = nodeParams.dataIns || []; + + if (dataOuts.some((data: any) => data.name === handleId) || + dataIns.some((data: any) => data.name === handleId)) { + return 'data'; + } + + // 默认为data类型 + return 'data'; + }; + + // 验证数据类型是否匹配 + const validateDataType = (sourceNode: defaultNodeTypes, targetNode: defaultNodeTypes, sourceHandleId: string, targetHandleId: string) => { + const sourceParams = sourceNode.data?.parameters || {}; + const targetParams = targetNode.data?.parameters || {}; + + // 获取源节点的输出参数 + let sourceDataType = ''; + const sourceApiOuts = sourceParams.apiOuts || []; + const sourceDataOuts = sourceParams.dataOuts || []; + + // 查找源handle的数据类型 + const sourceApi = sourceApiOuts.find((api: any) => api.name === sourceHandleId); + const sourceData = sourceDataOuts.find((data: any) => data.name === sourceHandleId); + + if (sourceApi) { + sourceDataType = sourceApi.dataType || ''; + } + else if (sourceData) { + sourceDataType = sourceData.dataType || ''; + } + + // 获取目标节点的输入参数 + let targetDataType = ''; + const targetApiIns = targetParams.apiIns || []; + const targetDataIns = targetParams.dataIns || []; + + // 查找目标handle的数据类型 + const targetApi = targetApiIns.find((api: any) => api.name === targetHandleId); + const targetData = targetDataIns.find((data: any) => data.name === targetHandleId); + + if (targetApi) { + targetDataType = targetApi.dataType || ''; + } + else if (targetData) { + targetDataType = targetData.dataType || ''; + } + + // 如果任一数据类型为空,则允许连接 + if (!sourceDataType || !targetDataType) { + return true; + } + + // 比较数据类型是否匹配 + return sourceDataType === targetDataType; + }; + const onNodesChange = useCallback( (changes: any) => { const newNodes = applyNodeChanges(changes, nodes); setNodes(newNodes); // 如果需要在节点变化时执行某些操作,可以在这里添加 + onPaneClick(); }, [nodes] ); @@ -73,20 +146,84 @@ const FlowEditor: React.FC = () => { const newEdges = applyEdgeChanges(changes, edges); setEdges(newEdges); // 如果需要在边变化时执行某些操作,可以在这里添加 + onPaneClick(); }, [edges] ); const onConnect = useCallback( - (params: any) => setEdges((edgesSnapshot) => addEdge({ ...params, type: 'custom' }, edgesSnapshot)), - [] + (params: any) => { + // 获取源节点和目标节点 + const sourceNode = nodes.find(node => node.id === params.source); + const targetNode = nodes.find(node => node.id === params.target); + + // 如果找不到节点,不创建连接 + if (!sourceNode || !targetNode) { + return; + } + + // 获取源节点和目标节点的参数信息 + const sourceParams = sourceNode.data?.parameters || {}; + const targetParams = targetNode.data?.parameters || {}; + + // 获取源handle和目标handle的类型 (api或data) + const sourceHandleType = getHandleType(params.sourceHandle, sourceParams); + const targetHandleType = getHandleType(params.targetHandle, targetParams); + + // 验证连接类型是否匹配 (api只能连api, data只能连data) + if (sourceHandleType !== targetHandleType) { + console.warn('连接类型不匹配: ', sourceHandleType, targetHandleType); + return; + } + + // 验证数据类型是否匹配 + if (!validateDataType(sourceNode, targetNode, params.sourceHandle, params.targetHandle)) { + console.warn('数据类型不匹配'); + return; + } + + // 如果验证通过,创建连接 + setEdges((edgesSnapshot) => addEdge({ ...params, type: 'custom' }, edgesSnapshot)); + }, + [nodes] ); // 边重新连接处理 const onReconnect = useCallback( - (oldEdge: Edge, newConnection: any) => - setEdges((els) => reconnectEdge(oldEdge, newConnection, els)), - [] + (oldEdge: Edge, newConnection: any) => { + // 获取源节点和目标节点 + const sourceNode = nodes.find(node => node.id === newConnection.source); + const targetNode = nodes.find(node => node.id === newConnection.target); + + // 如果找不到节点,不创建连接 + if (!sourceNode || !targetNode) { + return; + } + + // 获取源节点和目标节点的参数信息 + const sourceParams = sourceNode.data?.parameters || {}; + const targetParams = targetNode.data?.parameters || {}; + + // 获取源handle和目标handle的类型 (api或data) + const sourceHandleType = getHandleType(newConnection.sourceHandle, sourceParams); + const targetHandleType = getHandleType(newConnection.targetHandle, targetParams); + + // 验证连接类型是否匹配 (api只能连api, data只能连data) + if (sourceHandleType !== targetHandleType) { + console.warn('连接类型不匹配: ', sourceHandleType, targetHandleType); + return; + } + + // 验证数据类型是否匹配 + if (!validateDataType(sourceNode, targetNode, newConnection.sourceHandle, newConnection.targetHandle)) { + console.warn('数据类型不匹配'); + return; + } + + // 如果验证通过,重新连接 + setEdges((els) => reconnectEdge(oldEdge, newConnection, els)); + }, + [nodes] ); const onDragOver = useCallback((event: React.DragEvent) => { @@ -161,8 +298,8 @@ const FlowEditor: React.FC = () => { // 节点双击处理 const onNodeDoubleClick = useCallback( (event: React.MouseEvent, node: Node) => { - // 与门和或门不可编辑 - if (['AND', 'OR'].includes(node.type)) return; + // 不可编辑的类型 + if (['AND', 'OR', 'JSON2STR', 'STR2JSON'].includes(node.type)) return; setEditingNode(node); setIsEditModalOpen(true); }, @@ -312,9 +449,9 @@ const FlowEditor: React.FC = () => { > - +
从左侧拖拽节点到画布中
- +
diff --git a/src/pages/flowEditor/node/basicNode/BasicNode.tsx b/src/pages/flowEditor/node/basicNode/BasicNode.tsx index 2029b0d..c302fe0 100644 --- a/src/pages/flowEditor/node/basicNode/BasicNode.tsx +++ b/src/pages/flowEditor/node/basicNode/BasicNode.tsx @@ -2,21 +2,10 @@ import React from 'react'; import styles from '@/pages/flowEditor/node/style/base.module.less'; import NodeContent from '@/pages/flowEditor/components/nodeContent'; import { useStore } from '@xyflow/react'; +import { defaultNodeTypes } from '@/pages/flowEditor/node/types/defaultType'; -interface BasicNodeData { - title?: string; - parameters?: { - dataIns?: any[]; - dataOuts?: any[]; - apiIns?: any[]; - apiOuts?: any[]; - }; - showFooter?: boolean; - [key: string]: any; -} - -const BasicNode = ({ data, id }: { data: BasicNodeData; id: string }) => { +const BasicNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => { const title = data.title || '基础节点'; // 获取节点选中状态 - 适配React Flow v12 API diff --git a/src/pages/flowEditor/node/endNode/EndNode.tsx b/src/pages/flowEditor/node/endNode/EndNode.tsx index e5c3728..27852d9 100644 --- a/src/pages/flowEditor/node/endNode/EndNode.tsx +++ b/src/pages/flowEditor/node/endNode/EndNode.tsx @@ -2,21 +2,10 @@ import React from 'react'; import styles from '@/pages/flowEditor/node/style/base.module.less'; import NodeContent from '@/pages/flowEditor/components/nodeContent'; import { useStore } from '@xyflow/react'; +import { defaultNodeTypes } from '@/pages/flowEditor/node/types/defaultType'; -interface EndNodeData { - title?: string; - parameters?: { - dataIns?: any[]; - dataOuts?: any[]; - apiIns?: any[]; - apiOuts?: any[]; - }; - showFooter?: boolean; - [key: string]: any; -} - -const EndNode = ({ data, id }: { data: EndNodeData; id: string }) => { +const EndNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => { const title = data.title || '结束'; // 获取节点选中状态 - 适配React Flow v12 API diff --git a/src/pages/flowEditor/node/startNode/StartNode.tsx b/src/pages/flowEditor/node/startNode/StartNode.tsx index 3aaf18a..6ea0b48 100644 --- a/src/pages/flowEditor/node/startNode/StartNode.tsx +++ b/src/pages/flowEditor/node/startNode/StartNode.tsx @@ -2,21 +2,10 @@ import React from 'react'; import styles from '@/pages/flowEditor/node/style/base.module.less'; import NodeContent from '@/pages/flowEditor/components/nodeContent'; import { useStore } from '@xyflow/react'; +import { defaultNodeTypes } from '@/pages/flowEditor/node/types/defaultType'; -interface StartNodeData { - title?: string; - parameters?: { - dataIns?: any[]; - dataOuts?: any[]; - apiIns?: any[]; - apiOuts?: any[]; - }; - showFooter?: boolean; - [key: string]: any; -} - -const StartNode = ({ data, id }: { data: StartNodeData; id: string }) => { +const StartNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => { const title = data.title || '开始'; // 获取节点选中状态 - 适配React Flow v12 API diff --git a/src/pages/flowEditor/node/types/defaultType.ts b/src/pages/flowEditor/node/types/defaultType.ts new file mode 100644 index 0000000..da62869 --- /dev/null +++ b/src/pages/flowEditor/node/types/defaultType.ts @@ -0,0 +1,12 @@ +export interface defaultNodeTypes { + title?: string; + parameters?: { + dataIns?: any[]; + dataOuts?: any[]; + apiIns?: any[]; + apiOuts?: any[]; + }; + showFooter?: boolean; + + [key: string]: any; +} \ No newline at end of file