From d81df7974024ca3ed414424d3a467ed6bc8af1bb Mon Sep 17 00:00:00 2001 From: ZLY Date: Fri, 27 Feb 2026 14:53:18 +0800 Subject: [PATCH] =?UTF-8?q?refactor(flow):=20=E9=87=8D=E6=9E=84=E7=BC=96?= =?UTF-8?q?=E6=8E=92=E5=BA=95=E5=B1=82=E9=80=BB=E8=BE=91=EF=BC=8C=E6=8A=BD?= =?UTF-8?q?=E7=A6=BB=E8=8A=82=E7=82=B9=E9=80=BB=E8=BE=91=EF=BC=8C=E5=B0=81?= =?UTF-8?q?=E8=A3=85=E8=8A=82=E7=82=B9=E5=B7=A5=E5=8E=82=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96useFlowCallbacks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useFlowCallbacks.ts | 2327 ++++++++--------- src/hooks/useFlowEditorState.ts | 104 +- src/pages/flowEditor/FlowEditorMain.tsx | 376 ++- src/pages/flowEditor/components/actionBar.tsx | 62 +- src/pages/ideContainer/logBar.tsx | 156 +- src/store/ideContainer.ts | 127 +- src/utils/convertFlowData.ts | 477 ++-- src/utils/flow/edgeInsertionFactory.ts | 54 + src/utils/flow/loopFactory.ts | 104 + src/utils/flow/nodeFactory.ts | 79 + src/utils/flow/nodeOnboarding.ts | 61 + src/utils/flow/nodeRegistry.ts | 40 + src/utils/flow/runtime.ts | 34 + src/utils/flow/snapshot.ts | 22 + src/utils/flowCommon.ts | 77 +- 15 files changed, 2278 insertions(+), 1822 deletions(-) create mode 100644 src/utils/flow/edgeInsertionFactory.ts create mode 100644 src/utils/flow/loopFactory.ts create mode 100644 src/utils/flow/nodeFactory.ts create mode 100644 src/utils/flow/nodeOnboarding.ts create mode 100644 src/utils/flow/nodeRegistry.ts create mode 100644 src/utils/flow/runtime.ts create mode 100644 src/utils/flow/snapshot.ts diff --git a/src/hooks/useFlowCallbacks.ts b/src/hooks/useFlowCallbacks.ts index 4b2dbc1..d16660d 100644 --- a/src/hooks/useFlowCallbacks.ts +++ b/src/hooks/useFlowCallbacks.ts @@ -5,15 +5,17 @@ import { addEdge, reconnectEdge, Node, - Edge + Edge, } from '@xyflow/react'; import { getAppInfoNew, setMainFlowNew, setSubFlowNew } from '@/api/appRes'; import { Message } from '@arco-design/web-react'; -import { nodeTypeMap, registerNodeType } from '@/components/FlowEditor/node'; -import { convertFlowData, reverseConvertFlowData, revertFlowData } from '@/utils/convertFlowData'; +import { + convertFlowData, + reverseConvertFlowData, + revertFlowData, +} from '@/utils/convertFlowData'; import { localNodeData } from '@/pages/flowEditor/sideBar/config/localNodeData'; import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines'; -import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode'; import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode'; import { updateCanvasDataMap, @@ -23,28 +25,67 @@ import { updateEventListOld, addRuntimeLog, clearRuntimeLogs, - updateRuntimeId, updateFlowData + updateRuntimeId, + updateFlowData, } from '@/store/ideContainer'; import { validateAllNodes, showValidationErrors, - validateAllEdges + validateAllEdges, } from '@/components/FlowEditor/nodeEditors/validators/nodeValidators'; -import { - getHandleType, - validateDataType, - getNodeComponent -} from '@/utils/flowCommon'; +import { getHandleType, validateDataType } from '@/utils/flowCommon'; import { projectFlowHandle } from '@/pages/flowEditor/utils/projectFlowHandle'; import { appFLowHandle } from '@/pages/flowEditor/utils/appFlowhandle'; -import { handelEventNodeList, updateEvent, upDatePublish } from '@/pages/flowEditor/utils/common'; +import { + handelEventNodeList, + updateEvent, + upDatePublish, +} from '@/pages/flowEditor/utils/common'; +import { getCurrentAppKey } from '@/utils/flow/runtime'; +import { + ensureNodeTypeRegistered, + resolveNodeComponent, +} from '@/utils/flow/nodeRegistry'; +import { + dispatchFlowSnapshot, + dispatchFlowSnapshotAsync, +} from '@/utils/flow/snapshot'; +import { + createLoopGroupEdge, + createLoopInsertConnectionEdges, + createLoopNodePair, +} from '@/utils/flow/loopFactory'; +import { + buildInsertedNodeEdges, + resolveInsertedNodeHandles, +} from '@/utils/flow/edgeInsertionFactory'; +import { + buildRuntimeNode, + resolveNodeDefinition, +} from '@/utils/flow/nodeOnboarding'; import { Dispatch } from 'redux'; -import { getAppListBySceneId, runMainFlow, runSubFlow, stopApp, pauseApp, resumeApp, reRunApp } from '@/api/apps'; +import { + getAppListBySceneId, + runMainFlow, + runSubFlow, + stopApp, + pauseApp, + resumeApp, + reRunApp, +} from '@/api/apps'; import store from '@/store'; -import { updateAppEvent, updateAppEventChannel, updateAppFlowData } from '@/api/appEvent'; +import { + updateAppEvent, + updateAppEventChannel, + updateAppFlowData, +} from '@/api/appEvent'; import { getUrlParams, sleep } from '@/utils/common'; -import { queryEventItemBySceneIdOld, deleteEventSub, deleteEventPub } from '@/api/event'; +import { + queryEventItemBySceneIdOld, + deleteEventSub, + deleteEventPub, +} from '@/api/event'; export const useFlowCallbacks = ( nodes: Node[], @@ -70,388 +111,397 @@ export const useFlowCallbacks = ( setIsEditModalOpen: React.Dispatch>, edgeForNodeAdd: Edge | null, setEdgeForNodeAdd: React.Dispatch>, - positionForNodeAdd: { x: number, y: number } | null, - setPositionForNodeAdd: React.Dispatch>, + positionForNodeAdd: { x: number; y: number } | null, + setPositionForNodeAdd: React.Dispatch< + React.SetStateAction<{ x: number; y: number } | null> + >, setIsDelete: React.Dispatch> ) => { const { getGuidelines, clearGuidelines } = useAlignmentGuidelines(); // 辅助函数:获取当前应用/子流程的唯一标识符 - const getCurrentAppKey = useCallback(() => { + const getCurrentFlowAppKey = useCallback(() => { const { currentAppData } = store.getState().ideContainer; - if (!currentAppData) return null; - // 如果是子流程(key包含'sub'),使用key作为标识符 - if (currentAppData.key && currentAppData.key.includes('sub')) { - return currentAppData.key; - } - // 否则使用id或initialData.appId - return currentAppData.id || initialData?.appId; + return getCurrentAppKey(currentAppData) || initialData?.appId; }, [initialData]); // region 画布操作 // 节点变更处理,添加防抖机制 - const onNodesChange = useCallback((changes: any) => { - // 深度克隆节点数组以避免修改冻结的对象 - const clonedNodes = JSON.parse(JSON.stringify(nodes)); - const newNodes = applyNodeChanges(changes, clonedNodes); - setNodes(newNodes); - // 如果需要在节点变化时执行某些操作,可以在这里添加 - - // 只有当变化是节点位置变化时才不立即记录历史 - const isPositionChange = changes.some((change: any) => - change.type === 'position' && change.dragging === false - ); - - // 如果是位置变化结束或者不是位置变化,则记录历史 - if (isPositionChange || !changes.some((change: any) => change.type === 'position')) { - // 清除之前的定时器 - if (historyTimeoutRef.current) { - clearTimeout(historyTimeoutRef.current); - } + const onNodesChange = useCallback( + (changes: any) => { + // 深度克隆节点数组以避免修改冻结的对象 + const clonedNodes = JSON.parse(JSON.stringify(nodes)); + const newNodes = applyNodeChanges(changes, clonedNodes); + setNodes(newNodes); + // 如果需要在节点变化时执行某些操作,可以在这里添加 + + // 只有当变化是节点位置变化时才不立即记录历史 + const isPositionChange = changes.some( + (change: any) => change.type === 'position' && change.dragging === false + ); + + // 如果是位置变化结束或者不是位置变化,则记录历史 + if ( + isPositionChange || + !changes.some((change: any) => change.type === 'position') + ) { + // 清除之前的定时器 + if (historyTimeoutRef.current) { + clearTimeout(historyTimeoutRef.current); + } - // 设置新的定时器,延迟记录历史记录 - historyTimeoutRef.current = setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { nodes: [...newNodes], edges: [...edges] } - }); - document.dispatchEvent(event); - }, 100); - } - }, [nodes, edges]); + // 设置新的定时器,延迟记录历史记录 + historyTimeoutRef.current = dispatchFlowSnapshotAsync( + { nodes: [...newNodes], edges: [...edges] }, + 100 + ); + } + }, + [nodes, edges] + ); // 边变更处理 - const onEdgesChange = useCallback((changes: any) => { - const newEdges = applyEdgeChanges(changes, edges); - setEdges(newEdges); - // 如果需要在边变化时执行某些操作,可以在这里添加 - - // 边的变化立即记录历史 - const event = new CustomEvent('takeSnapshot', { - detail: { nodes: [...nodes], edges: [...newEdges] } - }); - document.dispatchEvent(event); - }, [edges, nodes]); + const onEdgesChange = useCallback( + (changes: any) => { + const newEdges = applyEdgeChanges(changes, edges); + setEdges(newEdges); + // 如果需要在边变化时执行某些操作,可以在这里添加 + + // 边的变化立即记录历史 + dispatchFlowSnapshot({ nodes: [...nodes], edges: [...newEdges] }); + }, + [edges, nodes] + ); // 节点删除处理 const onNodesDelete = useCallback((deletedNodes: any) => { setIsDelete(true); }, []); // 边连接处理 - const onConnect = useCallback((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: any = sourceNode.data?.parameters || {}; - const targetParams: any = 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; - } + const onConnect = useCallback( + (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: any = sourceNode.data?.parameters || {}; + const targetParams: any = 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; - } + // 验证数据类型是否匹配 + if ( + !validateDataType( + sourceNode, + targetNode, + params.sourceHandle, + params.targetHandle + ) + ) { + console.warn('数据类型不匹配'); + return; + } - // 如果验证通过,创建连接 - setEdges((edgesSnapshot: Edge[]) => { - // 创建带有事件信息的连接 - const edgeParams = { ...params, type: 'custom' }; + // 如果验证通过,创建连接 + setEdges((edgesSnapshot: Edge[]) => { + // 创建带有事件信息的连接 + const edgeParams = { ...params, type: 'custom' }; - // 添加lineType字段,用于区分API连接和数据连接 - edgeParams.data = { - ...edgeParams.data, - lineType: sourceHandleType // 'api' 或 'data' - }; + // 添加lineType字段,用于区分API连接和数据连接 + edgeParams.data = { + ...edgeParams.data, + lineType: sourceHandleType, // 'api' 或 'data' + }; - // 对于数据类型的边,需要额外验证dataIns和dataOuts中的数据类型是否一致 - if (sourceHandleType === 'data') { - // 查找源节点的dataOuts中对应的数据 - const sourceDataOut = (sourceParams.dataOuts || []).find((dataOut: any) => - dataOut.name === params.sourceHandle || dataOut.id === params.sourceHandle); - - // 查找目标节点的dataIns中对应的数据 - const targetDataIn = (targetParams.dataIns || []).find((dataIn: any) => - dataIn.name === params.targetHandle || dataIn.id === params.targetHandle); - - // 验证数据类型是否一致 - if (sourceDataOut && targetDataIn && sourceDataOut.dataType !== targetDataIn.dataType) { - console.warn('数据类型不匹配,源节点数据类型:', sourceDataOut.dataType, '目标节点数据类型:', targetDataIn.dataType); - Message.warning(`数据类型不匹配,源节点数据类型: ${sourceDataOut.dataType},目标节点数据类型: ${targetDataIn.dataType}`); - return edgesSnapshot; // 不创建连接 + // 对于数据类型的边,需要额外验证dataIns和dataOuts中的数据类型是否一致 + if (sourceHandleType === 'data') { + // 查找源节点的dataOuts中对应的数据 + const sourceDataOut = (sourceParams.dataOuts || []).find( + (dataOut: any) => + dataOut.name === params.sourceHandle || + dataOut.id === params.sourceHandle + ); + + // 查找目标节点的dataIns中对应的数据 + const targetDataIn = (targetParams.dataIns || []).find( + (dataIn: any) => + dataIn.name === params.targetHandle || + dataIn.id === params.targetHandle + ); + + // 验证数据类型是否一致 + if ( + sourceDataOut && + targetDataIn && + sourceDataOut.dataType !== targetDataIn.dataType + ) { + console.warn( + '数据类型不匹配,源节点数据类型:', + sourceDataOut.dataType, + '目标节点数据类型:', + targetDataIn.dataType + ); + Message.warning( + `数据类型不匹配,源节点数据类型: ${sourceDataOut.dataType},目标节点数据类型: ${targetDataIn.dataType}` + ); + return edgesSnapshot; // 不创建连接 + } } - } - // 检查源节点和目标节点是否都有事件信息 - const sourceApi = (sourceParams.apiOuts || []).find((api: any) => (api?.eventId || api.name || api.id) === params.sourceHandle); - const targetApi = (targetParams.apiIns || []).find((api: any) => (api?.eventId || api.name || api.id) === params.targetHandle); + // 检查源节点和目标节点是否都有事件信息 + const sourceApi = (sourceParams.apiOuts || []).find( + (api: any) => + (api?.eventId || api.name || api.id) === params.sourceHandle + ); + const targetApi = (targetParams.apiIns || []).find( + (api: any) => + (api?.eventId || api.name || api.id) === params.targetHandle + ); - // 如果源节点有事件topic信息 - if (sourceApi && sourceApi.topic) { - // 如果目标节点的topic是**empty**或没有topic,则使用源节点的事件信息 - if (!targetApi || !targetApi.topic || targetApi.topic.includes('**empty**')) { - edgeParams.data = { - ...edgeParams.data, - lineType: 'api', - displayData: { - name: sourceApi.eventName, - eventId: sourceApi.eventId, - topic: sourceApi.topic - } - }; + // 如果源节点有事件topic信息 + if (sourceApi && sourceApi.topic) { + // 如果目标节点的topic是**empty**或没有topic,则使用源节点的事件信息 + if ( + !targetApi || + !targetApi.topic || + targetApi.topic.includes('**empty**') + ) { + edgeParams.data = { + ...edgeParams.data, + lineType: 'api', + displayData: { + name: sourceApi.eventName, + eventId: sourceApi.eventId, + topic: sourceApi.topic, + }, + }; + } + // 如果两个节点都有非empty的topic,则以源节点为准 + else if ( + sourceApi.topic && + targetApi.topic && + !sourceApi.topic.includes('**empty**') && + !targetApi.topic.includes('**empty**') + ) { + edgeParams.data = { + ...edgeParams.data, + lineType: 'api', + displayData: { + name: sourceApi.eventName, + eventId: sourceApi.eventId, + topic: sourceApi.topic, + }, + }; + } } - // 如果两个节点都有非empty的topic,则以源节点为准 - else if (sourceApi.topic && targetApi.topic && - !sourceApi.topic.includes('**empty**') && - !targetApi.topic.includes('**empty**')) { + // 如果源节点没有事件信息,但目标节点有 + else if ( + targetApi && + targetApi.topic && + !targetApi.topic.includes('**empty**') + ) { edgeParams.data = { ...edgeParams.data, lineType: 'api', displayData: { - name: sourceApi.eventName, - eventId: sourceApi.eventId, - topic: sourceApi.topic - } + name: targetApi.eventName, + eventId: targetApi.eventId, + topic: targetApi.topic, + }, }; } - } - // 如果源节点没有事件信息,但目标节点有 - else if (targetApi && targetApi.topic && !targetApi.topic.includes('**empty**')) { - edgeParams.data = { - ...edgeParams.data, - lineType: 'api', - displayData: { - name: targetApi.eventName, - eventId: targetApi.eventId, - topic: targetApi.topic - } - }; - } - const newEdges = addEdge(edgeParams, edgesSnapshot); + const newEdges = addEdge(edgeParams, edgesSnapshot); - // 连接建立后记录历史 - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { nodes: [...nodes], edges: [...newEdges] } - }); - document.dispatchEvent(event); - }, 0); + // 连接建立后记录历史 + dispatchFlowSnapshotAsync({ nodes: [...nodes], edges: [...newEdges] }); - return newEdges; - }); - }, [nodes]); + return newEdges; + }); + }, + [nodes] + ); // 边重新连接处理 - const onReconnect = useCallback((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); + const onReconnect = useCallback( + (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; + } - // 验证连接类型是否匹配 (api只能连api, data只能连data) - if (sourceHandleType !== targetHandleType) { - console.warn('连接类型不匹配: ', sourceHandleType, targetHandleType); - 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; - } + // 验证数据类型是否匹配 + if ( + !validateDataType( + sourceNode, + targetNode, + newConnection.sourceHandle, + newConnection.targetHandle + ) + ) { + console.warn('数据类型不匹配'); + return; + } - // 如果验证通过,重新连接 - setEdges((els) => reconnectEdge(oldEdge, newConnection, els)); - }, [nodes]); + // 如果验证通过,重新连接 + setEdges((els) => reconnectEdge(oldEdge, newConnection, els)); + }, + [nodes] + ); // 拖动处理 const onDragOver = useCallback((event: React.DragEvent) => { event.preventDefault(); event.dataTransfer.dropEffect = 'move'; }, []); - // 侧边栏节点实例 - const onDrop = useCallback((event: React.DragEvent) => { - event.preventDefault(); - if (!reactFlowInstance) return; + const getNodeDefinition = useCallback( + (nodeType: string, fallbackNode?: any) => + resolveNodeDefinition(localNodeData, nodeType, fallbackNode), + [] + ); - 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 clearAddNodeContext = useCallback(() => { + setEdgeForNodeAdd(null); + setPositionForNodeAdd(null); + }, []); - // 特殊处理循环节点,添加开始和结束节点 - if (nodeData.nodeType === 'LOOP') { - addLoopNodeWithStartEnd(position, nodeData); - return; - } + // 添加循环节点及其开始和结束节点 + const addLoopNodeWithStartEnd = useCallback( + (position: { x: number; y: number }) => { + const { loopStartNode, loopEndNode } = createLoopNodePair(position); - const newNode = { - id: `${nodeData.nodeType}-${Date.now()}`, - type: nodeData.nodeType, - position, - data: { ...nodeData.data, title: nodeData.nodeName, type: nodeData.nodeType } - }; + // 创建连接边(连接循环开始和结束节点的顶部连接点) + const newEdges = [createLoopGroupEdge(loopStartNode.id, loopEndNode.id)]; - // 将未定义的节点动态追加进nodeTypes - const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); - // 目前默认添加的都是系统组件/本地组件 - if (!nodeMap.includes(nodeData.nodeType)) { - registerNodeType(nodeData.nodeType, LocalNode, nodeData.nodeName); - } + // 将未定义的节点动态追加进nodeTypes + ensureNodeTypeRegistered('LOOP', '循环', LoopNode); - setNodes((nds: Node[]) => { - const newNodes = nds.concat(newNode); + setNodes((nds: Node[]) => { + const newNodes = [...nds, loopStartNode, loopEndNode]; - // 添加节点后记录历史 - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { nodes: [...newNodes], edges: [...edges] } + // 添加节点后记录历史 + dispatchFlowSnapshotAsync({ + nodes: [...newNodes], + edges: [...edges, ...newEdges], }); - document.dispatchEvent(event); - }, 0); - return newNodes; - }); - }, [reactFlowInstance, edges]); - // 添加循环节点及其开始和结束节点 - const addLoopNodeWithStartEnd = useCallback((position: { x: number, y: number }, nodeData: any) => { - // 创建循环开始节点 - const loopStartNode = { - id: `LOOP_START-${Date.now()}`, - type: 'LOOP', // 使用本地节点类型 - position: { x: position.x, y: position.y }, - data: { - title: '循环开始', - type: 'LOOP_START', - parameters: { - apiIns: [{ name: 'start', desc: '', dataType: '', defaultValue: '' }], - apiOuts: [{ name: 'done', desc: '', dataType: '', defaultValue: '' }], - dataIns: [], - dataOuts: [] - }, - component: {} - } + return newNodes; + }); - }; + setEdges((eds: Edge[]) => { + const updatedEdges = [...eds, ...newEdges]; - // 创建循环结束节点 - const loopEndNode = { - id: `LOOP_END-${Date.now()}`, - type: 'LOOP', // 使用本地节点类型 - position: { x: position.x + 400, y: position.y }, - data: { - title: '循环结束', - type: 'LOOP_END', - parameters: { - apiIns: [{ name: 'continue', desc: '', dataType: '', defaultValue: '' }], - apiOuts: [{ name: 'break', desc: '', dataType: '', defaultValue: '' }], - dataIns: [{ - 'arrayType': null, - 'dataType': 'INTEGER', - 'defaultValue': 10, - 'desc': '最大循环次数', - 'id': 'maxTime' - }], - dataOuts: [] - }, - component: { - type: 'LOOP_END', - customDef: JSON.stringify({ - apiOutIds: ['continue', 'break'], - conditions: [], - loopStartNodeId: loopStartNode.id - }), - loopStartNodeId: loopStartNode.id // 这里的参数是为了提供在组件内部处理数据是使用,最后这个字段要序列化后放进customDef - } + // 添加边后记录历史 + dispatchFlowSnapshotAsync({ + nodes: [...nodes, loopStartNode, loopEndNode], + edges: [...updatedEdges], + }); + + return updatedEdges; + }); + }, + [nodes, edges] + ); + + const addNodeOnCanvas = useCallback( + ( + nodeType: string, + nodeDefinition: any, + position: { x: number; y: number } + ) => { + if (nodeType === 'LOOP') { + addLoopNodeWithStartEnd(position); + return; } - }; - loopStartNode.data.component = { - type: 'LOOP_START', - customDef: JSON.stringify({ loopEndNodeId: loopEndNode.id }) - }; + const { eventList } = store.getState().ideContainer; + const newNode = buildRuntimeNode({ + nodeType, + nodeDefinition, + position, + eventList, + eventIdMode: 'eventIdOptional', + }); - // 创建连接边(连接循环开始和结束节点的顶部连接点) - const newEdges = [ - { - id: `${loopStartNode.id}-${loopEndNode.id}-group`, - source: loopStartNode.id, - target: loopEndNode.id, - sourceHandle: `${loopStartNode.id}-group`, - targetHandle: `${loopEndNode.id}-group`, - type: 'custom' - } - ]; + setNodes((nds: Node[]) => { + const newNodes = nds.concat(newNode); - // 将未定义的节点动态追加进nodeTypes - const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); - if (!nodeMap.includes('LOOP')) { - registerNodeType('LOOP', LoopNode, '循环'); - } + dispatchFlowSnapshotAsync({ nodes: [...newNodes], edges: [...edges] }); - setNodes((nds: Node[]) => { - const newNodes = [...nds, loopStartNode, loopEndNode]; + return newNodes; + }); + }, + [addLoopNodeWithStartEnd, edges] + ); - // 添加节点后记录历史 - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { nodes: [...newNodes], edges: [...edges, ...newEdges] } - }); - document.dispatchEvent(event); - }, 0); + // 侧边栏节点实例 + const onDrop = useCallback( + (event: React.DragEvent) => { + event.preventDefault(); - return newNodes; - }); + if (!reactFlowInstance) return; - setEdges((eds: Edge[]) => { - const updatedEdges = [...eds, ...newEdges]; + const callBack = event.dataTransfer.getData('application/reactflow'); + const nodeData = JSON.parse(callBack); + if (typeof nodeData.nodeType === 'undefined' || !nodeData.nodeType) { + return; + } - // 添加边后记录历史 - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { nodes: [...nodes, loopStartNode, loopEndNode], edges: [...updatedEdges] } - }); - document.dispatchEvent(event); - }, 0); + const position = reactFlowInstance.screenToFlowPosition({ + x: event.clientX, + y: event.clientY, + }); - return updatedEdges; - }); - }, [nodes, edges]); + const nodeDefinition = getNodeDefinition(nodeData.nodeType, nodeData); + if (!nodeDefinition) return; + + addNodeOnCanvas(nodeData.nodeType, nodeDefinition, position); + }, + [reactFlowInstance, getNodeDefinition, addNodeOnCanvas] + ); // 节点拖拽处理 - const onNodeDrag = useCallback((_: any, node: Node) => { - // 获取对齐线 - getGuidelines(node, nodes); - }, [nodes, getGuidelines]); + const onNodeDrag = useCallback( + (_: any, node: Node) => { + // 获取对齐线 + getGuidelines(node, nodes); + }, + [nodes, getGuidelines] + ); // 节点拖拽结束处理 const onNodeDragStop = useCallback(() => { // 清除对齐线 @@ -462,54 +512,70 @@ export const useFlowCallbacks = ( // region 画布数据处理 // 初始化画布数据 const initializeCanvasData = useCallback(() => { - const appKey = getCurrentAppKey(); + const appKey = getCurrentFlowAppKey(); if (appKey && canvasDataMap[appKey]) { const { edges, nodes } = canvasDataMap[appKey]; setNodes(nodes); setEdges(edges); - } - else { + } else { // 首次进入 - if (useDefault) projectFlowHandle(initialData, useDefault, setNodes, setEdges, dispatch, canvasDataMap); + if (useDefault) + projectFlowHandle( + initialData, + useDefault, + setNodes, + setEdges, + dispatch, + canvasDataMap + ); else appFLowHandle(initialData, useDefault, setNodes, setEdges, dispatch); } // 标记历史记录已初始化 setHistoryInitialized(true); - }, [initialData, canvasDataMap, getCurrentAppKey]); + }, [initialData, canvasDataMap, getCurrentFlowAppKey]); // 实时更新 canvasDataMap const updateCanvasDataMapEffect = useCallback(() => { - const appKey = getCurrentAppKey(); + const appKey = getCurrentFlowAppKey(); if (appKey) { - updateCanvasDataMapDebounced(dispatch, canvasDataMap, appKey, nodes, edges); + updateCanvasDataMapDebounced( + dispatch, + canvasDataMap, + appKey, + nodes, + edges + ); } // 清理函数,在组件卸载时取消防抖 return () => { // 取消防抖函数 }; - }, [nodes, edges, dispatch, canvasDataMap, getCurrentAppKey]); + }, [nodes, edges, dispatch, canvasDataMap, getCurrentFlowAppKey]); // 关闭编辑弹窗 const closeEditModal = useCallback(() => { setIsEditModalOpen(false); setEditingNode(null); }, []); // 保存节点编辑 - const saveNodeEdit = useCallback((updatedData: any) => { - console.log('updatedData:', updatedData); - const updatedNodes = nodes.map((node) => { - if (node.id === editingNode?.id) { - return { - ...node, - data: { ...node.data, ...updatedData } - }; - } - return node; - }); + const saveNodeEdit = useCallback( + (updatedData: any) => { + console.log('updatedData:', updatedData); + const updatedNodes = nodes.map((node) => { + if (node.id === editingNode?.id) { + return { + ...node, + data: { ...node.data, ...updatedData }, + }; + } + return node; + }); - setNodes(updatedNodes); - closeEditModal(); - }, [nodes, editingNode, closeEditModal]); + setNodes(updatedNodes); + closeEditModal(); + }, + [nodes, editingNode, closeEditModal] + ); // 编辑节点 const editNode = useCallback((node: Node) => { setEditingNode(node); @@ -521,740 +587,552 @@ export const useFlowCallbacks = ( console.log('编辑边:', edge); }, []); // 复制节点(支持多节点和多边) - const copyNode = useCallback((node: Node) => { - // 获取所有选中的节点(包括当前节点) - const selectedNodes = nodes.filter(n => n.selected || n.id === node.id); + const copyNode = useCallback( + (node: Node) => { + // 获取所有选中的节点(包括当前节点) + const selectedNodes = nodes.filter((n) => n.selected || n.id === node.id); + + // 过滤掉开始和结束节点 + const nodesToCopy = selectedNodes.filter( + (n) => n.type !== 'start' && n.type !== 'end' + ); + + if (nodesToCopy.length === 0) { + console.warn('没有可复制的节点(开始和结束节点不能复制)'); + return; + } - // 过滤掉开始和结束节点 - const nodesToCopy = selectedNodes.filter(n => n.type !== 'start' && n.type !== 'end'); + // 获取这些节点之间的边 + const nodeIds = new Set(nodesToCopy.map((n) => n.id)); + const edgesToCopy = edges.filter( + (e) => nodeIds.has(e.source) && nodeIds.has(e.target) + ); + + // 清除运行时状态 + const cleanedNodes = nodesToCopy.map((n) => ({ + ...n, + selected: false, + dragging: false, + })); - if (nodesToCopy.length === 0) { - console.warn('没有可复制的节点(开始和结束节点不能复制)'); - return; - } + const cleanedEdges = edgesToCopy.map((e) => ({ + ...e, + selected: false, + })); - // 获取这些节点之间的边 - const nodeIds = new Set(nodesToCopy.map(n => n.id)); - const edgesToCopy = edges.filter(e => - nodeIds.has(e.source) && nodeIds.has(e.target) - ); - - // 清除运行时状态 - const cleanedNodes = nodesToCopy.map(n => ({ - ...n, - selected: false, - dragging: false - })); - - const cleanedEdges = edgesToCopy.map(e => ({ - ...e, - selected: false - })); - - // 存储复制的数据 - const copiedData = { - nodes: cleanedNodes, - edges: cleanedEdges, - appId: initialData?.appId - }; + // 存储复制的数据 + const copiedData = { + nodes: cleanedNodes, + edges: cleanedEdges, + appId: initialData?.appId, + }; - localStorage.setItem('copiedFlowData', JSON.stringify(copiedData)); - console.log(`已复制 ${nodesToCopy.length} 个节点和 ${edgesToCopy.length} 条边`); - }, [nodes, edges, initialData?.appId]); + localStorage.setItem('copiedFlowData', JSON.stringify(copiedData)); + console.log( + `已复制 ${nodesToCopy.length} 个节点和 ${edgesToCopy.length} 条边` + ); + }, + [nodes, edges, initialData?.appId] + ); // 粘贴节点(支持多节点和多边) - const pasteNode = useCallback((position: { x: number; y: number }) => { - // 尝试读取新格式的复制数据(多节点+边) - const copiedFlowDataStr = localStorage.getItem('copiedFlowData'); + const pasteNode = useCallback( + (position: { x: number; y: number }) => { + // 尝试读取新格式的复制数据(多节点+边) + const copiedFlowDataStr = localStorage.getItem('copiedFlowData'); - // 兼容旧格式的单节点复制 - const copiedNodeStr = localStorage.getItem('copiedNode'); + // 兼容旧格式的单节点复制 + const copiedNodeStr = localStorage.getItem('copiedNode'); - if (!copiedFlowDataStr && !copiedNodeStr) { - console.warn('没有找到复制的节点数据'); - return; - } + if (!copiedFlowDataStr && !copiedNodeStr) { + console.warn('没有找到复制的节点数据'); + return; + } - try { - // 处理新格式(多节点+边) - if (copiedFlowDataStr) { - const copiedData = JSON.parse(copiedFlowDataStr); + try { + // 处理新格式(多节点+边) + if (copiedFlowDataStr) { + const copiedData = JSON.parse(copiedFlowDataStr); + + // 检查是否为同一应用 + if ( + copiedData.appId && + initialData?.appId && + copiedData.appId !== initialData.appId + ) { + console.warn('不能在不同应用之间粘贴节点'); + return; + } - // 检查是否为同一应用 - if (copiedData.appId && initialData?.appId && copiedData.appId !== initialData.appId) { - console.warn('不能在不同应用之间粘贴节点'); - return; - } + const { nodes: copiedNodes, edges: copiedEdges } = copiedData; - const { nodes: copiedNodes, edges: copiedEdges } = copiedData; + if (!copiedNodes || copiedNodes.length === 0) { + console.warn('没有可粘贴的节点'); + return; + } - if (!copiedNodes || copiedNodes.length === 0) { - console.warn('没有可粘贴的节点'); - return; - } + // 计算所有节点的边界框中心点 + const minX = Math.min(...copiedNodes.map((n: Node) => n.position.x)); + const minY = Math.min(...copiedNodes.map((n: Node) => n.position.y)); + const maxX = Math.max(...copiedNodes.map((n: Node) => n.position.x)); + const maxY = Math.max(...copiedNodes.map((n: Node) => n.position.y)); + const centerX = (minX + maxX) / 2; + const centerY = (minY + maxY) / 2; + + // 计算偏移量,使粘贴的节点组以鼠标位置为中心 + const offsetX = position.x - centerX; + const offsetY = position.y - centerY; + + // 创建旧ID到新ID的映射 + const idMap = new Map(); + const timestamp = Date.now(); + + // 创建新节点 + const newNodes = copiedNodes.map((node: Node, index: number) => { + const newId = `${node.type}-${timestamp}-${index}`; + idMap.set(node.id, newId); + + const newNode = { + ...node, + id: newId, + position: { + x: node.position.x + offsetX, + y: node.position.y + offsetY, + }, + selected: false, + dragging: false, + appId: undefined, + }; + + // 注册节点类型 + ensureNodeTypeRegistered( + newNode.type, + (newNode.data?.title as string) || newNode.type, + resolveNodeComponent(newNode.type) + ); + + return newNode; + }); - // 计算所有节点的边界框中心点 - const minX = Math.min(...copiedNodes.map((n: Node) => n.position.x)); - const minY = Math.min(...copiedNodes.map((n: Node) => n.position.y)); - const maxX = Math.max(...copiedNodes.map((n: Node) => n.position.x)); - const maxY = Math.max(...copiedNodes.map((n: Node) => n.position.y)); - const centerX = (minX + maxX) / 2; - const centerY = (minY + maxY) / 2; + // 创建新边,使用新的节点ID + const newEdges = copiedEdges + .map((edge: Edge, index: number) => { + const newSourceId = idMap.get(edge.source); + const newTargetId = idMap.get(edge.target); + + if (!newSourceId || !newTargetId) { + console.warn('边的源节点或目标节点未找到:', edge); + return null; + } + + return { + ...edge, + id: `e${newSourceId}-${newTargetId}-${timestamp}-${index}`, + source: newSourceId, + target: newTargetId, + selected: false, + }; + }) + .filter(Boolean); // 过滤掉null值 + + // 更新节点和边 + setNodes((nds: Node[]) => { + const updatedNodes = [...nds, ...newNodes]; - // 计算偏移量,使粘贴的节点组以鼠标位置为中心 - const offsetX = position.x - centerX; - const offsetY = position.y - centerY; + // 添加节点后记录历史 + dispatchFlowSnapshotAsync({ + nodes: [...updatedNodes], + edges: [...edges, ...newEdges], + }); - // 创建旧ID到新ID的映射 - const idMap = new Map(); - const timestamp = Date.now(); + return updatedNodes; + }); - // 创建新节点 - const newNodes = copiedNodes.map((node: Node, index: number) => { - const newId = `${node.type}-${timestamp}-${index}`; - idMap.set(node.id, newId); + setEdges((eds: Edge[]) => { + const updatedEdges = [...eds, ...newEdges]; + return updatedEdges; + }); + console.log( + `已粘贴 ${newNodes.length} 个节点和 ${newEdges.length} 条边` + ); + } + // 处理旧格式(单节点)- 保持向后兼容 + else if (copiedNodeStr) { + const copiedNode = JSON.parse(copiedNodeStr); + + // 检查是否为同一应用 + if ( + copiedNode.appId && + initialData?.appId && + copiedNode.appId !== initialData.appId + ) { + console.warn('不能在不同应用之间粘贴节点'); + return; + } + + // 创建新节点,更新ID和位置 const newNode = { - ...node, - id: newId, - position: { - x: node.position.x + offsetX, - y: node.position.y + offsetY - }, + ...copiedNode, + id: `${copiedNode.type}-${Date.now()}`, + position, selected: false, dragging: false, - appId: undefined + appId: undefined, }; - // 注册节点类型 - const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); - if (!nodeMap.includes(newNode.type)) { - registerNodeType(newNode.type, getNodeComponent(newNode.type), newNode.data?.title as string || newNode.type); - } - - return newNode; - }); - - // 创建新边,使用新的节点ID - const newEdges = copiedEdges.map((edge: Edge, index: number) => { - const newSourceId = idMap.get(edge.source); - const newTargetId = idMap.get(edge.target); - - if (!newSourceId || !newTargetId) { - console.warn('边的源节点或目标节点未找到:', edge); - return null; - } + // 特殊处理循环节点 + if (copiedNode.type === 'LOOP') { + setNodes((nds: Node[]) => { + const newNodes = [...nds, newNode]; - return { - ...edge, - id: `e${newSourceId}-${newTargetId}-${timestamp}-${index}`, - source: newSourceId, - target: newTargetId, - selected: false - }; - }).filter(Boolean); // 过滤掉null值 - - // 更新节点和边 - setNodes((nds: Node[]) => { - const updatedNodes = [...nds, ...newNodes]; + dispatchFlowSnapshotAsync({ + nodes: [...newNodes], + edges: [...edges], + }); - // 添加节点后记录历史 - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { nodes: [...updatedNodes], edges: [...edges, ...newEdges] } + return newNodes; }); - document.dispatchEvent(event); - }, 0); - - return updatedNodes; - }); - - setEdges((eds: Edge[]) => { - const updatedEdges = [...eds, ...newEdges]; - return updatedEdges; - }); - - console.log(`已粘贴 ${newNodes.length} 个节点和 ${newEdges.length} 条边`); - } - // 处理旧格式(单节点)- 保持向后兼容 - else if (copiedNodeStr) { - const copiedNode = JSON.parse(copiedNodeStr); - - // 检查是否为同一应用 - if (copiedNode.appId && initialData?.appId && copiedNode.appId !== initialData.appId) { - console.warn('不能在不同应用之间粘贴节点'); - return; - } + return; + } - // 创建新节点,更新ID和位置 - const newNode = { - ...copiedNode, - id: `${copiedNode.type}-${Date.now()}`, - position, - selected: false, - dragging: false, - appId: undefined - }; + // 注册节点类型 + ensureNodeTypeRegistered( + newNode.type, + newNode.data?.title || newNode.type, + resolveNodeComponent(newNode.type) + ); - // 特殊处理循环节点 - if (copiedNode.type === 'LOOP') { setNodes((nds: Node[]) => { const newNodes = [...nds, newNode]; - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { nodes: [...newNodes], edges: [...edges] } - }); - document.dispatchEvent(event); - }, 0); + dispatchFlowSnapshotAsync({ + nodes: [...newNodes], + edges: [...edges], + }); return newNodes; }); - return; - } - - // 注册节点类型 - const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); - if (!nodeMap.includes(newNode.type)) { - registerNodeType(newNode.type, getNodeComponent(newNode.type), newNode.data?.title || newNode.type); } - - setNodes((nds: Node[]) => { - const newNodes = [...nds, newNode]; - - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { nodes: [...newNodes], edges: [...edges] } - }); - document.dispatchEvent(event); - }, 0); - - return newNodes; - }); + } catch (error) { + console.error('粘贴节点时出错:', error); } - } catch (error) { - console.error('粘贴节点时出错:', error); - } - }, [edges, initialData?.appId, nodes]); + }, + [edges, initialData?.appId, nodes] + ); // endregion // region 节点/边操作 // 删除节点函数 - const deleteNode = useCallback((node: Node) => { - // 在应用编排模式下(useDefault为false)不允许删除节点 - if (!useDefault) { - console.warn('在应用编排模式下不允许删除节点'); - return; - } - - // 开始和结束节点不允许删除 - if (node.type === 'start' || node.type === 'end') { - console.warn('开始和结束节点不允许删除'); - return; - } - - // 处理循环节点删除逻辑 - if (node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END') { - // 获取关联的另一个循环节点 - let relatedNodeId = null; - - // 类型断言,将component从unknown转换为具有customDef属性的对象 - const component = node.data?.component as { customDef?: string } | undefined; - - if (node.data?.type === 'LOOP_START' && component?.customDef) { - try { - const customDef = JSON.parse(component.customDef); - relatedNodeId = customDef.loopEndNodeId; - } catch (e) { - console.error('解析循环开始节点数据失败:', e); - } + const deleteNode = useCallback( + (node: Node) => { + // 在应用编排模式下(useDefault为false)不允许删除节点 + if (!useDefault) { + console.warn('在应用编排模式下不允许删除节点'); + return; } - else if (node.data?.type === 'LOOP_END' && component?.customDef) { - try { - const customDef = JSON.parse(component.customDef); - relatedNodeId = customDef.loopStartNodeId; - } catch (e) { - console.error('解析循环结束节点数据失败:', e); - } + + // 开始和结束节点不允许删除 + if (node.type === 'start' || node.type === 'end') { + console.warn('开始和结束节点不允许删除'); + return; } - // 删除两个节点及相关边 - setNodes((nds: Node[]) => { - const updatedNodes = nds.filter((n) => - n.id !== node.id && n.id !== relatedNodeId - ); - return updatedNodes; - }); + // 处理循环节点删除逻辑 + if (node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END') { + // 获取关联的另一个循环节点 + let relatedNodeId = null; + + // 类型断言,将component从unknown转换为具有customDef属性的对象 + const component = node.data?.component as + | { customDef?: string } + | undefined; + + if (node.data?.type === 'LOOP_START' && component?.customDef) { + try { + const customDef = JSON.parse(component.customDef); + relatedNodeId = customDef.loopEndNodeId; + } catch (e) { + console.error('解析循环开始节点数据失败:', e); + } + } else if (node.data?.type === 'LOOP_END' && component?.customDef) { + try { + const customDef = JSON.parse(component.customDef); + relatedNodeId = customDef.loopStartNodeId; + } catch (e) { + console.error('解析循环结束节点数据失败:', e); + } + } - setEdges((eds: Edge[]) => { - const updatedEdges = eds.filter((e) => - e.source !== node.id && - e.target !== node.id && - e.source !== relatedNodeId && - e.target !== relatedNodeId - ); - return updatedEdges; - }); + // 删除两个节点及相关边 + setNodes((nds: Node[]) => { + const updatedNodes = nds.filter( + (n) => n.id !== node.id && n.id !== relatedNodeId + ); + return updatedNodes; + }); - // 删除节点后记录历史 - setTimeout(() => { - const updatedNodes = nodes.filter((n) => - n.id !== node.id && n.id !== relatedNodeId - ); - const updatedEdges = edges.filter((e) => - e.source !== node.id && - e.target !== node.id && - e.source !== relatedNodeId && - e.target !== relatedNodeId - ); + setEdges((eds: Edge[]) => { + const updatedEdges = eds.filter( + (e) => + e.source !== node.id && + e.target !== node.id && + e.source !== relatedNodeId && + e.target !== relatedNodeId + ); + return updatedEdges; + }); - const event = new CustomEvent('takeSnapshot', { - detail: { + // 删除节点后记录历史 + setTimeout(() => { + const updatedNodes = nodes.filter( + (n) => n.id !== node.id && n.id !== relatedNodeId + ); + const updatedEdges = edges.filter( + (e) => + e.source !== node.id && + e.target !== node.id && + e.source !== relatedNodeId && + e.target !== relatedNodeId + ); + + dispatchFlowSnapshot({ nodes: [...updatedNodes], - edges: [...updatedEdges] - } - }); - document.dispatchEvent(event); - }, 0); + edges: [...updatedEdges], + }); + }, 0); - return; - } + return; + } - // 普通节点删除逻辑 - setNodes((nds: Node[]) => nds.filter((n) => n.id !== node.id)); - setEdges((eds: Edge[]) => eds.filter((e) => e.source !== node.id && e.target !== node.id)); + // 普通节点删除逻辑 + setNodes((nds: Node[]) => nds.filter((n) => n.id !== node.id)); + setEdges((eds: Edge[]) => + eds.filter((e) => e.source !== node.id && e.target !== node.id) + ); - // 删除节点后记录历史 - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { + // 删除节点后记录历史 + setTimeout(() => { + dispatchFlowSnapshot({ nodes: [...nodes.filter((n) => n.id !== node.id)], - edges: [...edges.filter((e) => e.source !== node.id && e.target !== node.id)] - } - }); - document.dispatchEvent(event); - }, 0); - }, [nodes, edges, useDefault]); + edges: [ + ...edges.filter( + (e) => e.source !== node.id && e.target !== node.id + ), + ], + }); + }, 0); + }, + [nodes, edges, useDefault] + ); // 删除边函数 - const deleteEdge = useCallback((edge: Edge) => { - // 获取当前应用的运行状态 - const { appRuntimeData, currentAppData } = store.getState().ideContainer; - const currentAppIsRunning = currentAppData?.id && appRuntimeData[currentAppData.id] - ? appRuntimeData[currentAppData.id].isRunning - : false; - - // 在运行时禁止删除边 - if (currentAppIsRunning) { - console.warn('在运行时不允许删除边'); - return; - } + const deleteEdge = useCallback( + (edge: Edge) => { + // 获取当前应用的运行状态 + const { appRuntimeData, currentAppData } = store.getState().ideContainer; + const currentAppIsRunning = + currentAppData?.id && appRuntimeData[currentAppData.id] + ? appRuntimeData[currentAppData.id].isRunning + : false; + + // 在运行时禁止删除边 + if (currentAppIsRunning) { + console.warn('在运行时不允许删除边'); + return; + } - setEdges((eds: Edge[]) => eds.filter((e) => e.id !== edge.id)); + setEdges((eds: Edge[]) => eds.filter((e) => e.id !== edge.id)); - // 删除边后记录历史 - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { + // 删除边后记录历史 + setTimeout(() => { + dispatchFlowSnapshot({ nodes: [...nodes], - edges: [...edges.filter((e) => e.id !== edge.id)] - } - }); - document.dispatchEvent(event); - }, 0); - }, [nodes, edges]); - // 在边上添加节点的具体实现 - const addNodeOnEdge = useCallback((nodeType: string, node: any) => { - const { currentAppData, flowData } = store.getState().ideContainer; - if (!edgeForNodeAdd || !reactFlowInstance) return; - - // 查找节点定义 - const nodeDefinition = localNodeData.find(n => n.nodeType === nodeType) || node; - if (!nodeDefinition) return; - - // 特殊处理循环节点,添加开始和结束节点 - if (nodeType === 'LOOP') { - // 获取源节点和目标节点的位置 - const sourceNode = nodes.find(n => n.id === edgeForNodeAdd.source); - const targetNode = nodes.find(n => n.id === edgeForNodeAdd.target); + edges: [...edges.filter((e) => e.id !== edge.id)], + }); + }, 0); + }, + [nodes, edges] + ); - if (!sourceNode || !targetNode) return; + const getEdgeMidpointPosition = useCallback( + (sourceId: string, targetId: string) => { + const sourceNode = nodes.find((n) => n.id === sourceId); + const targetNode = nodes.find((n) => n.id === targetId); + if (!sourceNode || !targetNode) return null; - // 计算中点位置 - const position = { + return { x: (sourceNode.position.x + targetNode.position.x) / 2, - y: (sourceNode.position.y + targetNode.position.y) / 2 + y: (sourceNode.position.y + targetNode.position.y) / 2, }; + }, + [nodes] + ); - // 创建循环开始和结束节点 - const loopStartNode = { - id: `LOOP_START-${Date.now()}`, - type: 'LOOP', - position: { x: position.x, y: position.y }, - data: { - title: '循环开始', - type: 'LOOP_START', - parameters: { - apiIns: [{ name: 'start', desc: '', dataType: '', defaultValue: '' }], - apiOuts: [{ name: 'done', desc: '', dataType: '', defaultValue: '' }], - dataIns: [], - dataOuts: [] - }, - component: {} - } - }; + // 在边上添加节点的具体实现 + const addNodeOnEdge = useCallback( + (nodeType: string, node: any) => { + if (!edgeForNodeAdd || !reactFlowInstance) return; + + // 查找节点定义 + const nodeDefinition = getNodeDefinition(nodeType, node); + if (!nodeDefinition) return; + + // 特殊处理循环节点,添加开始和结束节点 + if (nodeType === 'LOOP') { + const position = getEdgeMidpointPosition( + edgeForNodeAdd.source, + edgeForNodeAdd.target + ); + if (!position) return; - const loopEndNode = { - id: `LOOP_END-${Date.now()}`, - type: 'LOOP', - position: { x: position.x + 400, y: position.y }, - data: { - title: '循环结束', - type: 'LOOP_END', - parameters: { - apiIns: [{ name: 'continue', desc: '', dataType: '', defaultValue: '' }], - apiOuts: [{ name: 'break', desc: '', dataType: '', defaultValue: '' }], - dataIns: [{ - 'arrayType': null, - 'dataType': 'INTEGER', - 'defaultValue': 10, - 'desc': '最大循环次数', - 'id': 'maxTime' - }], - dataOuts: [] - }, - component: { - type: 'LOOP_END', - customDef: JSON.stringify({ - apiOutIds: ['continue', 'break'], - conditions: [], - loopStartNodeId: loopStartNode.id - }), - loopStartNodeId: loopStartNode.id - } - } - }; + const { loopStartNode, loopEndNode } = createLoopNodePair(position); - loopStartNode.data.component = { - type: 'LOOP_START', - customDef: JSON.stringify({ loopEndNodeId: loopEndNode.id }) - }; + // 创建连接边(连接循环开始和结束节点的顶部连接点) + const groupEdge = createLoopGroupEdge(loopStartNode.id, loopEndNode.id); - // 创建连接边(连接循环开始和结束节点的顶部连接点) - const groupEdge = { - id: `${loopStartNode.id}-${loopEndNode.id}-group`, - source: loopStartNode.id, - target: loopEndNode.id, - sourceHandle: `${loopStartNode.id}-group`, - targetHandle: `${loopEndNode.id}-group`, - type: 'custom' - }; - - // 创建连接原有节点到循环开始节点,以及循环结束节点到目标节点的边 - const connectionEdges = [ - { - id: `e${edgeForNodeAdd.source}-${loopStartNode.id}`, - source: edgeForNodeAdd.source, - target: loopStartNode.id, + // 创建连接原有节点到循环开始节点,以及循环结束节点到目标节点的边 + const connectionEdges = createLoopInsertConnectionEdges({ + sourceId: edgeForNodeAdd.source, sourceHandle: edgeForNodeAdd.sourceHandle, - targetHandle: 'start', // 循环开始节点的输入句柄 - type: 'custom', - lineType: 'api', - data: { - lineType: 'api' - } - }, - { - id: `e${loopEndNode.id}-${edgeForNodeAdd.target}`, - source: loopEndNode.id, - target: edgeForNodeAdd.target, - sourceHandle: 'break', // 循环结束节点的输出句柄 + targetId: edgeForNodeAdd.target, targetHandle: edgeForNodeAdd.targetHandle, - type: 'custom', - lineType: 'api', - data: { - lineType: 'api' - } - } - ]; + loopStartId: loopStartNode.id, + loopEndId: loopEndNode.id, + }); - // 将未定义的节点动态追加进nodeTypes - const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); - if (!nodeMap.includes('LOOP')) { - registerNodeType('LOOP', LoopNode, '循环'); - } + // 将未定义的节点动态追加进nodeTypes + ensureNodeTypeRegistered('LOOP', '循环', LoopNode); - // 更新节点和边 - setNodes((nds: Node[]) => { - const newNodes = [...nds, loopStartNode, loopEndNode]; + // 更新节点和边 + setNodes((nds: Node[]) => { + const newNodes = [...nds, loopStartNode, loopEndNode]; - // 添加节点后记录历史 - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { nodes: [...newNodes], edges: [...edges, groupEdge, ...connectionEdges] } + // 添加节点后记录历史 + dispatchFlowSnapshotAsync({ + nodes: [...newNodes], + edges: [...edges, groupEdge, ...connectionEdges], }); - document.dispatchEvent(event); - }, 0); - - return newNodes; - }); - setEdges((eds: Edge[]) => { - // 删除原来的边,添加新的边 - const updatedEdges = [...eds.filter(e => e.id !== edgeForNodeAdd.id), groupEdge, ...connectionEdges]; + return newNodes; + }); - // 添加边后记录历史 - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { - nodes: [...nodes, loopStartNode, loopEndNode], - edges: [...updatedEdges] - } + setEdges((eds: Edge[]) => { + // 删除原来的边,添加新的边 + const updatedEdges = [ + ...eds.filter((e) => e.id !== edgeForNodeAdd.id), + groupEdge, + ...connectionEdges, + ]; + + // 添加边后记录历史 + dispatchFlowSnapshotAsync({ + nodes: [...nodes, loopStartNode, loopEndNode], + edges: [...updatedEdges], }); - document.dispatchEvent(event); - }, 0); - return updatedEdges; - }); - - // 关闭菜单 - setEdgeForNodeAdd(null); - setPositionForNodeAdd(null); - return; - } - - // 获取源节点和目标节点 - const sourceNode = nodes.find(n => n.id === edgeForNodeAdd.source); - const targetNode = nodes.find(n => n.id === edgeForNodeAdd.target); - - if (!sourceNode || !targetNode) return; - - // 计算中点位置 - const position = { - x: (sourceNode.position.x + targetNode.position.x) / 2, - y: (sourceNode.position.y + targetNode.position.y) / 2 - }; + return updatedEdges; + }); - // 创建新节点 - const newNode = { - id: `${nodeType}-${Date.now()}`, - type: nodeType, - position, - data: { - ...nodeDefinition.data, - title: nodeDefinition.nodeName, - type: nodeType + // 关闭菜单 + clearAddNodeContext(); + return; } - }; - // 有组件ID的节点就追加组件id数据 - if (nodeDefinition.id || nodeDefinition?.flowHousVO?.id) newNode.data.compId = nodeDefinition.id || nodeDefinition?.flowHousVO?.id; + const position = getEdgeMidpointPosition( + edgeForNodeAdd.source, + edgeForNodeAdd.target + ); + if (!position) return; - if (nodeType === 'SWITCH') { - newNode.data.component = { - customDef: JSON.stringify({ - apiOutIds: ['default'], - conditions: [] - }) - }; - } - else if (nodeType === 'SUB') { - newNode.data.component = { - type: nodeType, - compId: newNode.data.compId - }; - } - else if (nodeType === 'EVENTSEND' || nodeType === 'EVENTLISTENE') { const { eventList } = store.getState().ideContainer; - const emptyEvent = eventList.find(item => item.topic.includes('**empty**')); - newNode.data.component = { - type: nodeType, - customDef: { eventId: emptyEvent.id, name: emptyEvent.name, topic: emptyEvent.topic } - }; - } - else { - newNode.data.component = { - type: nodeType, - compId: nodeDefinition.id - }; - } - - // 将未定义的节点动态追加进nodeTypes - const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); - if (!nodeMap.includes(nodeType)) { - registerNodeType(nodeType, getNodeComponent(nodeType), nodeDefinition.nodeName); - } - - // 添加新节点 - setNodes((nds: Node[]) => [...nds, newNode]); - - // 删除旧边 - setEdges((eds: Edge[]) => eds.filter(e => e.id !== edgeForNodeAdd.id)); - - // 确定新边的句柄 - // 对于第一条边 (source -> new node): 使用原始边的 sourceHandle,目标句柄根据节点类型确定 - // 对于第二条边 (new node -> target): 源句柄根据节点类型确定,使用原始边的 targetHandle - - // 获取新节点的默认句柄 - let newNodeSourceHandle = 'done'; // 默认源句柄 - let newNodeTargetHandle = 'start'; // 默认目标句柄 + const newNode = buildRuntimeNode({ + nodeType, + nodeDefinition, + position, + eventList, + eventIdMode: 'id', + }); - // 如果新节点有参数定义,尝试获取更准确的句柄信息 - if (newNode.data?.parameters) { - const { apiOuts, apiIns } = newNode.data.parameters; + // 添加新节点 + setNodes((nds: Node[]) => [...nds, newNode]); - // 获取第一个api输出作为源句柄(如果存在) - if (apiOuts && apiOuts.length > 0) { - newNodeSourceHandle = apiOuts[0].name || apiOuts[0].id || newNodeSourceHandle; - } + // 删除旧边 + setEdges((eds: Edge[]) => eds.filter((e) => e.id !== edgeForNodeAdd.id)); - // 获取第一个api输入作为目标句柄(如果存在) - if (apiIns && apiIns.length > 0) { - newNodeTargetHandle = apiIns[0].name || apiIns[0].id || newNodeTargetHandle; - } - } + const { sourceHandle, targetHandle } = + resolveInsertedNodeHandles(newNode); - // 创建新边: source -> new node, new node -> target - const newEdges = [ - ...edges.filter(e => e.id !== edgeForNodeAdd.id), - { - id: `e${edgeForNodeAdd.source}-${newNode.id}`, - source: edgeForNodeAdd.source, - target: newNode.id, + const newEdges = buildInsertedNodeEdges({ + baseEdges: edges, + removedEdgeId: edgeForNodeAdd.id, + sourceId: edgeForNodeAdd.source, sourceHandle: edgeForNodeAdd.sourceHandle, - targetHandle: newNodeTargetHandle, - type: 'custom', - lineType: 'api', - data: { - lineType: 'api' - } - }, - { - id: `e${newNode.id}-${edgeForNodeAdd.target}`, - source: newNode.id, - target: edgeForNodeAdd.target, - sourceHandle: newNodeSourceHandle, + targetId: edgeForNodeAdd.target, targetHandle: edgeForNodeAdd.targetHandle, - type: 'custom', - lineType: 'api', - data: { - lineType: 'api' - } - } - ]; - - setEdges(newEdges); - - // 关闭菜单 - setEdgeForNodeAdd(null); - setPositionForNodeAdd(null); - - // 添加节点后记录历史 - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { - nodes: [...nodes, newNode], - edges: [...newEdges] - } + insertedNodeId: newNode.id, + insertedNodeSourceHandle: sourceHandle, + insertedNodeTargetHandle: targetHandle, }); - document.dispatchEvent(event); - }, 0); - }, [edgeForNodeAdd, nodes, reactFlowInstance, edges, addLoopNodeWithStartEnd]); - // 在画布上添加节点 - const addNodeOnPane = useCallback((nodeType: string, position: { x: number; y: number }, node?: any) => { - const { currentAppData, flowData } = store.getState().ideContainer; - if (!reactFlowInstance) return; - - // 查找节点定义 - const nodeDefinition = localNodeData.find(n => n.nodeType === nodeType) || node; - if (!nodeDefinition) return; - - // 特殊处理循环节点,添加开始和结束节点 - if (nodeType === 'LOOP') { - addLoopNodeWithStartEnd(position, nodeDefinition); - return; - } - - // 创建新节点 - const newNode = { - id: `${nodeType}-${Date.now()}`, - type: nodeType, - position, - data: { - ...nodeDefinition.data, - title: nodeDefinition.nodeName, - type: nodeType - } - }; - if (nodeDefinition.id || nodeDefinition?.flowHousVO?.id) newNode.data.compId = nodeDefinition.id || nodeDefinition?.flowHousVO?.id; + setEdges(newEdges); - if (nodeType === 'SWITCH') { - newNode.data.component = { - customDef: JSON.stringify({ - apiOutIds: ['default'], - conditions: [] - }) - }; - } - else if (nodeType === 'SUB') { - newNode.data.component = { - type: nodeType, - compId: newNode.data.compId - }; - } - else if (nodeType === 'EVENTSEND' || nodeType === 'EVENTLISTENE') { - const { eventList } = store.getState().ideContainer; - const emptyEvent = eventList.find(item => item.topic.includes('**empty**')); - newNode.data.component = { - type: nodeType, - customDef: { eventId: emptyEvent?.eventId ?? null, name: emptyEvent.name, topic: emptyEvent.topic } - }; - } - else { - newNode.data.component = { - type: nodeType, - compId: nodeDefinition.id - }; - } - // 将未定义的节点动态追加进nodeTypes - const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); - // 目前默认添加的都是系统组件/本地组件 - if (!nodeMap.includes(nodeType)) { - registerNodeType(nodeType, getNodeComponent(nodeType), nodeDefinition.nodeName); - } - - setNodes((nds: Node[]) => { - const newNodes = [...nds, newNode]; + // 关闭菜单 + clearAddNodeContext(); // 添加节点后记录历史 - setTimeout(() => { - const event = new CustomEvent('takeSnapshot', { - detail: { nodes: [...newNodes], edges: [...edges] } - }); - document.dispatchEvent(event); - }, 0); - - return newNodes; - }); - }, [reactFlowInstance, edges, addLoopNodeWithStartEnd]); + dispatchFlowSnapshotAsync({ + nodes: [...nodes, newNode], + edges: [...newEdges], + }); + }, + [ + edgeForNodeAdd, + reactFlowInstance, + edges, + addLoopNodeWithStartEnd, + getEdgeMidpointPosition, + nodes, + clearAddNodeContext, + ] + ); + // 在画布上添加节点 + const addNodeOnPane = useCallback( + (nodeType: string, position: { x: number; y: number }, node?: any) => { + if (!reactFlowInstance) return; + + // 查找节点定义 + const nodeDefinition = getNodeDefinition(nodeType, node); + if (!nodeDefinition) return; + + addNodeOnCanvas(nodeType, nodeDefinition, position); + }, + [reactFlowInstance, getNodeDefinition, addNodeOnCanvas] + ); // 处理添加节点的统一方法 - const handleAddNode = useCallback((nodeType: string, node: any) => { - // 如果是通过边添加节点 - if (edgeForNodeAdd) { - addNodeOnEdge(nodeType, node); - } - // 如果是通过画布添加节点 - else if (positionForNodeAdd) { - addNodeOnPane(nodeType, positionForNodeAdd, node); - } + const handleAddNode = useCallback( + (nodeType: string, node: any) => { + // 如果是通过边添加节点 + if (edgeForNodeAdd) { + addNodeOnEdge(nodeType, node); + } + // 如果是通过画布添加节点 + else if (positionForNodeAdd) { + addNodeOnPane(nodeType, positionForNodeAdd, node); + } - // 清除状态 - setEdgeForNodeAdd(null); - setPositionForNodeAdd(null); - }, [edgeForNodeAdd, positionForNodeAdd, addNodeOnEdge, addNodeOnPane]); + // 清除状态 + clearAddNodeContext(); + }, + [ + edgeForNodeAdd, + positionForNodeAdd, + addNodeOnEdge, + addNodeOnPane, + clearAddNodeContext, + ] + ); // endregion - const saveFlowDataToServer = useCallback(async () => { if (useDefault) { try { @@ -1274,21 +1152,33 @@ export const useFlowCallbacks = ( // 转换会原始数据类型 const revertedData = revertFlowData(nodes, edges); const upDatePublishCB = await upDatePublish(revertedData.nodeConfigs); - const newRevertedData = reverseConvertFlowData(nodes, edges, upDatePublishCB); - const { flowData, currentAppData, info } = store.getState().ideContainer; - const { deleteEventSendNodeList, deleteEventlisteneList } = handelEventNodeList(newRevertedData); + const newRevertedData = reverseConvertFlowData( + nodes, + edges, + upDatePublishCB + ); + const { flowData, currentAppData, info } = + store.getState().ideContainer; + const { deleteEventSendNodeList, deleteEventlisteneList } = + handelEventNodeList(newRevertedData); let params = {}; // 更新复合组件/子流程 if (currentAppData.key.includes('sub')) { - const appEventDefinition = updateEvent(revertedData.nodeConfigs, initialData.appId); + const appEventDefinition = updateEvent( + revertedData.nodeConfigs, + initialData.appId + ); params = { - ...currentAppData?.compData || {}, + ...(currentAppData?.compData || {}), components: newRevertedData, appEventDefinition, - sceneId: info.id + sceneId: info.id, }; - const res: any = await setSubFlowNew(params, currentAppData.parentAppId); + const res: any = await setSubFlowNew( + params, + currentAppData.parentAppId + ); if (res.code === 200) { Message.success('保存成功'); // 更新事件枚举表 @@ -1297,33 +1187,45 @@ export const useFlowCallbacks = ( const appRes: any = await getAppInfoNew(currentAppData.parentAppId); // 更新 flowData 中的数据 - dispatch(updateFlowData({ [currentAppData.parentAppId]: appRes.data })); + dispatch( + updateFlowData({ [currentAppData.parentAppId]: appRes.data }) + ); // 同步更新主流程到 canvasDataMap(使用父应用ID) if (appRes.data.main?.components) { - const { nodes: parentNodes, edges: parentEdges } = convertFlowData(appRes.data.main.components, true); - dispatch(updateCanvasDataMap({ - ...canvasDataMap, - [currentAppData.parentAppId]: { nodes: parentNodes, edges: parentEdges } - })); + const { nodes: parentNodes, edges: parentEdges } = + convertFlowData(appRes.data.main.components, true); + dispatch( + updateCanvasDataMap({ + ...canvasDataMap, + [currentAppData.parentAppId]: { + nodes: parentNodes, + edges: parentEdges, + }, + }) + ); } // 同步更新子流程到 canvasDataMap(使用子流程key) - dispatch(updateCanvasDataMap({ - ...canvasDataMap, - [currentAppData.key]: { nodes, edges } - })); - } - else { + dispatch( + updateCanvasDataMap({ + ...canvasDataMap, + [currentAppData.key]: { nodes, edges }, + }) + ); + } else { Message.error(res.message); } } // 更新主流程 else { - const appEventDefinition = updateEvent(revertedData.nodeConfigs, initialData.appId); + const appEventDefinition = updateEvent( + revertedData.nodeConfigs, + initialData.appId + ); params = { - ...flowData[currentAppData.id]?.main || {}, + ...(flowData[currentAppData.id]?.main || {}), components: newRevertedData, appEventDefinition, - sceneId: info.id + sceneId: info.id, }; const res: any = await setMainFlowNew(params, initialData.appId); @@ -1334,62 +1236,70 @@ export const useFlowCallbacks = ( if (res1.code === 200) dispatch(updateEventListOld(res1.data)); // 更新缓存数据 - dispatch(updateCanvasDataMap({ - ...canvasDataMap, - [currentAppData.id]: { nodes, edges } - })); + dispatch( + updateCanvasDataMap({ + ...canvasDataMap, + [currentAppData.id]: { nodes, edges }, + }) + ); const appRes: any = await getAppInfoNew(currentAppData.id); // 更新 flowData 中的数据 dispatch(updateFlowData({ [currentAppData.id]: appRes.data })); // 同步更新到 canvasDataMap if (appRes.data.main?.components) { - const { nodes, edges } = convertFlowData(appRes.data.main.components, true); + const { nodes, edges } = convertFlowData( + appRes.data.main.components, + true + ); setNodes(nodes); setEdges(edges); - dispatch(updateCanvasDataMap({ - ...canvasDataMap, - [currentAppData.id]: { nodes, edges } - })); + dispatch( + updateCanvasDataMap({ + ...canvasDataMap, + [currentAppData.id]: { nodes, edges }, + }) + ); } - } - else { + } else { Message.error(res.message); } } // 事件节点变动数据有长度就通知后端,主流程和子流程(复合节点)通用 - if (deleteEventSendNodeList.length > 0 || deleteEventlisteneList.length > 0) { - deleteEventSendNodeList.length > 0 && deleteEventPub({ - appId: currentAppData.id, - topics: deleteEventSendNodeList - }); - deleteEventlisteneList.length > 0 && deleteEventSub({ - appId: currentAppData.id, - topics: deleteEventlisteneList - }); + if ( + deleteEventSendNodeList.length > 0 || + deleteEventlisteneList.length > 0 + ) { + deleteEventSendNodeList.length > 0 && + deleteEventPub({ + appId: currentAppData.id, + topics: deleteEventSendNodeList, + }); + deleteEventlisteneList.length > 0 && + deleteEventSub({ + appId: currentAppData.id, + topics: deleteEventlisteneList, + }); } } catch (error) { console.error('Error saving flow data:', error); Message.error('保存失败'); } - } - else { + } else { const appFlowParams = { appEventList: {}, - eventEdges: [] + eventEdges: [], }; - nodes.forEach(node => { - + nodes.forEach((node) => { appFlowParams.appEventList[node.id] = { x: node.position.x, - y: node.position.y + y: node.position.y, }; }); const eventMap = new Map(); edges.forEach((edge: any) => { - // 处理事件连线 appFlowParams.eventEdges.push({ id: edge.id, @@ -1398,9 +1308,9 @@ export const useFlowCallbacks = ( lineType: 'data', data: { displayData: { - ...edge.data.displayData - } - } + ...edge.data.displayData, + }, + }, }); // 应用组件的桩点id就是事件id @@ -1412,19 +1322,18 @@ export const useFlowCallbacks = ( // 如果topic已存在,将eventId添加到数组中 eventMap.get(topic).eventId.push(sourceId); eventMap.get(topic).eventId.push(targetId); - } - else { + } else { // 如果topic不存在,创建新的条目 eventMap.set(topic, { eventId: [sourceId, targetId], - topic: topic + topic: topic, }); } }); // 对eventId数组进行去重处理 - const appEventParams = Array.from(eventMap.values()).map(item => ({ + const appEventParams = Array.from(eventMap.values()).map((item) => ({ ...item, - eventId: Array.from(new Set(item.eventId)) + eventId: Array.from(new Set(item.eventId)), })); try { updateAppFlowData(appFlowParams); @@ -1440,176 +1349,185 @@ export const useFlowCallbacks = ( Message.success('保存成功'); } catch (error) { console.error('保存失败:', error); - Message.error('保存失败: ' + (error.message)); + Message.error('保存失败: ' + error.message); } } }, [nodes, edges, initialData?.appId]); // 运行处理函数 - const handleRun = useCallback(async (running: boolean) => { - const { currentAppData, socketId, appRuntimeData } = store.getState().ideContainer; - const appKey = getCurrentAppKey(); - - if (running) { - // 子流程运行 - if (currentAppData.key.includes('sub')) { - // 启动运行 - const params = { - appId: currentAppData.parentAppId, - socketId, - subflowId: currentAppData.key - }; - const res: any = await runSubFlow(params); - - if (res.code === 200) { - // 设置运行状态为true - dispatch(updateIsRunning(true)); - - // 重置节点状态 - dispatch(resetNodeStatus()); - - // 更新运行ID - dispatch(updateRuntimeId(res.data)); + const handleRun = useCallback( + async (running: boolean) => { + const { currentAppData, socketId, appRuntimeData } = + store.getState().ideContainer; + const appKey = getCurrentFlowAppKey(); + + if (running) { + // 子流程运行 + if (currentAppData.key.includes('sub')) { + // 启动运行 + const params = { + appId: currentAppData.parentAppId, + socketId, + subflowId: currentAppData.key, + }; + const res: any = await runSubFlow(params); - // 开始运行时动画 - setEdges((eds) => eds.map(edge => ({ - ...edge, - data: { - ...edge.data, - isRunning: true, - animationProgress: 0 - } - }))); + if (res.code === 200) { + // 设置运行状态为true + dispatch(updateIsRunning(true)); + + // 重置节点状态 + dispatch(resetNodeStatus()); + + // 更新运行ID + dispatch(updateRuntimeId(res.data)); + + // 开始运行时动画 + setEdges((eds) => + eds.map((edge) => ({ + ...edge, + data: { + ...edge.data, + isRunning: true, + animationProgress: 0, + }, + })) + ); + } else { + Message.error(res.message); + } } + // 主流程运行 else { - Message.error(res.message); + // 启动运行 + const params = { + appId: currentAppData.id, + socketId, + }; + const res: any = await runMainFlow(params); + if (res.code === 200) { + // 设置运行状态为true + dispatch(updateIsRunning(true)); + + // 重置节点状态 + dispatch(resetNodeStatus()); + + // 更新运行ID + dispatch(updateRuntimeId(res.data)); + + // 开始运行时动画 + setEdges((eds) => + eds.map((edge) => ({ + ...edge, + data: { + ...edge.data, + isRunning: true, + animationProgress: 0, + }, + })) + ); + } else { + Message.error(res.message); + } + } + } else { + // 设置运行状态为false + dispatch(updateIsRunning(false)); + + // 使用正确的 appKey 获取 runId + const runId = + appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : ''; + if (runId) { + await stopApp(runId); + } else { + // 特殊停止逻辑,持久化运行的应用使用这里的入参 + await stopApp(currentAppData.instanceId); + // 特殊停止完成后触发事件,通知刷新应用列表 + document.dispatchEvent(new CustomEvent('refreshAppList')); } - } - // 主流程运行 - else { - // 启动运行 - const params = { - appId: currentAppData.id, - socketId - }; - const res: any = await runMainFlow(params); - if (res.code === 200) { - // 设置运行状态为true - dispatch(updateIsRunning(true)); - // 重置节点状态 - dispatch(resetNodeStatus()); + // 重置节点状态 + dispatch(resetNodeStatus()); - // 更新运行ID - dispatch(updateRuntimeId(res.data)); + // 更新运行ID + dispatch(updateRuntimeId('')); - // 开始运行时动画 - setEdges((eds) => eds.map(edge => ({ + // 停止运行 + setEdges((eds) => + eds.map((edge) => ({ ...edge, data: { ...edge.data, - isRunning: true, - animationProgress: 0 - } - }))); - } - else { - Message.error(res.message); - } - } - } - else { - // 设置运行状态为false - dispatch(updateIsRunning(false)); - - // 使用正确的 appKey 获取 runId - const runId = appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : ''; - if (runId) { - await stopApp(runId); - } - else { - // 特殊停止逻辑,持久化运行的应用使用这里的入参 - await stopApp(currentAppData.instanceId); - // 特殊停止完成后触发事件,通知刷新应用列表 - document.dispatchEvent(new CustomEvent('refreshAppList')); - } - - // 重置节点状态 - dispatch(resetNodeStatus()); - - // 更新运行ID - dispatch(updateRuntimeId('')); + isRunning: false, + animationProgress: 0, + }, + })) + ); - // 停止运行 - setEdges((eds) => eds.map(edge => ({ - ...edge, - data: { - ...edge.data, - isRunning: false, - animationProgress: 0 + // 清空当前应用的运行日志 + if (appKey) { + dispatch(clearRuntimeLogs({ appId: appKey })); } - }))); - - // 清空当前应用的运行日志 - if (appKey) { - dispatch(clearRuntimeLogs({ appId: appKey })); } - } - }, [getCurrentAppKey]); + }, + [getCurrentFlowAppKey] + ); // 暂停/恢复应用 - const handlePause = useCallback(async (isPaused: boolean) => { - const { currentAppData, appRuntimeData } = store.getState().ideContainer; - const appKey = getCurrentAppKey(); - - if (!currentAppData) { - Message.warning('请先选择一个应用'); - return; - } + const handlePause = useCallback( + async (isPaused: boolean) => { + const { currentAppData, appRuntimeData } = store.getState().ideContainer; + const appKey = getCurrentFlowAppKey(); + + if (!currentAppData) { + Message.warning('请先选择一个应用'); + return; + } - // 获取runId - const runId = appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : ''; + // 获取runId + const runId = + appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : ''; - if (!runId) { - Message.warning('应用未运行'); - return; - } - - try { - if (isPaused) { - // 当前已暂停,执行恢复操作 - const res: any = await resumeApp({ id: runId }); - if (res.code === 200) { - Message.success('应用已恢复'); - // 更新暂停状态为 false - dispatch(updateIsPaused(false)); - } - else { - Message.error(res.msg || '恢复失败'); - } + if (!runId) { + Message.warning('应用未运行'); + return; } - else { - // 当前正在运行,执行暂停操作 - const res: any = await pauseApp({ id: runId }); - if (res.code === 200) { - Message.success('应用已暂停'); - // 更新暂停状态为 true - dispatch(updateIsPaused(true)); - } - else { - Message.error(res.msg || '暂停失败'); + + try { + if (isPaused) { + // 当前已暂停,执行恢复操作 + const res: any = await resumeApp({ id: runId }); + if (res.code === 200) { + Message.success('应用已恢复'); + // 更新暂停状态为 false + dispatch(updateIsPaused(false)); + } else { + Message.error(res.msg || '恢复失败'); + } + } else { + // 当前正在运行,执行暂停操作 + const res: any = await pauseApp({ id: runId }); + if (res.code === 200) { + Message.success('应用已暂停'); + // 更新暂停状态为 true + dispatch(updateIsPaused(true)); + } else { + Message.error(res.msg || '暂停失败'); + } } + } catch (error) { + console.error('暂停/恢复失败:', error); + Message.error('操作失败'); } - } catch (error) { - console.error('暂停/恢复失败:', error); - Message.error('操作失败'); - } - }, [getCurrentAppKey]); + }, + [getCurrentFlowAppKey] + ); // 重跑应用 const handleReRun = useCallback(async () => { - const { currentAppData, appRuntimeData, socketId } = store.getState().ideContainer; - const appKey = getCurrentAppKey(); + const { currentAppData, appRuntimeData, socketId } = + store.getState().ideContainer; + const appKey = getCurrentFlowAppKey(); if (!currentAppData) { Message.warning('请先选择一个应用'); @@ -1617,7 +1535,8 @@ export const useFlowCallbacks = ( } // 获取runId (instanceId) - const instanceId = appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : ''; + const instanceId = + appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : ''; if (!instanceId) { Message.warning('应用未运行'); @@ -1626,14 +1545,15 @@ export const useFlowCallbacks = ( try { // 判断是主流程还是子流程 - const appId = currentAppData.key && currentAppData.key.includes('sub') - ? currentAppData.parentAppId - : currentAppData.id; + const appId = + currentAppData.key && currentAppData.key.includes('sub') + ? currentAppData.parentAppId + : currentAppData.id; const res: any = await reRunApp({ appId, instanceId, - socketId + socketId, }); if (res.code === 200) { @@ -1641,15 +1561,14 @@ export const useFlowCallbacks = ( // 重置节点状态 dispatch(resetNodeStatus()); - } - else { + } else { Message.error(res.msg || '重跑失败'); } } catch (error) { console.error('重跑失败:', error); Message.error('重跑失败'); } - }, [getCurrentAppKey]); + }, [getCurrentFlowAppKey]); return { // Event handlers @@ -1686,7 +1605,7 @@ export const useFlowCallbacks = ( saveFlowDataToServer, handleRun, handlePause, - handleReRun + handleReRun, }; }; -export default useFlowCallbacks; \ No newline at end of file +export default useFlowCallbacks; diff --git a/src/hooks/useFlowEditorState.ts b/src/hooks/useFlowEditorState.ts index 9e01227..7acb218 100644 --- a/src/hooks/useFlowEditorState.ts +++ b/src/hooks/useFlowEditorState.ts @@ -3,6 +3,7 @@ import { Node, Edge } from '@xyflow/react'; import { debounce } from 'lodash'; import { useSelector, useDispatch, shallowEqual } from 'react-redux'; import { updateCanvasDataMap } from '@/store/ideContainer'; +import { getCurrentAppKey } from '@/utils/flow/runtime'; import { Dispatch } from 'redux'; @@ -11,31 +12,24 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => { const [edges, setEdges] = useState([]); // 使用 shallowEqual 比较器来避免不必要的重新渲染 - const ideContainerState = useSelector((state: any) => ({ - canvasDataMap: state.ideContainer.canvasDataMap, - appRuntimeData: state.ideContainer.appRuntimeData, - currentAppData: state.ideContainer.currentAppData, - }), shallowEqual); + const ideContainerState = useSelector( + (state: any) => ({ + canvasDataMap: state.ideContainer.canvasDataMap, + appRuntimeData: state.ideContainer.appRuntimeData, + currentAppData: state.ideContainer.currentAppData, + }), + shallowEqual + ); const { canvasDataMap, appRuntimeData, currentAppData } = ideContainerState; const dispatch = useDispatch(); - // 辅助函数:获取当前应用/子流程的唯一标识符 - const getCurrentAppKey = (currentAppData: any) => { - if (!currentAppData) return null; - // 如果是子流程(key包含'sub'),使用key作为标识符 - if (currentAppData.key && currentAppData.key.includes('sub')) { - return currentAppData.key; - } - // 否则使用id - return currentAppData.id; - }; - // 获取当前应用的运行状态 const currentAppKey = getCurrentAppKey(currentAppData); - const currentAppIsRunning = currentAppKey && appRuntimeData[currentAppKey] - ? appRuntimeData[currentAppKey].isRunning - : false; + const currentAppIsRunning = + currentAppKey && appRuntimeData[currentAppKey] + ? appRuntimeData[currentAppKey].isRunning + : false; // 添加编辑弹窗相关状态 const [editingNode, setEditingNode] = useState(null); @@ -44,7 +38,10 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => { // 添加节点选择弹窗状态 const [edgeForNodeAdd, setEdgeForNodeAdd] = useState(null); - const [positionForNodeAdd, setPositionForNodeAdd] = useState<{ x: number, y: number } | null>(null); + const [positionForNodeAdd, setPositionForNodeAdd] = useState<{ + x: number; + y: number; + } | null>(null); // 在组件顶部添加历史记录相关状态 const [historyInitialized, setHistoryInitialized] = useState(false); @@ -53,16 +50,18 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => { // 更新节点状态,将从store获取的状态应用到节点上 useEffect(() => { // 获取当前应用对应的节点状态映射 - const currentNodeStatusMap = currentAppKey && appRuntimeData[currentAppKey] - ? appRuntimeData[currentAppKey].nodeStatusMap - : {}; + const currentNodeStatusMap = + currentAppKey && appRuntimeData[currentAppKey] + ? appRuntimeData[currentAppKey].nodeStatusMap + : {}; // 检查 initialData 中是否包含节点状态(用于查看历史实例) - const hasInitialDataStatus = initialData?.components && + const hasInitialDataStatus = + initialData?.components && Object.values(initialData.components).some((comp: any) => comp.status); - setNodes(prevNodes =>{ - return prevNodes.map(node => { + setNodes((prevNodes) => { + return prevNodes.map((node) => { // 如果是只读模式(历史实例查看),优先使用节点自身的历史状态 // 如果是正常运行模式,只使用运行时状态 let nodeStatus = 'waiting'; @@ -70,8 +69,11 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => { if (readOnly) { // 只读模式:使用历史状态 - nodeStatus = node.data.status as string || 'waiting'; - showStatus = hasInitialDataStatus as boolean || node.data.isStatusVisible as boolean || false; + nodeStatus = (node.data.status as string) || 'waiting'; + showStatus = + (hasInitialDataStatus as boolean) || + (node.data.isStatusVisible as boolean) || + false; } else { // 正常模式:只使用运行时状态 nodeStatus = currentNodeStatusMap[node.id] || 'waiting'; @@ -83,21 +85,39 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => { data: { ...node.data, status: nodeStatus, - isStatusVisible: showStatus - } + isStatusVisible: showStatus, + }, }; }); - }); - }, [appRuntimeData, currentAppKey, currentAppIsRunning, initialData?.id, initialData?.components, nodes.length, readOnly]); + }, [ + appRuntimeData, + currentAppKey, + currentAppIsRunning, + initialData?.id, + initialData?.components, + nodes.length, + readOnly, + ]); const updateCanvasDataMapDebounced = useRef( - debounce((dispatch: Dispatch, canvasDataMap: any, id: string, nodes: Node[], edges: Edge[]) => { - dispatch(updateCanvasDataMap({ - ...canvasDataMap, - [id]: { nodes, edges } - })); - }, 500) + debounce( + ( + dispatch: Dispatch, + canvasDataMap: any, + id: string, + nodes: Node[], + edges: Edge[] + ) => { + dispatch( + updateCanvasDataMap({ + ...canvasDataMap, + [id]: { nodes, edges }, + }) + ); + }, + 500 + ) ).current; return { @@ -122,11 +142,11 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => { setHistoryInitialized, historyTimeoutRef, updateCanvasDataMapDebounced, - + // Redux dispatch, - + // Initial data - initialData: initialData + initialData: initialData, }; -}; \ No newline at end of file +}; diff --git a/src/pages/flowEditor/FlowEditorMain.tsx b/src/pages/flowEditor/FlowEditorMain.tsx index d8976cc..a4c4367 100644 --- a/src/pages/flowEditor/FlowEditorMain.tsx +++ b/src/pages/flowEditor/FlowEditorMain.tsx @@ -27,6 +27,7 @@ import { NodeTypes } from '@xyflow/react'; import { useSelector } from 'react-redux'; import HandlerBar from '@/pages/flowEditor/components/handlerBar'; import PublishFlowModal from '@/pages/flowEditor/components/publishFlowModal'; +import { getCurrentAppKey } from '@/utils/flow/runtime'; const edgeTypes = { custom: CustomEdge @@ -52,8 +53,10 @@ interface FlowEditorMainProps { setIsDelete: React.Dispatch>; edgeForNodeAdd: Edge | null; setEdgeForNodeAdd: React.Dispatch>; - positionForNodeAdd: { x: number, y: number } | null; - setPositionForNodeAdd: React.Dispatch>; + positionForNodeAdd: { x: number; y: number } | null; + setPositionForNodeAdd: React.Dispatch< + React.SetStateAction<{ x: number; y: number } | null> + >; isRunning: boolean; initialData: any; canvasDataMap: any; @@ -81,7 +84,11 @@ interface FlowEditorMainProps { copyNode: (node: Node) => void; pasteNode: (position: { x: number; y: number }) => void; addNodeOnEdge: (nodeType: string, node: any) => void; - addNodeOnPane: (nodeType: string, position: { x: number, y: number }, node?: any) => void; + addNodeOnPane: ( + nodeType: string, + position: { x: number; y: number }, + node?: any + ) => void; handleAddNode: (nodeType: string, node: any) => void; saveFlowDataToServer: () => void; handleRun: (running: boolean) => void; @@ -145,7 +152,8 @@ const FlowEditorMain: React.FC = (props) => { handleReRun } = props; - const { getGuidelines, clearGuidelines, AlignmentGuides } = useAlignmentGuidelines(); + const { getGuidelines, clearGuidelines, AlignmentGuides } = + useAlignmentGuidelines(); const { undo, redo, canUndo, canRedo } = useHistory(); const reactFlowId = useMemo(() => new Date().getTime().toString(), []); @@ -162,7 +170,7 @@ const FlowEditorMain: React.FC = (props) => { if (isVisible) { // 显示节点 - 从隐藏节点集合中移除 - setHiddenNodes(prev => { + setHiddenNodes((prev) => { const newSet = new Set(prev); newSet.delete(appId); return newSet; @@ -170,61 +178,33 @@ const FlowEditorMain: React.FC = (props) => { } else { // 隐藏节点 - 添加到隐藏节点集合 - setHiddenNodes(prev => new Set(prev).add(appId)); + setHiddenNodes((prev) => new Set(prev).add(appId)); } }; - document.addEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener); + document.addEventListener( + 'toggleNodeVisibility', + handleToggleNodeVisibility as EventListener + ); return () => { - document.removeEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener); + document.removeEventListener( + 'toggleNodeVisibility', + handleToggleNodeVisibility as EventListener + ); }; }, []); // 从Redux store中获取当前应用的运行状态 - const { appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer); - - // 辅助函数:获取当前应用/子流程的唯一标识符 - const getCurrentAppKey = () => { - if (!currentAppData) return null; - // 如果是子流程(key包含'sub'),使用key作为标识符 - if (currentAppData.key && currentAppData.key.includes('sub')) { - return currentAppData.key; - } - // 否则使用id - return currentAppData.id; - }; - - const currentAppKey = getCurrentAppKey(); - const currentAppIsRunning = currentAppKey && appRuntimeData[currentAppKey] - ? appRuntimeData[currentAppKey].isRunning - : false; - - // 监听自定义事件以隐藏/显示节点 - useEffect(() => { - const handleToggleNodeVisibility = (event: CustomEvent) => { - const { appId, isVisible } = event.detail; - - if (isVisible) { - // 显示节点 - 从隐藏节点集合中移除 - setHiddenNodes(prev => { - const newSet = new Set(prev); - newSet.delete(appId); - return newSet; - }); - } - else { - // 隐藏节点 - 添加到隐藏节点集合 - setHiddenNodes(prev => new Set(prev).add(appId)); - } - }; + const { appRuntimeData, currentAppData } = useSelector( + (state: any) => state.ideContainer + ); - document.addEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener); - - return () => { - document.removeEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener); - }; - }, []); + const currentAppKey = getCurrentAppKey(currentAppData); + const currentAppIsRunning = + currentAppKey && appRuntimeData[currentAppKey] + ? appRuntimeData[currentAppKey].isRunning + : false; // 监听键盘事件实现快捷键 useEffect(() => { @@ -275,7 +255,7 @@ const FlowEditorMain: React.FC = (props) => { // Ctrl/Cmd+C 复制选中的节点 if (isModifierKey && e.key === 'c' && canEdit) { // 获取当前选中的节点 - const selectedNode = nodes.find(node => node.selected); + const selectedNode = nodes.find((node) => node.selected); if (selectedNode) { // 不允许复制开始和结束节点 if (selectedNode.type === 'start' || selectedNode.type === 'end') { @@ -312,7 +292,18 @@ const FlowEditorMain: React.FC = (props) => { return () => { document.removeEventListener('keydown', handleKeyDown); }; - }, [undo, redo, canUndo, canRedo, currentAppIsRunning, useDefault, nodes, copyNode, pasteNode, reactFlowInstance]); + }, [ + undo, + redo, + canUndo, + canRedo, + currentAppIsRunning, + useDefault, + nodes, + copyNode, + pasteNode, + reactFlowInstance + ]); // 处理流程发布 const handlePublish = () => { @@ -338,18 +329,21 @@ const FlowEditorMain: React.FC = (props) => { }, [nodes, edges]); return ( -
e.preventDefault()} +
e.preventDefault()} // 添加点击事件处理器,用于关闭添加节点菜单 - onClick={() => { - if (edgeForNodeAdd || positionForNodeAdd) { - setEdgeForNodeAdd(null); - setPositionForNodeAdd(null); - } - }}> + onClick={() => { + if (edgeForNodeAdd || positionForNodeAdd) { + setEdgeForNodeAdd(null); + setPositionForNodeAdd(null); + } + }} + > { + nodes={nodes.map((node) => { // 检查节点是否应该被隐藏 const isHidden = hiddenNodes.has(node.id); // 应用透明度样式 @@ -364,12 +358,13 @@ const FlowEditorMain: React.FC = (props) => { } }; })} - edges={edges.map(edge => { + edges={edges.map((edge) => { // 检查边连接的节点是否被隐藏 const isSourceHidden = hiddenNodes.has(edge.source); const isTargetHidden = hiddenNodes.has(edge.target); // 如果源节点或目标节点被隐藏,则边也应用透明度 - const style = (isSourceHidden || isTargetHidden) ? { opacity: 0.3 } : {}; + const style = + isSourceHidden || isTargetHidden ? { opacity: 0.3 } : {}; // 更新边的数据,确保选择框也应用透明度 return { @@ -380,7 +375,9 @@ const FlowEditorMain: React.FC = (props) => { }, data: { ...edge.data, - ...(isSourceHidden || isTargetHidden ? { hidden: true, opacity: 0.3 } : { opacity: 1 }) + ...(isSourceHidden || isTargetHidden + ? { hidden: true, opacity: 0.3 } + : { opacity: 1 }) } }; })} @@ -410,15 +407,18 @@ const FlowEditorMain: React.FC = (props) => { } // 检查是否有开始或结束节点 - const hasStartOrEndNode = nodes.some(node => node.type === 'start' || node.type === 'end'); + const hasStartOrEndNode = nodes.some( + (node) => node.type === 'start' || node.type === 'end' + ); if (hasStartOrEndNode) { console.warn('开始和结束节点不允许删除'); return false; // 阻止删除操作 } // 检查是否有循环节点(这里只是检查,实际删除逻辑在onNodesDelete中处理) - const loopNodes = nodes.filter(node => - node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END' + const loopNodes = nodes.filter( + (node) => + node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END' ); // 允许删除操作继续进行 @@ -437,8 +437,9 @@ const FlowEditorMain: React.FC = (props) => { } // 检查是否有循环节点 - const loopNodes = deleted.filter(node => - node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END' + const loopNodes = deleted.filter( + (node) => + node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END' ); if (loopNodes.length > 0) { @@ -446,15 +447,20 @@ const FlowEditorMain: React.FC = (props) => { let nodesToRemove = [...deleted]; // 为每个循环节点找到其配对节点 - loopNodes.forEach(loopNode => { - const component = loopNode.data?.component as { customDef?: string } | undefined; - - if (loopNode.data?.type === 'LOOP_START' && component?.customDef) { + loopNodes.forEach((loopNode) => { + const component = loopNode.data?.component as + | { customDef?: string } + | undefined; + + if ( + loopNode.data?.type === 'LOOP_START' && + component?.customDef + ) { try { const customDef = JSON.parse(component.customDef); const relatedNodeId = customDef.loopEndNodeId; // 添加关联的结束节点到删除列表 - const relatedNode = nodes.find(n => n.id === relatedNodeId); + const relatedNode = nodes.find((n) => n.id === relatedNodeId); if (relatedNode) { nodesToRemove.push(relatedNode); } @@ -462,12 +468,15 @@ const FlowEditorMain: React.FC = (props) => { console.error('解析循环开始节点数据失败:', e); } } - else if (loopNode.data?.type === 'LOOP_END' && component?.customDef) { + else if ( + loopNode.data?.type === 'LOOP_END' && + component?.customDef + ) { try { const customDef = JSON.parse(component.customDef); const relatedNodeId = customDef.loopStartNodeId; // 添加关联的开始节点到删除列表 - const relatedNode = nodes.find(n => n.id === relatedNodeId); + const relatedNode = nodes.find((n) => n.id === relatedNodeId); if (relatedNode) { nodesToRemove.push(relatedNode); } @@ -478,56 +487,123 @@ const FlowEditorMain: React.FC = (props) => { }); // 去重 - nodesToRemove = nodesToRemove.filter((node, index, self) => - index === self.findIndex(n => n.id === node.id) + nodesToRemove = nodesToRemove.filter( + (node, index, self) => + index === self.findIndex((n) => n.id === node.id) ); // 删除所有相关节点和边 - setNodes((nds) => nds.filter((n) => !nodesToRemove.find((d) => d.id === n.id))); + setNodes((nds) => + nds.filter((n) => !nodesToRemove.find((d) => d.id === n.id)) + ); // 删除与这些节点相关的所有边 - const nodeIdsToRemove = nodesToRemove.map(node => node.id); - setEdges((eds) => eds.filter((e) => - !nodeIdsToRemove.includes(e.source) && !nodeIdsToRemove.includes(e.target) - )); + const nodeIdsToRemove = nodesToRemove.map((node) => node.id); + setEdges((eds) => + eds.filter( + (e) => + !nodeIdsToRemove.includes(e.source) && + !nodeIdsToRemove.includes(e.target) + ) + ); } else { // 普通节点删除 - setNodes((nds) => nds.filter((n) => !deleted.find((d) => d.id === n.id))); + setNodes((nds) => + nds.filter((n) => !deleted.find((d) => d.id === n.id)) + ); } setIsEditModalOpen(false); }} - onNodesChange={readOnly ? undefined : (!currentAppIsRunning ? onNodesChange : undefined)} // readOnly 或运行时禁用节点变更 - onEdgesChange={readOnly ? undefined : (!currentAppIsRunning ? onEdgesChange : undefined)} // readOnly 或运行时禁用边变更 - onConnect={readOnly ? undefined : (!currentAppIsRunning ? onConnect : undefined)} // readOnly 或运行时禁用连接 - onReconnect={readOnly ? undefined : (!currentAppIsRunning ? onReconnect : undefined)} // readOnly 或运行时禁用重新连接 - onDragOver={readOnly ? undefined : (!currentAppIsRunning ? onDragOver : undefined)} // readOnly 或运行时禁用拖拽 - onDrop={readOnly ? undefined : (!currentAppIsRunning ? onDrop : undefined)} // readOnly 或运行时禁用放置 - onNodeDrag={readOnly ? undefined : (!currentAppIsRunning ? onNodeDrag : undefined)} // readOnly 或运行时禁用节点拖拽 + onNodesChange={ + readOnly + ? undefined + : !currentAppIsRunning + ? onNodesChange + : undefined + } // readOnly 或运行时禁用节点变更 + onEdgesChange={ + readOnly + ? undefined + : !currentAppIsRunning + ? onEdgesChange + : undefined + } // readOnly 或运行时禁用边变更 + onConnect={ + readOnly ? undefined : !currentAppIsRunning ? onConnect : undefined + } // readOnly 或运行时禁用连接 + onReconnect={ + readOnly ? undefined : !currentAppIsRunning ? onReconnect : undefined + } // readOnly 或运行时禁用重新连接 + onDragOver={ + readOnly ? undefined : !currentAppIsRunning ? onDragOver : undefined + } // readOnly 或运行时禁用拖拽 + onDrop={ + readOnly ? undefined : !currentAppIsRunning ? onDrop : undefined + } // readOnly 或运行时禁用放置 + onNodeDrag={ + readOnly ? undefined : !currentAppIsRunning ? onNodeDrag : undefined + } // readOnly 或运行时禁用节点拖拽 connectionLineType={ConnectionLineType.SmoothStep} connectionLineComponent={CustomConnectionLine} - onNodeDragStop={readOnly ? undefined : (!currentAppIsRunning ? onNodeDragStop : undefined)} // readOnly 或运行时禁用节点拖拽停止 - onNodeContextMenu={readOnly ? undefined : ((useDefault || currentAppIsRunning) ? onNodeContextMenu : undefined)} // readOnly 或应用编排模式下禁用节点上下文菜单 - onNodeDoubleClick={readOnly ? undefined : (!currentAppIsRunning ? onNodeDoubleClick : undefined)} // readOnly 或运行时禁用节点双击 - onEdgeContextMenu={readOnly ? undefined : ((!currentAppIsRunning && useDefault) ? onEdgeContextMenu : undefined)} // readOnly 或运行时或应用编排模式下禁用边上下文菜单 - onPaneClick={(useDefault || currentAppIsRunning) ? onPaneClick : undefined} // 运行时禁用面板点击 - onPaneContextMenu={readOnly ? undefined : ((!currentAppIsRunning && useDefault) ? onPaneContextMenu : undefined)} // readOnly 或运行时或应用编排模式下禁用面板上下文菜单 + onNodeDragStop={ + readOnly + ? undefined + : !currentAppIsRunning + ? onNodeDragStop + : undefined + } // readOnly 或运行时禁用节点拖拽停止 + onNodeContextMenu={ + readOnly + ? undefined + : useDefault || currentAppIsRunning + ? onNodeContextMenu + : undefined + } // readOnly 或应用编排模式下禁用节点上下文菜单 + onNodeDoubleClick={ + readOnly + ? undefined + : !currentAppIsRunning + ? onNodeDoubleClick + : undefined + } // readOnly 或运行时禁用节点双击 + onEdgeContextMenu={ + readOnly + ? undefined + : !currentAppIsRunning && useDefault + ? onEdgeContextMenu + : undefined + } // readOnly 或运行时或应用编排模式下禁用边上下文菜单 + onPaneClick={ + useDefault || currentAppIsRunning ? onPaneClick : undefined + } // 运行时禁用面板点击 + onPaneContextMenu={ + readOnly + ? undefined + : !currentAppIsRunning && useDefault + ? onPaneContextMenu + : undefined + } // readOnly 或运行时或应用编排模式下禁用面板上下文菜单 onEdgeMouseEnter={(_event, edge) => { - setEdges((eds) => eds.map(e => { - if (e.id === edge.id) { - return { ...e, data: { ...e.data, hovered: true } }; - } - return e; - })); + setEdges((eds) => + eds.map((e) => { + if (e.id === edge.id) { + return { ...e, data: { ...e.data, hovered: true } }; + } + return e; + }) + ); }} onEdgeMouseLeave={(_event, edge) => { - setEdges((eds) => eds.map(e => { - if (e.id === edge.id) { - return { ...e, data: { ...e.data, hovered: false } }; - } - return e; - })); + setEdges((eds) => + eds.map((e) => { + if (e.id === edge.id) { + return { ...e, data: { ...e.data, hovered: false } }; + } + return e; + }) + ); }} fitView selectionOnDrag={!currentAppIsRunning} // 运行时禁用拖拽选择 @@ -550,14 +626,16 @@ const FlowEditorMain: React.FC = (props) => { > )} - {useDefault && !readOnly && - - } + {useDefault && !readOnly && ( + + + + )} @@ -572,7 +650,7 @@ const FlowEditorMain: React.FC = (props) => { }} > n.id === menu.id)!} + node={nodes.find((n) => n.id === menu.id)!} onDelete={deleteNode} onEdit={editNode} onCopy={copyNode} @@ -594,7 +672,7 @@ const FlowEditorMain: React.FC = (props) => { }} > e.id === menu.id)!} + edge={edges.find((e) => e.id === menu.id)!} onDelete={deleteEdge} onEdit={editEdge} onAddNode={(edge) => { @@ -618,7 +696,11 @@ const FlowEditorMain: React.FC = (props) => { > { + onAddNode={( + nodeType: string, + position: { x: number; y: number }, + node: any + ) => { addNodeOnPane(nodeType, position, node); setIsEditModalOpen(false); setMenu(null); // 关闭上下文菜单 @@ -638,30 +720,36 @@ const FlowEditorMain: React.FC = (props) => { /> {/*统一的添加节点菜单 - 仅在默认模式且非运行时显示*/} - {!currentAppIsRunning && useDefault && (edgeForNodeAdd || positionForNodeAdd) && ( -
e.stopPropagation()} - > - { - handleAddNode(nodeType, node); - // 关闭菜单 - setEdgeForNodeAdd(null); - setPositionForNodeAdd(null); + {!currentAppIsRunning && + useDefault && + (edgeForNodeAdd || positionForNodeAdd) && ( +
-
- )} + // 点击事件冒泡到父级处理,这里阻止默认行为 + onClick={(e) => e.stopPropagation()} + > + { + handleAddNode(nodeType, node); + // 关闭菜单 + setEdgeForNodeAdd(null); + setPositionForNodeAdd(null); + }} + position={positionForNodeAdd || undefined} + edgeId={edgeForNodeAdd?.id} + /> +
+ )} {/*流程发布弹窗*/} = (props) => { ); }; -export default FlowEditorMain; \ No newline at end of file +export default FlowEditorMain; diff --git a/src/pages/flowEditor/components/actionBar.tsx b/src/pages/flowEditor/components/actionBar.tsx index 00ab72d..2cdbd10 100644 --- a/src/pages/flowEditor/components/actionBar.tsx +++ b/src/pages/flowEditor/components/actionBar.tsx @@ -7,10 +7,11 @@ import { IconPause, IconSync, IconUndo, - IconRedo + IconRedo, } from '@arco-design/web-react/icon'; import { updateLogBarStatus } from '@/store/ideContainer'; import { useSelector, useDispatch } from 'react-redux'; +import { getCurrentAppKey } from '@/utils/flow/runtime'; const ButtonGroup = Button.Group; @@ -28,41 +29,34 @@ interface ActionBarProps { } const ActionBar: React.FC = ({ - useDefault, - onSave, - onUndo, - onRedo, - canUndo = false, - canRedo = false, - onRun, - onPause, - onReRun, - isRunning = false - }) => { - const { logBarStatus, appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer); + useDefault, + onSave, + onUndo, + onRedo, + canUndo = false, + canRedo = false, + onRun, + onPause, + onReRun, + isRunning = false, +}) => { + const { logBarStatus, appRuntimeData, currentAppData } = useSelector( + (state: any) => state.ideContainer + ); const dispatch = useDispatch(); - // 辅助函数:获取当前应用/子流程的唯一标识符 - const getCurrentAppKey = () => { - if (!currentAppData) return null; - // 如果是子流程(key包含'sub'),使用key作为标识符 - if (currentAppData.key && currentAppData.key.includes('sub')) { - return currentAppData.key; - } - // 否则使用id - return currentAppData.id; - }; - // 获取当前应用的运行状态 - const currentAppKey = getCurrentAppKey(); - const currentAppIsRunning = currentAppKey && appRuntimeData[currentAppKey] - ? appRuntimeData[currentAppKey].isRunning - : false; + const currentAppKey = getCurrentAppKey(currentAppData); + const currentAppIsRunning = + currentAppKey && appRuntimeData[currentAppKey] + ? appRuntimeData[currentAppKey].isRunning + : false; // 获取当前应用的暂停状态(如果有的话) - const currentAppIsPaused = currentAppKey && appRuntimeData[currentAppKey] - ? appRuntimeData[currentAppKey].isPaused - : false; + const currentAppIsPaused = + currentAppKey && appRuntimeData[currentAppKey] + ? appRuntimeData[currentAppKey].isPaused + : false; const changeLogBarStatus = () => { dispatch(updateLogBarStatus(!logBarStatus)); @@ -84,7 +78,9 @@ const ActionBar: React.FC = ({ return (
- + {useDefault && ( <> @@ -158,4 +154,4 @@ const ActionBar: React.FC = ({ ); }; -export default ActionBar; \ No newline at end of file +export default ActionBar; diff --git a/src/pages/ideContainer/logBar.tsx b/src/pages/ideContainer/logBar.tsx index df8d242..aa3b4db 100644 --- a/src/pages/ideContainer/logBar.tsx +++ b/src/pages/ideContainer/logBar.tsx @@ -5,6 +5,7 @@ import { updateLogBarStatus } from '@/store/ideContainer'; import { useSelector, useDispatch } from 'react-redux'; import { getNodeData } from '@/api/appIns'; import RunTimeData from './components/runTimeData'; +import { getCurrentAppKey } from '@/utils/flow/runtime'; const TabPane = Tabs.TabPane; @@ -28,17 +29,17 @@ const data = [ { key: '1', title: '运行日志', - content: '运行时日志...' + content: '运行时日志...', }, { key: '2', title: '校验日志', - content: '校验日志...' + content: '校验日志...', }, { key: '3', title: '运行数据', - content: '运行数据日志...' + content: '运行数据日志...', }, // { // key: '4', @@ -54,30 +55,20 @@ const LogBar: React.FC = () => { const [validationLogs, setValidationLogs] = useState([]); const [runtimeLogs, setruntimeLogs] = useState([]); // 添加运行时日志状态 const [logContainerHeight, setLogContainerHeight] = useState('250px'); // 添加日志容器高度状态 - const [runtimeData, setRuntimeData] = useState({}); // 添加运行数据状态 + const [runtimeData, setRuntimeData] = useState({}); // 添加运行数据状态 const [loading, setLoading] = useState(false); - const { logBarStatus, appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer); + const { logBarStatus, appRuntimeData, currentAppData } = useSelector( + (state: any) => state.ideContainer + ); const dispatch = useDispatch(); - // 辅助函数:获取当前应用/子流程的唯一标识符 - const getCurrentAppKey = () => { - if (!currentAppData) return null; - // 如果是子流程(key包含'sub'),使用key作为标识符 - if (currentAppData.key && currentAppData.key.includes('sub')) { - return currentAppData.key; - } - // 否则使用id - return currentAppData.id; - }; - // 处理 Tab 点击事件 const handleTabClick = (key: string) => { // 如果点击当前激活的 tab,则切换收起状态 if (key === activeTab) { dispatch(updateLogBarStatus(!logBarStatus)); - } - else { + } else { // 如果点击的是其他 tab,则切换到该 tab 并展开 setActiveTab(key); dispatch(updateLogBarStatus(true)); @@ -87,20 +78,24 @@ const LogBar: React.FC = () => { // 当 collapsed 状态改变时,直接更新元素的样式 useEffect(() => { if (resizeBoxRef.current) { - resizeBoxRef.current.style.height = logBarStatus ? logContainerHeight : '0px'; + resizeBoxRef.current.style.height = logBarStatus + ? logContainerHeight + : '0px'; } }, [logBarStatus, logContainerHeight]); // 处理 ResizeBox 手动调整大小事件 - const handleResize = (e: MouseEvent, size: { - width: number; - height: number; - }) => { + const handleResize = ( + e: MouseEvent, + size: { + width: number; + height: number; + } + ) => { // 当高度接近收起状态的高度时,同步更新 logBarStatus 状态 if (size.height <= 40) { dispatch(updateLogBarStatus(false)); - } - else { + } else { dispatch(updateLogBarStatus(true)); // 更新日志容器高度状态 setLogContainerHeight(`${size.height}px`); @@ -118,10 +113,10 @@ const LogBar: React.FC = () => { id: Date.now(), type, message, - timestamp + timestamp, }; - setValidationLogs(prev => [...prev, newLog]); + setValidationLogs((prev) => [...prev, newLog]); // 自动切换到校验日志tab并展开logBar // setActiveTab('2'); @@ -133,24 +128,24 @@ const LogBar: React.FC = () => { id: Date.now(), type, message, - timestamp + timestamp, }; - setruntimeLogs(prev => [...prev, newLog]); + setruntimeLogs((prev) => [...prev, newLog]); // 自动切换到运行日志tab并展开logBar dispatch(updateLogBarStatus(true)); // 同时将日志添加到对应应用的运行日志中 // 如果提供了 appId,优先使用提供的 appId;否则使用当前激活的应用 - const targetAppKey = appId || getCurrentAppKey(); + const targetAppKey = appId || getCurrentAppKey(currentAppData); if (targetAppKey) { dispatch({ type: 'ideContainer/addRuntimeLog', payload: { log: newLog, - appId: targetAppKey - } + appId: targetAppKey, + }, }); } } @@ -161,28 +156,35 @@ const LogBar: React.FC = () => { // 清理事件监听器 return () => { - document.removeEventListener('logMessage', handleLogMessage as EventListener); + document.removeEventListener( + 'logMessage', + handleLogMessage as EventListener + ); }; }, [dispatch, currentAppData]); // 获取当前应用的运行状态 - const currentAppKey = getCurrentAppKey(); + const currentAppKey = getCurrentAppKey(currentAppData); const isRunning = currentAppKey && appRuntimeData[currentAppKey]?.isRunning; // 实现轮询获取运行数据 - 只在应用运行时轮询 useEffect(() => { let intervalId: NodeJS.Timeout | null = null; - const appKey = getCurrentAppKey(); + const appKey = getCurrentAppKey(currentAppData); // 只有在应用正在运行且有 runId 时才开始轮询 - if (appKey && appRuntimeData[appKey]?.isRunning && appRuntimeData[appKey]?.runId) { + if ( + appKey && + appRuntimeData[appKey]?.isRunning && + appRuntimeData[appKey]?.runId + ) { const fetchRuntimeData = async () => { try { setLoading(true); const response = await getNodeData(appRuntimeData[appKey].runId); - setRuntimeData(prev => ({ + setRuntimeData((prev) => ({ ...prev, - [appKey]: response.data + [appKey]: response.data, })); } catch (error) { console.error('获取运行数据失败:', error); @@ -208,10 +210,10 @@ const LogBar: React.FC = () => { // 当应用停止运行时,清除运行数据 useEffect(() => { - const appKey = getCurrentAppKey(); + const appKey = getCurrentAppKey(currentAppData); if (appKey && !appRuntimeData[appKey]?.isRunning) { // 清除当前应用的运行数据 - setRuntimeData(prev => { + setRuntimeData((prev) => { const newData = { ...prev }; delete newData[appKey]; return newData; @@ -222,12 +224,25 @@ const LogBar: React.FC = () => { // 渲染校验日志内容 const renderValidationLogs = () => { return ( -
+
{validationLogs.length === 0 ? (

暂无校验日志

) : ( - validationLogs.map(log => ( -
+ validationLogs.map((log) => ( +
{new Date(log.timestamp).toLocaleString()}
@@ -244,18 +259,32 @@ const LogBar: React.FC = () => { // 渲染运行时日志内容 const renderRuntimeLogs = () => { // 获取当前应用的运行日志 - const currentAppKey = getCurrentAppKey(); - const currentAppLogs = currentAppKey && appRuntimeData[currentAppKey] - ? appRuntimeData[currentAppKey].logs || [] - : []; + const currentAppKey = getCurrentAppKey(currentAppData); + const currentAppLogs = + currentAppKey && appRuntimeData[currentAppKey] + ? appRuntimeData[currentAppKey].logs || [] + : []; return ( -
+
{currentAppLogs.length === 0 ? (

暂无运行时日志

) : ( currentAppLogs.map((log: LogMessage) => ( -
+
{new Date(log.timestamp).toLocaleString()}
@@ -271,11 +300,19 @@ const LogBar: React.FC = () => { // 渲染运行数据内容 const renderRuntimeData = () => { - const currentAppKey = getCurrentAppKey(); - const currentAppDataContent = currentAppKey ? runtimeData[currentAppKey] : null; + const currentAppKey = getCurrentAppKey(currentAppData); + const currentAppDataContent = currentAppKey + ? runtimeData[currentAppKey] + : null; return ( -
+
{!currentAppDataContent ? (

暂无运行数据

) : ( @@ -292,7 +329,7 @@ const LogBar: React.FC = () => { className={styles.logBar} directions={['top']} style={{ - height: logBarStatus ? logContainerHeight : '0px' + height: logBarStatus ? logContainerHeight : '0px', }} onMoving={handleResize} > @@ -304,10 +341,13 @@ const LogBar: React.FC = () => { > {tabs.map((x) => ( - {x.key === '1' ? renderRuntimeLogs() : - x.key === '2' ? renderValidationLogs() : - x.key === '3' ? renderRuntimeData() : // 添加运行数据渲染 - x.content} + {x.key === '1' + ? renderRuntimeLogs() + : x.key === '2' + ? renderValidationLogs() + : x.key === '3' + ? renderRuntimeData() // 添加运行数据渲染 + : x.content} ))} @@ -316,4 +356,4 @@ const LogBar: React.FC = () => { ); }; -export default LogBar; \ No newline at end of file +export default LogBar; diff --git a/src/store/ideContainer.ts b/src/store/ideContainer.ts index c158be6..3dddcaa 100644 --- a/src/store/ideContainer.ts +++ b/src/store/ideContainer.ts @@ -1,4 +1,8 @@ import { createSlice } from '@reduxjs/toolkit'; +import { + createDefaultAppRuntimeState, + getCurrentAppKey, +} from '@/utils/flow/runtime'; // 定义初始状态类型 interface IDEContainerState { @@ -16,22 +20,25 @@ interface IDEContainerState { nodeStatusMap: Record; // 节点状态映射 isRunning: boolean; // 是否正在运行 // 应用运行状态和日志数据,按应用ID隔离存储 - appRuntimeData: Record; - isRunning: boolean; - isPaused: boolean; - logs: any[]; - runId: string; - eventSendNodeList: any[], // [{nodeID:topic}] - eventlisteneList: any[] // [{nodeID:topic}] - }>; + appRuntimeData: Record< + string, + { + nodeStatusMap: Record; + isRunning: boolean; + isPaused: boolean; + logs: any[]; + runId: string; + eventSendNodeList: any[]; // [{nodeID:topic}] + eventlisteneList: any[]; // [{nodeID:topic}] + } + >; gloBalVarList: any; // 组件开发相关状态 componentCoding: { localProjectPath: string; // code-server路径 name: string; // 组件名称 projectId: string; // 组件标识 - id: string,// 组件id + id: string; // 组件id }; } @@ -51,25 +58,13 @@ const initialState: IDEContainerState = { nodeStatusMap: {}, // 初始化节点状态映射 isRunning: false, // 默认未运行 appRuntimeData: {}, // 按应用ID隔离的应用运行状态和日志数据 - gloBalVarList: [], // 工程级全局变量列表 + gloBalVarList: [], // 工程级全局变量列表 componentCoding: { localProjectPath: '', name: '', projectId: '', - id: '' - } -}; - -// 辅助函数:获取当前应用/子流程的唯一标识符 -// 对于子流程,使用 key(如 sub_xxx);对于主流程,使用 id -const getCurrentAppKey = (currentAppData: any) => { - if (!currentAppData) return null; - // 如果是子流程(key包含'sub'),使用key作为标识符 - if (currentAppData.key && currentAppData.key.includes('sub')) { - return currentAppData.key; - } - // 否则使用id - return currentAppData.id; + id: '', + }, }; // 创建切片 @@ -90,7 +85,10 @@ const ideContainerSlice = createSlice({ state.canvasDataMap = { ...state.canvasDataMap, ...action.payload }; }, updateProjectComponentData(state, action) { - state.projectComponentData = { ...state.projectComponentData, ...action.payload }; + state.projectComponentData = { + ...state.projectComponentData, + ...action.payload, + }; }, updateCurrentAppData(state, action) { state.currentAppData = action.payload; @@ -98,7 +96,9 @@ const ideContainerSlice = createSlice({ // 切换应用时,同步全局 nodeStatusMap 为新应用的节点状态 const newAppKey = getCurrentAppKey(action.payload); if (newAppKey && state.appRuntimeData[newAppKey]) { - state.nodeStatusMap = { ...state.appRuntimeData[newAppKey].nodeStatusMap }; + state.nodeStatusMap = { + ...state.appRuntimeData[newAppKey].nodeStatusMap, + }; state.isRunning = state.appRuntimeData[newAppKey].isRunning; } else { // 如果新应用没有运行时数据,清空节点状态 @@ -140,15 +140,7 @@ const ideContainerSlice = createSlice({ // 更新目标应用的节点状态 if (targetAppKey) { if (!state.appRuntimeData[targetAppKey]) { - state.appRuntimeData[targetAppKey] = { - nodeStatusMap: {}, - isRunning: false, - isPaused: false, - logs: [], - runId: '', - eventSendNodeList: [], - eventlisteneList: [] - }; + state.appRuntimeData[targetAppKey] = createDefaultAppRuntimeState(); } state.appRuntimeData[targetAppKey].nodeStatusMap[nodeId] = status; } @@ -171,15 +163,7 @@ const ideContainerSlice = createSlice({ const appKey = getCurrentAppKey(state.currentAppData); if (appKey) { if (!state.appRuntimeData[appKey]) { - state.appRuntimeData[appKey] = { - nodeStatusMap: {}, - isRunning: false, - isPaused: false, - logs: [], - runId: '', - eventSendNodeList: [], - eventlisteneList: [] - }; + state.appRuntimeData[appKey] = createDefaultAppRuntimeState(); } state.appRuntimeData[appKey].isRunning = payload; } @@ -189,15 +173,7 @@ const ideContainerSlice = createSlice({ const appKey = getCurrentAppKey(state.currentAppData); if (appKey) { if (!state.appRuntimeData[appKey]) { - state.appRuntimeData[appKey] = { - nodeStatusMap: {}, - isRunning: false, - isPaused: false, - logs: [], - runId: '', - eventSendNodeList: [], - eventlisteneList: [] - }; + state.appRuntimeData[appKey] = createDefaultAppRuntimeState(); } state.appRuntimeData[appKey].isPaused = payload; } @@ -206,15 +182,7 @@ const ideContainerSlice = createSlice({ updateRuntimeId: (state, { payload }) => { const appKey = getCurrentAppKey(state.currentAppData); if (!state.appRuntimeData[appKey]) { - state.appRuntimeData[appKey] = { - nodeStatusMap: {}, - isRunning: false, - isPaused: false, - logs: [], - runId: '', - eventSendNodeList: [], - eventlisteneList: [] - }; + state.appRuntimeData[appKey] = createDefaultAppRuntimeState(); } state.appRuntimeData[appKey].runId = payload; }, @@ -222,31 +190,18 @@ const ideContainerSlice = createSlice({ updateEventNodeList: (state, { payload }) => { const appKey = getCurrentAppKey(state.currentAppData); if (!state.appRuntimeData[appKey]) { - state.appRuntimeData[appKey] = { - nodeStatusMap: {}, - isRunning: false, - isPaused: false, - logs: [], - runId: '', - eventSendNodeList: [], - eventlisteneList: [] - }; + state.appRuntimeData[appKey] = createDefaultAppRuntimeState(); } - state.appRuntimeData[appKey] = { ...state.appRuntimeData[appKey], ...payload }; + state.appRuntimeData[appKey] = { + ...state.appRuntimeData[appKey], + ...payload, + }; }, // 添加运行日志 addRuntimeLog: (state, { payload }) => { const { log, appId } = payload; if (!state.appRuntimeData[appId]) { - state.appRuntimeData[appId] = { - nodeStatusMap: {}, - isRunning: false, - isPaused: false, - logs: [], - runId: '', - eventSendNodeList: [], - eventlisteneList: [] - }; + state.appRuntimeData[appId] = createDefaultAppRuntimeState(); } state.appRuntimeData[appId].logs.push(log); }, @@ -260,8 +215,8 @@ const ideContainerSlice = createSlice({ // 更新组件编码路径 updateComponentCodingPath(state, action) { state.componentCoding = { ...action.payload }; - } - } + }, + }, }); // 导出动作 creators @@ -286,8 +241,8 @@ export const { updateEventNodeList, addRuntimeLog, clearRuntimeLogs, - updateComponentCodingPath + updateComponentCodingPath, } = ideContainerSlice.actions; // 默认导出 reducer -export default ideContainerSlice.reducer; \ No newline at end of file +export default ideContainerSlice.reducer; diff --git a/src/utils/convertFlowData.ts b/src/utils/convertFlowData.ts index 0a46f89..7cd3c88 100644 --- a/src/utils/convertFlowData.ts +++ b/src/utils/convertFlowData.ts @@ -1,14 +1,8 @@ import { nodeTypeMap, registerNodeType } from '@/components/FlowEditor/node'; import store from '@/store/index'; -import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode'; import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode'; -import SwitchNode from '@/components/FlowEditor/node/switchNode/SwitchNode'; -import BasicNode from '@/components/FlowEditor/node/basicNode/BasicNode'; -import ImageNode from '@/components/FlowEditor/node/imageNode/ImageNode'; -import CodeNode from '@/components/FlowEditor/node/codeNode/CodeNode'; -import RestNode from '@/components/FlowEditor/node/restNode/RestNode'; import { updateEventNodeList } from '@/store/ideContainer'; - +import { resolveNodeComponent } from '@/utils/flow/nodeRegistry'; /** * 将提供的数据结构转换为适用于 flow editor 的 nodes 和 edges @@ -37,12 +31,14 @@ export const convertFlowData = (flowData: any, useDefault = true) => { title: '开始', parameters: { apiIns: [], - apiOuts: [{ name: 'start', desc: '', dataType: '', defaultValue: '' }], + apiOuts: [ + { name: 'start', desc: '', dataType: '', defaultValue: '' }, + ], dataIns: [], - dataOuts: [] + dataOuts: [], }, - type: 'start' - } + type: 'start', + }, }, { id: `end-${timestamp}`, @@ -51,16 +47,18 @@ export const convertFlowData = (flowData: any, useDefault = true) => { data: { title: '结束', parameters: { - apiIns: [{ name: 'end', desc: '', dataType: '', defaultValue: '' }], + apiIns: [ + { name: 'end', desc: '', dataType: '', defaultValue: '' }, + ], apiOuts: [], dataIns: [], - dataOuts: [] + dataOuts: [], }, - type: 'end' - } - } + type: 'end', + }, + }, ], - edges: [] + edges: [], }; } // 否则返回空数组 @@ -80,44 +78,55 @@ export const convertFlowData = (flowData: any, useDefault = true) => { try { const customDef = JSON.parse(nodeConfig.component.customDef); // 使用展开运算符创建新数组,避免修改冻结对象 - eventlisteneList.splice(eventlisteneList.length, 0, { [nodeId]: customDef.topic }); + eventlisteneList.splice(eventlisteneList.length, 0, { + [nodeId]: customDef.topic, + }); } catch (error) { console.log(error); } - } - else if (nodeId.includes('EVENTSEND') && !nodeId.includes('EVENTSEND_SYNC')) { + } else if ( + nodeId.includes('EVENTSEND') && + !nodeId.includes('EVENTSEND_SYNC') + ) { try { const customDef = JSON.parse(nodeConfig.component.customDef); // 使用展开运算符创建新数组,避免修改冻结对象 - eventSendNodeList.splice(eventSendNodeList.length, 0, { [nodeId]: customDef.topic }); + eventSendNodeList.splice(eventSendNodeList.length, 0, { + [nodeId]: customDef.topic, + }); } catch (error) { console.log(error); } } - if (eventlisteneList.length > 0 || eventSendNodeList.length > 0) store.dispatch(updateEventNodeList({ - eventSendNodeList: [...eventSendNodeList], - eventlisteneList: [...eventlisteneList] - })); + if (eventlisteneList.length > 0 || eventSendNodeList.length > 0) + store.dispatch( + updateEventNodeList({ + eventSendNodeList: [...eventSendNodeList], + eventlisteneList: [...eventlisteneList], + }) + ); else { - store.dispatch(updateEventNodeList({ - eventSendNodeList: [], - eventlisteneList: [] - })); + store.dispatch( + updateEventNodeList({ + eventSendNodeList: [], + eventlisteneList: [], + }) + ); } // 确定节点类型 let nodeType = 'BASIC'; if (nodeId.includes('start')) { nodeType = 'start'; - } - else if (nodeId.includes('end')) { + } else if (nodeId.includes('end')) { nodeType = 'end'; - } - else if (nodeConfig.component?.type === 'LOOP_START' || nodeConfig.component?.type === 'LOOP_END') { + } else if ( + nodeConfig.component?.type === 'LOOP_START' || + nodeConfig.component?.type === 'LOOP_END' + ) { nodeType = 'LOOP'; - } - else { + } else { nodeType = nodeConfig.component?.type || 'BASIC'; } @@ -135,10 +144,10 @@ export const convertFlowData = (flowData: any, useDefault = true) => { apiIns: getNodeApiIns(nodeId, nodeConfig, currentProjectCompData), apiOuts: getNodeApiOuts(nodeId, nodeConfig, currentProjectCompData), dataIns: nodeConfig.dataIns || [], - dataOuts: nodeConfig.dataOuts || [] + dataOuts: nodeConfig.dataOuts || [], }, - type: nodeConfig.component?.type || nodeType - } + type: nodeConfig.component?.type || nodeType, + }, }; // 添加组件标识信息 @@ -157,16 +166,25 @@ export const convertFlowData = (flowData: any, useDefault = true) => { // 注册循环节点类型 if (nodeType === 'LOOP') { - const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); + const nodeMap = Array.from(Object.values(nodeTypeMap).map((key) => key)); if (!nodeMap.includes('LOOP')) { registerNodeType('LOOP', LoopNode, '循环'); } } // 注册其他节点类型 - const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key)); - if (!nodeMap.includes(nodeType) && nodeType !== 'start' && nodeType !== 'end' && nodeType !== 'LOOP') { - registerNodeType(nodeType, getNodeComponent(nodeType), nodeConfig.componentName); + const nodeMap = Array.from(Object.values(nodeTypeMap).map((key) => key)); + if ( + !nodeMap.includes(nodeType) && + nodeType !== 'start' && + nodeType !== 'end' && + nodeType !== 'LOOP' + ) { + registerNodeType( + nodeType, + resolveNodeComponent(nodeType), + nodeConfig.componentName + ); } nodes.push(node); @@ -176,7 +194,15 @@ export const convertFlowData = (flowData: any, useDefault = true) => { const addedEdges = new Set(); // 创建一个映射来存储所有连接信息 - const connections = new Map(); + const connections = new Map< + string, + { + source: string; + target: string; + sourceHandle: string; + targetHandle: string; + } + >(); // 遍历所有节点,收集连接信息 for (const entry of nodeEntries) { @@ -187,7 +213,7 @@ export const convertFlowData = (flowData: any, useDefault = true) => { if (nodeConfig.apiDownstream && Array.isArray(nodeConfig.apiDownstream)) { nodeConfig.apiDownstream.forEach((targetArray: string[]) => { if (Array.isArray(targetArray)) { - targetArray.forEach(target => { + targetArray.forEach((target) => { if (typeof target === 'string' && target.includes('$$')) { const [targetNodeId, targetHandle] = target.split('$$'); const connectionKey = `${nodeId}-${targetNodeId}`; @@ -199,17 +225,16 @@ export const convertFlowData = (flowData: any, useDefault = true) => { if (existing) { connections.set(connectionKey, { ...existing, - targetHandle: targetHandle + targetHandle: targetHandle, }); } - } - else { + } else { // 创建新的连接信息 connections.set(connectionKey, { source: nodeId, target: targetNodeId, sourceHandle: '', // 将根据节点信息填充 - targetHandle: targetHandle + targetHandle: targetHandle, }); } } @@ -222,7 +247,7 @@ export const convertFlowData = (flowData: any, useDefault = true) => { if (nodeConfig.apiUpstream && Array.isArray(nodeConfig.apiUpstream)) { nodeConfig.apiUpstream.forEach((sourceArray: string[]) => { if (Array.isArray(sourceArray)) { - sourceArray.forEach(source => { + sourceArray.forEach((source) => { if (typeof source === 'string' && source.includes('$$')) { const [sourceNodeId, sourceHandle] = source.split('$$'); const connectionKey = `${sourceNodeId}-${nodeId}`; @@ -234,17 +259,16 @@ export const convertFlowData = (flowData: any, useDefault = true) => { if (existing) { connections.set(connectionKey, { ...existing, - sourceHandle: sourceHandle + sourceHandle: sourceHandle, }); } - } - else { + } else { // 创建新的连接信息 connections.set(connectionKey, { source: sourceNodeId, target: nodeId, sourceHandle: sourceHandle, - targetHandle: '' // 将根据节点信息填充 + targetHandle: '', // 将根据节点信息填充 }); } } @@ -269,9 +293,13 @@ export const convertFlowData = (flowData: any, useDefault = true) => { if (!finalSourceHandle) { if (source === 'start') { finalSourceHandle = 'start'; - } - else if (sourceNode && sourceNode.data && sourceNode.data.parameters && - sourceNode.data.parameters.apiOuts && sourceNode.data.parameters.apiOuts.length > 0) { + } else if ( + sourceNode && + sourceNode.data && + sourceNode.data.parameters && + sourceNode.data.parameters.apiOuts && + sourceNode.data.parameters.apiOuts.length > 0 + ) { // 查找匹配的目标句柄 const matchingApiOut = sourceNode.data.parameters.apiOuts.find( (apiOut: any) => apiOut.name === targetHandle @@ -279,17 +307,18 @@ export const convertFlowData = (flowData: any, useDefault = true) => { if (matchingApiOut) { finalSourceHandle = matchingApiOut.name; - } - else { + } else { // 如果没有精确匹配,使用第一个apiOut finalSourceHandle = sourceNode.data.parameters.apiOuts[0].name; } - } - else if (sourceNode && sourceNode.component && sourceNode.component.type) { + } else if ( + sourceNode && + sourceNode.component && + sourceNode.component.type + ) { // 根据节点类型获取正确的源句柄 finalSourceHandle = getNodeApiOutHandle(source, sourceNode); - } - else { + } else { // 默认句柄 finalSourceHandle = 'done'; } @@ -301,9 +330,13 @@ export const convertFlowData = (flowData: any, useDefault = true) => { if (!finalTargetHandle) { if (target === 'end') { finalTargetHandle = 'end'; - } - else if (targetNode && targetNode.data && targetNode.data.parameters && - targetNode.data.parameters.apiIns && targetNode.data.parameters.apiIns.length > 0) { + } else if ( + targetNode && + targetNode.data && + targetNode.data.parameters && + targetNode.data.parameters.apiIns && + targetNode.data.parameters.apiIns.length > 0 + ) { // 查找匹配的源句柄 const matchingApiIn = targetNode.data.parameters.apiIns.find( (apiIn: any) => apiIn.name === sourceHandle @@ -311,13 +344,11 @@ export const convertFlowData = (flowData: any, useDefault = true) => { if (matchingApiIn) { finalTargetHandle = matchingApiIn.name; - } - else { + } else { // 如果没有精确匹配,使用第一个apiIn finalTargetHandle = targetNode.data.parameters.apiIns[0].name; } - } - else { + } else { // 默认句柄 finalTargetHandle = 'start'; } @@ -338,8 +369,8 @@ export const convertFlowData = (flowData: any, useDefault = true) => { type: 'custom', lineType: 'api', data: { - lineType: 'api' - } + lineType: 'api', + }, }); } } @@ -356,9 +387,12 @@ export const convertFlowData = (flowData: any, useDefault = true) => { // 第一个元素是源节点和句柄信息 const [sourceInfo, targetInfo] = connectionGroup; - if (typeof sourceInfo === 'string' && sourceInfo.includes('@@') && - typeof targetInfo === 'string' && targetInfo.includes('@@')) { - + if ( + typeof sourceInfo === 'string' && + sourceInfo.includes('@@') && + typeof targetInfo === 'string' && + targetInfo.includes('@@') + ) { const [sourceNodeId, sourceHandle] = sourceInfo.split('@@'); const [targetNodeId, targetHandle] = targetInfo.split('@@'); @@ -378,8 +412,8 @@ export const convertFlowData = (flowData: any, useDefault = true) => { type: 'custom', lineType: 'data', data: { - lineType: 'data' - } + lineType: 'data', + }, }); } } @@ -403,12 +437,12 @@ export const revertFlowData = (nodes: any[], edges: any[]) => { const flowData: any = { id: 'main', nodeConfigs: [], - lineConfigs: [] + lineConfigs: [], }; // 转换节点数据 if (nodes && nodes.length > 0) { - flowData.nodeConfigs = nodes.map(node => { + flowData.nodeConfigs = nodes.map((node) => { // 确定 nodeId 和 nodeName const nodeId = node.id || node.name; const nodeName = node.data?.title || nodeId; @@ -418,21 +452,20 @@ export const revertFlowData = (nodes: any[], edges: any[]) => { // 特殊处理 start 和 end 节点 if (nodeId.includes('start')) { nodeType = 'start'; - } - else if (nodeId.includes('end')) { + } else if (nodeId.includes('end')) { nodeType = 'end'; } // 构造 x6 数据(位置信息) const x6 = JSON.stringify({ - position: node.position + position: node.position, }); // 构造 nodeConfig 对象 const nodeConfig: any = { nodeId, nodeName, - x6 + x6, }; // 处理 component 信息 @@ -440,18 +473,20 @@ export const revertFlowData = (nodes: any[], edges: any[]) => { nodeConfig.component = { type: nodeType, compIdentifier: node.data.component.compIdentifier || '', - compInstanceIdentifier: node.data.component.compInstanceIdentifier || '', - compId: node.data.compId || '' + compInstanceIdentifier: + node.data.component.compInstanceIdentifier || '', + compId: node.data.compId || '', }; - if (node.data.component?.customDef) nodeConfig.component.customDef = node.data.component.customDef; - } - else if (nodeType !== 'start' && nodeType !== 'end') { + if (node.data.component?.customDef) + nodeConfig.component.customDef = node.data.component.customDef; + } else if (nodeType !== 'start' && nodeType !== 'end') { // 对于非 start/end 节点,添加基本的 component 信息 nodeConfig.component = { - type: nodeType + type: nodeType, }; } - if (['BASIC', 'SUB'].includes(nodeType)) nodeConfig.component.compId = node.data.compId || ''; + if (['BASIC', 'SUB'].includes(nodeType)) + nodeConfig.component.compId = node.data.compId || ''; // 处理参数信息 const parameters = node.data?.parameters || {}; @@ -463,7 +498,7 @@ export const revertFlowData = (nodes: any[], edges: any[]) => { desc: input.desc, dataType: input.dataType, defaultValue: input.defaultValue, - arrayType: input.arrayType || null + arrayType: input.arrayType || null, })); } @@ -474,7 +509,7 @@ export const revertFlowData = (nodes: any[], edges: any[]) => { desc: output.desc, dataType: output.dataType, defaultValue: output.defaultValue, - arrayType: output.arrayType || null + arrayType: output.arrayType || null, })); } @@ -486,22 +521,34 @@ export const revertFlowData = (nodes: any[], edges: any[]) => { if (edges && edges.length > 0) { flowData.lineConfigs = edges.map((edge, index) => { // 查找源节点和目标节点以确定连线类型 - const sourceNode = nodes.find(node => node.id === edge.source); - const targetNode = nodes.find(node => node.id === edge.target); + const sourceNode = nodes.find((node) => node.id === edge.source); + const targetNode = nodes.find((node) => node.id === edge.target); let lineType = 'DATA'; // 默认为DATA类型 // 判断是否为CONVERT类型的连线 - if (targetNode && ['JSONCONVERT', 'JSON2STR', 'STR2JSON'].includes(targetNode.type)) { + if ( + targetNode && + ['JSONCONVERT', 'JSON2STR', 'STR2JSON'].includes(targetNode.type) + ) { lineType = 'CONVERT'; } // 判断是否为API类型的连线 - else if (edge.sourceHandle && (edge.sourceHandle === 'apiOuts' || - sourceNode?.data?.parameters?.apiOuts?.some((out: any) => (out.name || out.id) === edge.sourceHandle))) { + else if ( + edge.sourceHandle && + (edge.sourceHandle === 'apiOuts' || + sourceNode?.data?.parameters?.apiOuts?.some( + (out: any) => (out.name || out.id) === edge.sourceHandle + )) + ) { lineType = 'API'; - } - else if (edge.targetHandle && (edge.targetHandle === 'apiIns' || - targetNode?.data?.parameters?.apiIns?.some((inp: any) => (inp.name || inp.id) === edge.targetHandle))) { + } else if ( + edge.targetHandle && + (edge.targetHandle === 'apiIns' || + targetNode?.data?.parameters?.apiIns?.some( + (inp: any) => (inp.name || inp.id) === edge.targetHandle + )) + ) { lineType = 'API'; } @@ -510,12 +557,12 @@ export const revertFlowData = (nodes: any[], edges: any[]) => { lineType, // 添加lineType属性 prev: { nodeId: edge.source, - endpointId: edge.sourceHandle || 'done' // 默认使用 'done' + endpointId: edge.sourceHandle || 'done', // 默认使用 'done' }, next: { nodeId: edge.target, - endpointId: edge.targetHandle || 'start' // 默认使用 'start' - } + endpointId: edge.targetHandle || 'start', // 默认使用 'start' + }, }; }); } @@ -530,20 +577,24 @@ export const revertFlowData = (nodes: any[], edges: any[]) => { * @param complexKV - 复合组件使用的组件id对照表 {数字ID/nodeId:sub_ID} * @returns 可用于 convertFlowData 的数据结构 */ -export const reverseConvertFlowData = (nodes: any[], edges: any[], complexKV: any) => { +export const reverseConvertFlowData = ( + nodes: any[], + edges: any[], + complexKV: any +) => { // 初始化返回的数据结构 const flowData: any = {}; // 转换节点数据 if (nodes && nodes.length > 0) { - nodes.forEach(node => { + nodes.forEach((node) => { const nodeId = node.id; // 构造节点配置对象 const nodeConfig: any = { id: nodeId, componentName: node.data?.title || nodeId, - position: node.position || { x: 0, y: 0 } + position: node.position || { x: 0, y: 0 }, }; // 处理 component 信息 @@ -570,16 +621,14 @@ export const reverseConvertFlowData = (nodes: any[], edges: any[], complexKV: an dataIns: node.data.parameters.dataIns, dataOuts: node.data.parameters.dataOuts, subflowId: subflowId, - name: node.data.title - }) + name: node.data.title, + }), }; - } - else if (node.data?.component) { + } else if (node.data?.component) { nodeConfig.component = { ...node.data.component }; - } - else { + } else { nodeConfig.component = { - type: node.type + type: node.type, }; } @@ -589,32 +638,28 @@ export const reverseConvertFlowData = (nodes: any[], edges: any[], complexKV: an // 处理 apiIns(输入API) if (parameters.apiIns && parameters.apiIns.length > 0) { nodeConfig.apiIns = parameters.apiIns; - } - else { + } else { nodeConfig.apiIns = []; } // 处理 apiOuts(输出API) if (parameters.apiOuts && parameters.apiOuts.length > 0) { nodeConfig.apiOuts = parameters.apiOuts; - } - else { + } else { nodeConfig.apiOuts = []; } // 处理 dataIns(输入数据) if (parameters.dataIns && parameters.dataIns.length > 0) { nodeConfig.dataIns = parameters.dataIns; - } - else { + } else { nodeConfig.dataIns = []; } // 处理 dataOuts(输出数据) if (parameters.dataOuts && parameters.dataOuts.length > 0) { nodeConfig.dataOuts = parameters.dataOuts; - } - else { + } else { nodeConfig.dataOuts = []; } @@ -632,33 +677,45 @@ export const reverseConvertFlowData = (nodes: any[], edges: any[], complexKV: an // 处理连接关系 if (edges && edges.length > 0) { // 分析边的连接关系 - edges.forEach(edge => { + edges.forEach((edge) => { const sourceNode = edge.source; const targetNode = edge.target; const sourceHandle = edge.sourceHandle || 'done'; const targetHandle = edge.targetHandle || 'start'; // 确定连接类型(API 还是 DATA) - const sourceNodeData = nodes.find(n => n.id === sourceNode); - const targetNodeData = nodes.find(n => n.id === targetNode); + const sourceNodeData = nodes.find((n) => n.id === sourceNode); + const targetNodeData = nodes.find((n) => n.id === targetNode); const isApiConnection = - (sourceNodeData?.data?.parameters?.apiOuts?.some((out: any) => (out.name || out.id) === sourceHandle)) || - (targetNodeData?.data?.parameters?.apiIns?.some((inp: any) => (inp.name || inp.id) === targetHandle)) || - sourceHandle === 'start' || targetHandle === 'end' || - sourceHandle === 'end' || targetHandle === 'start'; + sourceNodeData?.data?.parameters?.apiOuts?.some( + (out: any) => (out.name || out.id) === sourceHandle + ) || + targetNodeData?.data?.parameters?.apiIns?.some( + (inp: any) => (inp.name || inp.id) === targetHandle + ) || + sourceHandle === 'start' || + targetHandle === 'end' || + sourceHandle === 'end' || + targetHandle === 'start'; if (isApiConnection) { // API 连接 // 添加下游连接 - flowData[sourceNode].apiDownstream.push([`${targetNode}$$${targetHandle}`]); + flowData[sourceNode].apiDownstream.push([ + `${targetNode}$$${targetHandle}`, + ]); // 添加上游连接 - flowData[targetNode].apiUpstream.push([`${sourceNode}$$${sourceHandle}`]); - } - else { + flowData[targetNode].apiUpstream.push([ + `${sourceNode}$$${sourceHandle}`, + ]); + } else { // 数据连接 - const dataConnection = [`${sourceNode}@@${sourceHandle}`, `${targetNode}@@${targetHandle}`]; + const dataConnection = [ + `${sourceNode}@@${sourceHandle}`, + `${targetNode}@@${targetHandle}`, + ]; flowData[sourceNode].dataDownstream.push(dataConnection); flowData[targetNode].dataUpstream.push(dataConnection); } @@ -669,59 +726,67 @@ export const reverseConvertFlowData = (nodes: any[], edges: any[], complexKV: an }; // 获取节点的API输入参数 -const getNodeApiIns = (nodeId: string, nodeConfig: any, currentProjectCompData: any[]) => { +const getNodeApiIns = ( + nodeId: string, + nodeConfig: any, + currentProjectCompData: any[] +) => { // JSON2STR 和 STR2JSON 不需要 API 输入 - if (nodeConfig.component?.type === 'JSON2STR' || nodeConfig.component?.type === 'STR2JSON') { + if ( + nodeConfig.component?.type === 'JSON2STR' || + nodeConfig.component?.type === 'STR2JSON' + ) { return []; } // 对于特定类型的节点使用预定义值 if (nodeConfig.component?.type === 'LOOP_START') { return [{ name: 'start', desc: '', dataType: '', defaultValue: '' }]; - } - else if (nodeConfig.component?.type === 'LOOP_END') { + } else if (nodeConfig.component?.type === 'LOOP_END') { return [{ name: 'continue', desc: '', dataType: '', defaultValue: '' }]; - } - else if (nodeId.includes('end')) { + } else if (nodeId.includes('end')) { return [{ name: 'end', desc: '', dataType: '', defaultValue: '' }]; - } - else if (nodeConfig.component?.type === 'SUB') { + } else if (nodeConfig.component?.type === 'SUB') { return [{ name: 'start', desc: '', dataType: '', defaultValue: '' }]; - } - else { - const comp = currentProjectCompData.filter(item => { + } else { + const comp = currentProjectCompData.filter((item) => { return (item.id || item?.comp?.id) === nodeConfig?.component?.compId; }); if (comp && comp.length > 0) { const apiIns = comp[0]?.def?.apis || comp[0]?.comp?.def?.apis || []; - return apiIns.map(v => { + return apiIns.map((v) => { return { ...v, name: v.id, desc: v.desc, dataType: v?.dataType || '', - defaultValue: v?.defaultValue || '' + defaultValue: v?.defaultValue || '', }; }); - } - else { + } else { return [{ name: 'start', desc: '', dataType: '', defaultValue: '' }]; } } }; // 获取节点的API输出参数 -const getNodeApiOuts = (nodeId: string, nodeConfig: any, currentProjectCompData: any[]) => { +const getNodeApiOuts = ( + nodeId: string, + nodeConfig: any, + currentProjectCompData: any[] +) => { // JSON2STR 和 STR2JSON 不需要 API 输出 - if (nodeConfig.component?.type === 'JSON2STR' || nodeConfig.component?.type === 'STR2JSON') { + if ( + nodeConfig.component?.type === 'JSON2STR' || + nodeConfig.component?.type === 'STR2JSON' + ) { return []; } // 对于特定类型的节点使用预定义值 if (nodeConfig.component?.type === 'LOOP_START') { return [{ name: 'done', desc: '', dataType: '', defaultValue: '' }]; - } - else if (nodeConfig.component?.type === 'LOOP_END') { + } else if (nodeConfig.component?.type === 'LOOP_END') { // 从customDef中获取apiOutIds数组 try { const customDef = JSON.parse(nodeConfig.component?.customDef || '{}'); @@ -731,15 +796,14 @@ const getNodeApiOuts = (nodeId: string, nodeConfig: any, currentProjectCompData: const breakIndex = apiOutIds.indexOf('break'); if (breakIndex !== -1) { // 返回从"break"开始的所有项 - return apiOutIds.slice(breakIndex).map(id => ({ + return apiOutIds.slice(breakIndex).map((id) => ({ name: id, id: id, desc: id, dataType: '', - defaultValue: '' + defaultValue: '', })); - } - else { + } else { // 如果没有找到"break",则返回默认值 return [{ name: 'break', desc: '', dataType: '', defaultValue: '' }]; } @@ -747,8 +811,7 @@ const getNodeApiOuts = (nodeId: string, nodeConfig: any, currentProjectCompData: // 解析失败时返回默认值 return [{ name: 'break', desc: '', dataType: '', defaultValue: '' }]; } - } - else if (nodeConfig.component?.type === 'SWITCH') { + } else if (nodeConfig.component?.type === 'SWITCH') { // 从customDef中获取apiOutIds数组 try { const customDef = JSON.parse(nodeConfig.component?.customDef || '{}'); @@ -758,15 +821,14 @@ const getNodeApiOuts = (nodeId: string, nodeConfig: any, currentProjectCompData: const breakIndex = apiOutIds.indexOf('default'); if (breakIndex !== -1) { // 返回从"break"开始的所有项 - return apiOutIds.slice(breakIndex).map(id => ({ + return apiOutIds.slice(breakIndex).map((id) => ({ name: id, id: id, desc: id, dataType: '', - defaultValue: '' + defaultValue: '', })); - } - else { + } else { // 如果没有找到"break",则返回默认值 return [{ name: 'default', desc: '', dataType: '', defaultValue: '' }]; } @@ -774,26 +836,25 @@ const getNodeApiOuts = (nodeId: string, nodeConfig: any, currentProjectCompData: // 解析失败时返回默认值 return [{ name: 'done', desc: '', dataType: '', defaultValue: '' }]; } - } - else if (nodeId.includes('start')) { + } else if (nodeId.includes('start')) { return [{ name: 'start', desc: '', dataType: '', defaultValue: '' }]; - } - else if (nodeId.includes('end')) { + } else if (nodeId.includes('end')) { return []; - } - else if (nodeConfig.component?.type === 'SUB') { + } else if (nodeConfig.component?.type === 'SUB') { return [{ name: 'done', desc: '', dataType: '', defaultValue: '' }]; - } - else { - const comp = currentProjectCompData.filter(item => item.id === nodeConfig?.component?.compId); + } else { + const comp = currentProjectCompData.filter( + (item) => item.id === nodeConfig?.component?.compId + ); if (comp && comp.length > 0) { - return [{ - ...comp[0].def?.apiOut, - dataType: '', - defaultValue: '' - }]; - } - else { + return [ + { + ...comp[0].def?.apiOut, + dataType: '', + defaultValue: '', + }, + ]; + } else { return [{ name: 'done', desc: '', dataType: '', defaultValue: '' }]; } } @@ -803,14 +864,11 @@ const getNodeApiOuts = (nodeId: string, nodeConfig: any, currentProjectCompData: const getNodeApiOutHandle = (nodeId: string, nodeConfig: any) => { if (nodeConfig.component?.type === 'LOOP_START') { return 'done'; - } - else if (nodeConfig.component?.type === 'LOOP_END') { + } else if (nodeConfig.component?.type === 'LOOP_END') { return 'break'; - } - else if (nodeId.includes('start')) { + } else if (nodeId.includes('start')) { return 'start'; - } - else if (nodeId.includes('end')) { + } else if (nodeId.includes('end')) { return 'end'; } return 'done'; @@ -825,13 +883,17 @@ const getCurrentProjectStoreData = () => { // 处理projectCompDto中的数据 if (compData.projectCompDto) { - const { mineComp = [], pubComp = [], teamWorkComp = [] } = compData.projectCompDto; + const { + mineComp = [], + pubComp = [], + teamWorkComp = [], + } = compData.projectCompDto; // 添加mineComp数据 mineComp.forEach((item: any) => { result.push({ ...item, - type: 'mineComp' + type: 'mineComp', }); }); @@ -839,7 +901,7 @@ const getCurrentProjectStoreData = () => { pubComp.forEach((item: any) => { result.push({ ...item, - type: 'pubComp' + type: 'pubComp', }); }); @@ -847,7 +909,7 @@ const getCurrentProjectStoreData = () => { teamWorkComp.forEach((item: any) => { result.push({ ...item, - type: 'teamWorkComp' + type: 'teamWorkComp', }); }); } @@ -860,7 +922,7 @@ const getCurrentProjectStoreData = () => { mineFlow.forEach((item: any) => { result.push({ ...item, - type: 'mineFlow' + type: 'mineFlow', }); }); @@ -868,7 +930,7 @@ const getCurrentProjectStoreData = () => { pubFlow.forEach((item: any) => { result.push({ ...item, - type: 'pubFlow' + type: 'pubFlow', }); }); } @@ -881,7 +943,13 @@ const getCurrentProjectStoreData = () => { const compLibsData = sessionStorage.getItem(compLibsKey); if (compLibsData) { - const { myLibs = [], pubLibs = [], teamLibs = [], myFlow = [], pubFlow = [] } = JSON.parse(compLibsData); + const { + myLibs = [], + pubLibs = [], + teamLibs = [], + myFlow = [], + pubFlow = [], + } = JSON.parse(compLibsData); // 处理 myLibs(我的组件库) if (Array.isArray(myLibs)) { @@ -890,7 +958,7 @@ const getCurrentProjectStoreData = () => { lib.children.forEach((item: any) => { result.push({ ...item, - type: 'mineComp' + type: 'mineComp', }); }); } @@ -904,7 +972,7 @@ const getCurrentProjectStoreData = () => { lib.children.forEach((item: any) => { result.push({ ...item, - type: 'pubComp' + type: 'pubComp', }); }); } @@ -918,7 +986,7 @@ const getCurrentProjectStoreData = () => { lib.children.forEach((item: any) => { result.push({ ...item, - type: 'teamWorkComp' + type: 'teamWorkComp', }); }); } @@ -930,7 +998,7 @@ const getCurrentProjectStoreData = () => { myFlow.forEach((item: any) => { result.push({ ...item, - type: 'mineFlow' + type: 'mineFlow', }); }); } @@ -940,7 +1008,7 @@ const getCurrentProjectStoreData = () => { pubFlow.forEach((item: any) => { result.push({ ...item, - type: 'pubFlow' + type: 'pubFlow', }); }); } @@ -951,22 +1019,3 @@ const getCurrentProjectStoreData = () => { } return result; }; - -// 根据节点类型获取对应的节点组件 -const getNodeComponent = (nodeType: string) => { - switch (nodeType) { - case 'BASIC': - case 'SUB': - return BasicNode; - case 'SWITCH': - return SwitchNode; - case 'IMAGE': - return ImageNode; - case 'CODE': - return CodeNode; - case 'REST': - return RestNode; - default: - return LocalNode; - } -}; \ No newline at end of file diff --git a/src/utils/flow/edgeInsertionFactory.ts b/src/utils/flow/edgeInsertionFactory.ts new file mode 100644 index 0000000..51c9fbd --- /dev/null +++ b/src/utils/flow/edgeInsertionFactory.ts @@ -0,0 +1,54 @@ +import { Edge } from '@xyflow/react'; + +export const resolveInsertedNodeHandles = (node: any) => { + let sourceHandle = 'done'; + let targetHandle = 'start'; + + if (node?.data?.parameters) { + const { apiOuts, apiIns } = node.data.parameters; + + if (apiOuts && apiOuts.length > 0) { + sourceHandle = apiOuts[0].name || apiOuts[0].id || sourceHandle; + } + + if (apiIns && apiIns.length > 0) { + targetHandle = apiIns[0].name || apiIns[0].id || targetHandle; + } + } + + return { sourceHandle, targetHandle }; +}; + +export const buildInsertedNodeEdges = (params: { + baseEdges: Edge[]; + removedEdgeId: string; + sourceId: string; + sourceHandle?: string; + targetId: string; + targetHandle?: string; + insertedNodeId: string; + insertedNodeSourceHandle: string; + insertedNodeTargetHandle: string; +}): Edge[] => [ + ...params.baseEdges.filter((e) => e.id !== params.removedEdgeId), + { + id: `e${params.sourceId}-${params.insertedNodeId}`, + source: params.sourceId, + target: params.insertedNodeId, + sourceHandle: params.sourceHandle, + targetHandle: params.insertedNodeTargetHandle, + type: 'custom', + lineType: 'api', + data: { lineType: 'api' }, + } as Edge, + { + id: `e${params.insertedNodeId}-${params.targetId}`, + source: params.insertedNodeId, + target: params.targetId, + sourceHandle: params.insertedNodeSourceHandle, + targetHandle: params.targetHandle, + type: 'custom', + lineType: 'api', + data: { lineType: 'api' }, + } as Edge, +]; diff --git a/src/utils/flow/loopFactory.ts b/src/utils/flow/loopFactory.ts new file mode 100644 index 0000000..f33b81c --- /dev/null +++ b/src/utils/flow/loopFactory.ts @@ -0,0 +1,104 @@ +import { Edge } from '@xyflow/react'; + +export const createLoopNodePair = (position: { x: number; y: number }) => { + const loopStartNode: any = { + id: `LOOP_START-${Date.now()}`, + type: 'LOOP', + position: { x: position.x, y: position.y }, + data: { + title: '循环开始', + type: 'LOOP_START', + parameters: { + apiIns: [{ name: 'start', desc: '', dataType: '', defaultValue: '' }], + apiOuts: [{ name: 'done', desc: '', dataType: '', defaultValue: '' }], + dataIns: [], + dataOuts: [], + }, + component: {}, + }, + }; + + const loopEndNode: any = { + id: `LOOP_END-${Date.now()}`, + type: 'LOOP', + position: { x: position.x + 400, y: position.y }, + data: { + title: '循环结束', + type: 'LOOP_END', + parameters: { + apiIns: [ + { name: 'continue', desc: '', dataType: '', defaultValue: '' }, + ], + apiOuts: [{ name: 'break', desc: '', dataType: '', defaultValue: '' }], + dataIns: [ + { + arrayType: null, + dataType: 'INTEGER', + defaultValue: 10, + desc: '最大循环次数', + id: 'maxTime', + }, + ], + dataOuts: [], + }, + component: { + type: 'LOOP_END', + customDef: JSON.stringify({ + apiOutIds: ['continue', 'break'], + conditions: [], + loopStartNodeId: loopStartNode.id, + }), + loopStartNodeId: loopStartNode.id, + }, + }, + }; + + loopStartNode.data.component = { + type: 'LOOP_START', + customDef: JSON.stringify({ loopEndNodeId: loopEndNode.id }), + }; + + return { loopStartNode, loopEndNode }; +}; + +export const createLoopGroupEdge = ( + loopStartId: string, + loopEndId: string +): Edge => ({ + id: `${loopStartId}-${loopEndId}-group`, + source: loopStartId, + target: loopEndId, + sourceHandle: `${loopStartId}-group`, + targetHandle: `${loopEndId}-group`, + type: 'custom', +}); + +export const createLoopInsertConnectionEdges = (params: { + sourceId: string; + sourceHandle: string; + targetId: string; + targetHandle: string; + loopStartId: string; + loopEndId: string; +}): Edge[] => [ + { + id: `e${params.sourceId}-${params.loopStartId}`, + source: params.sourceId, + target: params.loopStartId, + sourceHandle: params.sourceHandle, + targetHandle: 'start', + type: 'custom', + lineType: 'api', + data: { lineType: 'api' }, + } as Edge, + { + id: `e${params.loopEndId}-${params.targetId}`, + source: params.loopEndId, + target: params.targetId, + sourceHandle: 'break', + targetHandle: params.targetHandle, + type: 'custom', + lineType: 'api', + data: { lineType: 'api' }, + } as Edge, +]; diff --git a/src/utils/flow/nodeFactory.ts b/src/utils/flow/nodeFactory.ts new file mode 100644 index 0000000..f592791 --- /dev/null +++ b/src/utils/flow/nodeFactory.ts @@ -0,0 +1,79 @@ +import { Node } from '@xyflow/react'; + +interface FlowNodeDefinition { + nodeName: string; + data: any; + id?: string; + flowHousVO?: { + id?: string; + }; +} + +type EventIdMode = 'id' | 'eventIdOptional'; + +export const createFlowNode = ( + nodeType: string, + nodeDefinition: FlowNodeDefinition, + position: { x: number; y: number } +): Node => { + const node: any = { + id: `${nodeType}-${Date.now()}`, + type: nodeType, + position, + data: { + ...nodeDefinition.data, + title: nodeDefinition.nodeName, + type: nodeType, + }, + }; + + if (nodeDefinition.id || nodeDefinition?.flowHousVO?.id) { + node.data.compId = nodeDefinition.id || nodeDefinition?.flowHousVO?.id; + } + + return node; +}; + +export const attachFlowNodeComponent = ( + node: any, + nodeType: string, + nodeDefinition: FlowNodeDefinition, + eventList: any[], + eventIdMode: EventIdMode +) => { + if (nodeType === 'SWITCH') { + node.data.component = { + customDef: JSON.stringify({ + apiOutIds: ['default'], + conditions: [], + }), + }; + } else if (nodeType === 'SUB') { + node.data.component = { + type: nodeType, + compId: node.data.compId, + }; + } else if (nodeType === 'EVENTSEND' || nodeType === 'EVENTLISTENE') { + const emptyEvent = eventList.find((item) => + item.topic.includes('**empty**') + ); + node.data.component = { + type: nodeType, + customDef: { + eventId: + eventIdMode === 'eventIdOptional' + ? emptyEvent?.eventId ?? null + : emptyEvent.id, + name: emptyEvent.name, + topic: emptyEvent.topic, + }, + }; + } else { + node.data.component = { + type: nodeType, + compId: nodeDefinition.id, + }; + } + + return node; +}; diff --git a/src/utils/flow/nodeOnboarding.ts b/src/utils/flow/nodeOnboarding.ts new file mode 100644 index 0000000..5c2a8ad --- /dev/null +++ b/src/utils/flow/nodeOnboarding.ts @@ -0,0 +1,61 @@ +import { + createFlowNode, + attachFlowNodeComponent, +} from '@/utils/flow/nodeFactory'; +import { + ensureNodeTypeRegistered, + resolveNodeComponent, +} from '@/utils/flow/nodeRegistry'; + +type EventIdMode = 'id' | 'eventIdOptional'; + +export const resolveNodeDefinition = ( + nodeList: any[], + nodeType: string, + fallbackNode?: any +) => { + return nodeList.find((n) => n.nodeType === nodeType) || fallbackNode || null; +}; + +export const buildRuntimeNode = (params: { + nodeType: string; + nodeDefinition: any; + position: { x: number; y: number }; + eventList: any[]; + eventIdMode: EventIdMode; +}) => { + const { nodeType, nodeDefinition, position, eventList, eventIdMode } = params; + + const newNode = attachFlowNodeComponent( + createFlowNode(nodeType, nodeDefinition, position), + nodeType, + nodeDefinition, + eventList, + eventIdMode + ); + + ensureNodeTypeRegistered( + nodeType, + nodeDefinition.nodeName, + resolveNodeComponent(nodeType) + ); + + return newNode; +}; + +export const buildDroppedNode = ( + nodeData: any, + position: { x: number; y: number } +) => { + ensureNodeTypeRegistered(nodeData.nodeType, nodeData.nodeName); + return { + id: `${nodeData.nodeType}-${Date.now()}`, + type: nodeData.nodeType, + position, + data: { + ...nodeData.data, + title: nodeData.nodeName, + type: nodeData.nodeType, + }, + }; +}; diff --git a/src/utils/flow/nodeRegistry.ts b/src/utils/flow/nodeRegistry.ts new file mode 100644 index 0000000..7a67ecf --- /dev/null +++ b/src/utils/flow/nodeRegistry.ts @@ -0,0 +1,40 @@ +import BasicNode from '@/components/FlowEditor/node/basicNode/BasicNode'; +import SwitchNode from '@/components/FlowEditor/node/switchNode/SwitchNode'; +import ImageNode from '@/components/FlowEditor/node/imageNode/ImageNode'; +import CodeNode from '@/components/FlowEditor/node/codeNode/CodeNode'; +import RestNode from '@/components/FlowEditor/node/restNode/RestNode'; +import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode'; +import { nodeTypeMap, registerNodeType } from '@/components/FlowEditor/node'; + +export const resolveNodeComponent = (nodeType: string) => { + switch (nodeType) { + case 'BASIC': + case 'SUB': + return BasicNode; + case 'SWITCH': + return SwitchNode; + case 'IMAGE': + return ImageNode; + case 'CODE': + return CodeNode; + case 'REST': + return RestNode; + default: + return LocalNode; + } +}; + +export const ensureNodeTypeRegistered = ( + nodeType: string, + nodeName: string, + component?: any +) => { + const nodeMap = Array.from(Object.values(nodeTypeMap).map((key) => key)); + if (!nodeMap.includes(nodeType)) { + registerNodeType( + nodeType, + component || resolveNodeComponent(nodeType), + nodeName + ); + } +}; diff --git a/src/utils/flow/runtime.ts b/src/utils/flow/runtime.ts new file mode 100644 index 0000000..0505e5b --- /dev/null +++ b/src/utils/flow/runtime.ts @@ -0,0 +1,34 @@ +export interface FlowCurrentAppData { + id?: string; + key?: string; +} + +export interface AppRuntimeState { + nodeStatusMap: Record; + isRunning: boolean; + isPaused: boolean; + logs: any[]; + runId: string; + eventSendNodeList: any[]; + eventlisteneList: any[]; +} + +export const getCurrentAppKey = ( + currentAppData: FlowCurrentAppData | null | undefined +) => { + if (!currentAppData) return null; + if (currentAppData.key && currentAppData.key.includes('sub')) { + return currentAppData.key; + } + return currentAppData.id || null; +}; + +export const createDefaultAppRuntimeState = (): AppRuntimeState => ({ + nodeStatusMap: {}, + isRunning: false, + isPaused: false, + logs: [], + runId: '', + eventSendNodeList: [], + eventlisteneList: [], +}); diff --git a/src/utils/flow/snapshot.ts b/src/utils/flow/snapshot.ts new file mode 100644 index 0000000..acc4e49 --- /dev/null +++ b/src/utils/flow/snapshot.ts @@ -0,0 +1,22 @@ +import { Edge, Node } from '@xyflow/react'; + +interface FlowSnapshotDetail { + nodes: Node[]; + edges: Edge[]; +} + +export const dispatchFlowSnapshot = (detail: FlowSnapshotDetail) => { + const event = new CustomEvent('takeSnapshot', { + detail, + }); + document.dispatchEvent(event); +}; + +export const dispatchFlowSnapshotAsync = ( + detail: FlowSnapshotDetail, + delay = 0 +) => { + return setTimeout(() => { + dispatchFlowSnapshot(detail); + }, delay); +}; diff --git a/src/utils/flowCommon.ts b/src/utils/flowCommon.ts index 5f1c4a0..e8de188 100644 --- a/src/utils/flowCommon.ts +++ b/src/utils/flowCommon.ts @@ -1,10 +1,5 @@ import { defaultNodeTypes } from '@/components/FlowEditor/node/types/defaultType'; -import BasicNode from '@/components/FlowEditor/node/basicNode/BasicNode'; -import SwitchNode from '@/components/FlowEditor/node/switchNode/SwitchNode'; -import ImageNode from '@/components/FlowEditor/node/imageNode/ImageNode'; -import CodeNode from '@/components/FlowEditor/node/codeNode/CodeNode'; -import RestNode from '@/components/FlowEditor/node/restNode/RestNode'; -import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode'; +import { resolveNodeComponent } from '@/utils/flow/nodeRegistry'; // 获取handle类型 (api或data) const getHandleType = (handleId: string, nodeParams: any) => { @@ -12,8 +7,15 @@ const getHandleType = (handleId: string, nodeParams: any) => { const apiOuts = nodeParams.apiOuts || []; const apiIns = nodeParams.apiIns || []; - if (apiOuts.some((api: any) => (api?.eventId || api.name || api.id) === handleId) || - apiIns.some((api: any) => (api?.eventId || api.name || api.id) === handleId) || (handleId.includes('loop'))) { + if ( + apiOuts.some( + (api: any) => (api?.eventId || api.name || api.id) === handleId + ) || + apiIns.some( + (api: any) => (api?.eventId || api.name || api.id) === handleId + ) || + handleId.includes('loop') + ) { return 'api'; } @@ -21,8 +23,10 @@ const getHandleType = (handleId: string, nodeParams: any) => { const dataOuts = nodeParams.dataOuts || []; const dataIns = nodeParams.dataIns || []; - if (dataOuts.some((data: any) => (data.name || data.id) === handleId) || - dataIns.some((data: any) => (data.name || data.id) === handleId)) { + if ( + dataOuts.some((data: any) => (data.name || data.id) === handleId) || + dataIns.some((data: any) => (data.name || data.id) === handleId) + ) { return 'data'; } @@ -31,7 +35,12 @@ const getHandleType = (handleId: string, nodeParams: any) => { }; // 验证数据类型是否匹配 -const validateDataType = (sourceNode: defaultNodeTypes, targetNode: defaultNodeTypes, sourceHandleId: string, targetHandleId: string) => { +const validateDataType = ( + sourceNode: defaultNodeTypes, + targetNode: defaultNodeTypes, + sourceHandleId: string, + targetHandleId: string +) => { const sourceParams = sourceNode.data?.parameters || {}; const targetParams = targetNode.data?.parameters || {}; @@ -41,13 +50,16 @@ const validateDataType = (sourceNode: defaultNodeTypes, targetNode: defaultNodeT const sourceDataOuts = sourceParams.dataOuts || []; // 查找源handle的数据类型 - const sourceApi = sourceApiOuts.find((api: any) => api.name === sourceHandleId); - const sourceData = sourceDataOuts.find((data: any) => data.name === sourceHandleId); + 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) { + } else if (sourceData) { sourceDataType = sourceData.dataType || ''; } @@ -57,13 +69,16 @@ const validateDataType = (sourceNode: defaultNodeTypes, targetNode: defaultNodeT const targetDataIns = targetParams.dataIns || []; // 查找目标handle的数据类型 - const targetApi = targetApiIns.find((api: any) => api.name === targetHandleId); - const targetData = targetDataIns.find((data: any) => data.name === targetHandleId); + 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) { + } else if (targetData) { targetDataType = targetData.dataType || ''; } @@ -76,29 +91,9 @@ const validateDataType = (sourceNode: defaultNodeTypes, targetNode: defaultNodeT return sourceDataType === targetDataType; }; - // 根据节点类型获取对应的节点组件 const getNodeComponent = (nodeType: string) => { - switch (nodeType) { - case 'BASIC': - case 'SUB': - return BasicNode; - case 'SWITCH': - return SwitchNode; - case 'IMAGE': - return ImageNode; - case 'CODE': - return CodeNode; - case 'REST': - return RestNode; - default: - return LocalNode; - } + return resolveNodeComponent(nodeType); }; - -export { - getHandleType, - validateDataType, - getNodeComponent -}; \ No newline at end of file +export { getHandleType, validateDataType, getNodeComponent };