From 4a0e60026c667439335a76c0398ff448352a77ca Mon Sep 17 00:00:00 2001 From: ZLY Date: Thu, 30 Oct 2025 10:16:17 +0800 Subject: [PATCH 01/18] =?UTF-8?q?refactor(utils):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E8=8A=82=E7=82=B9=E5=90=8D=E7=A7=B0=E6=98=A0?= =?UTF-8?q?=E5=B0=84=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/convertAppFlowData.ts | 10 ++++++---- src/utils/convertFlowData.ts | 1 - 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/utils/convertAppFlowData.ts b/src/utils/convertAppFlowData.ts index 7fc6e0d..f6aeef3 100644 --- a/src/utils/convertAppFlowData.ts +++ b/src/utils/convertAppFlowData.ts @@ -33,21 +33,23 @@ export const convertAppFlowData = (appFlowData: any[]) => { parameters: { // eventListenes 作为 apiIns(输入) apiIns: app.eventListenes ? app.eventListenes.map((event: any) => ({ - name: event.eventName, + name: event.nodeName, desc: event.description || '', dataType: '', defaultValue: '', topic: event.topic, - eventId: event.eventId + eventId: event.eventId, + eventName: event.eventName })) : [], // eventSends 作为 apiOuts(输出) apiOuts: app.eventSends ? app.eventSends.map((event: any) => ({ - name: event.eventName, + name: event.nodeName, desc: event.description || '', dataType: '', defaultValue: '', topic: event.topic, - eventId: event.eventId + eventId: event.eventId, + eventName: event.eventName })) : [], // 提取 dataIns 和 dataOuts 属性 dataIns: [], diff --git a/src/utils/convertFlowData.ts b/src/utils/convertFlowData.ts index c49dff8..c9a4154 100644 --- a/src/utils/convertFlowData.ts +++ b/src/utils/convertFlowData.ts @@ -492,7 +492,6 @@ export const reverseConvertFlowData = (nodes: any[], edges: any[], complexKV: an position: node.position || { x: 0, y: 0 } }; - console.log('node:', node); // 处理 component 信息 if (node.type === 'SUB' && !node.customDef) { nodeConfig.component = { From f67ed129c0bbc28c53172ed78e0d8131876d7d79 Mon Sep 17 00:00:00 2001 From: ZLY Date: Thu, 30 Oct 2025 10:23:25 +0800 Subject: [PATCH 02/18] =?UTF-8?q?feat(FlowEditor):=20=E9=AA=8C=E8=AF=81BAS?= =?UTF-8?q?IC=E8=8A=82=E7=82=B9=E7=BB=84=E4=BB=B6=E5=AE=9E=E4=BE=8B?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FlowEditor/nodeEditors/validators/nodeValidators.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/FlowEditor/nodeEditors/validators/nodeValidators.ts b/src/components/FlowEditor/nodeEditors/validators/nodeValidators.ts index 03fb8b8..82a62f7 100644 --- a/src/components/FlowEditor/nodeEditors/validators/nodeValidators.ts +++ b/src/components/FlowEditor/nodeEditors/validators/nodeValidators.ts @@ -313,6 +313,15 @@ const validateCycleNode = (nodeData: any): string[] => { const validateBasicParams = (nodeData: any): string[] => { const errors: string[] = []; + // 检查BASIC节点是否有关联的组件实例 + if (nodeData.type === 'BASIC') { + // 检查节点是否具有component属性和customDef属性 + console.log('nodeData:', nodeData); + if (!nodeData.component || !nodeData.component.compIdentifier) { + errors.push('基础节点缺少组件实例配置'); + } + } + // 检查输入参数的完整性 if (nodeData.parameters?.dataIns) { nodeData.parameters.dataIns.forEach((param: any, index: number) => { From 3232a2e08ff417d7801d985f0fc0f564a0aa5420 Mon Sep 17 00:00:00 2001 From: ZLY Date: Thu, 30 Oct 2025 10:33:52 +0800 Subject: [PATCH 03/18] =?UTF-8?q?feat(flow):=20=E5=BA=94=E7=94=A8=E7=BC=96?= =?UTF-8?q?=E6=8E=92=E6=B7=BB=E5=8A=A0=E4=BF=9D=E5=AD=98=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E5=92=8C=E5=A4=B1=E8=B4=A5=E7=9A=84=E6=8F=90=E7=A4=BA=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useFlowCallbacks.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/hooks/useFlowCallbacks.ts b/src/hooks/useFlowCallbacks.ts index e08d838..f0bc3dd 100644 --- a/src/hooks/useFlowCallbacks.ts +++ b/src/hooks/useFlowCallbacks.ts @@ -1243,13 +1243,19 @@ export const useFlowCallbacks = ( ...item, eventId: Array.from(new Set(item.eventId)) })); - - updateAppFlowData(appFlowParams); - if (appEventParams.length > 0) { - for (const item of appEventParams) { - await sleep(500); - updateAppEventChannel(item); + try { + updateAppFlowData(appFlowParams); + if (appEventParams.length > 0) { + for (const item of appEventParams) { + await sleep(500); + await updateAppEventChannel(item); + } } + + Message.success('保存成功'); + } catch (error) { + console.error('保存失败:', error); + Message.error('保存失败: ' + (error.message)); } } }, [nodes, edges, initialData?.appId]); From 2e26666c03b135abb84b6810ac67be4d8d660a60 Mon Sep 17 00:00:00 2001 From: ZLY Date: Thu, 30 Oct 2025 11:04:01 +0800 Subject: [PATCH 04/18] =?UTF-8?q?feat(flowEditor):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E8=BF=90=E8=A1=8C=E7=8A=B6=E6=80=81=E9=9A=94?= =?UTF-8?q?=E7=A6=BB=E4=B8=8E=E8=BF=90=E8=A1=8C=E6=97=A5=E5=BF=97=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 ideContainer 状态中增加 appRuntimeData 字段,用于按应用 ID 隔离存储运行状态 - 修改 FlowEditorMain 和 actionBar 组件,使用当前应用的独立运行状态控制界面交互 - 更新节点拖拽、连接、删除等操作的禁用逻辑,基于当前应用运行状态判断 - 在 logBar 中实现运行日志的分应用存储与展示功能 - 添加 addRuntimeLog 和 clearRuntimeLogs actions 用于管理各应用的运行日志- 优化 useFlowEditorState 和 useFlowCallbacks 钩子以支持新的状态结构 - 确保在应用启动时清空对应应用的历史运行日志 --- src/hooks/useFlowCallbacks.ts | 7 +- src/hooks/useFlowEditorState.ts | 13 ++-- src/pages/flowEditor/FlowEditorMain.tsx | 63 ++++++++++-------- src/pages/flowEditor/components/actionBar.tsx | 13 ++-- src/pages/ideContainer/logBar.tsx | 26 ++++++-- src/store/ideContainer.ts | 64 ++++++++++++++++++- 6 files changed, 142 insertions(+), 44 deletions(-) diff --git a/src/hooks/useFlowCallbacks.ts b/src/hooks/useFlowCallbacks.ts index f0bc3dd..3da3910 100644 --- a/src/hooks/useFlowCallbacks.ts +++ b/src/hooks/useFlowCallbacks.ts @@ -15,7 +15,7 @@ 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, resetNodeStatus, updateIsRunning, updateEventListOld } from '@/store/ideContainer'; +import { updateCanvasDataMap, resetNodeStatus, updateIsRunning, updateEventListOld, addRuntimeLog, clearRuntimeLogs } from '@/store/ideContainer'; import { validateAllNodes, showValidationErrors, @@ -1301,6 +1301,9 @@ export const useFlowCallbacks = ( animationProgress: 0 } }))); + + // 清空当前应用的运行日志 + dispatch(clearRuntimeLogs({ appId: currentAppData.id })); } }, [initialData?.appId]); @@ -1340,4 +1343,4 @@ export const useFlowCallbacks = ( handleRun }; }; -export default useFlowCallbacks; +export default useFlowCallbacks; \ No newline at end of file diff --git a/src/hooks/useFlowEditorState.ts b/src/hooks/useFlowEditorState.ts index bc28dc5..fe5df89 100644 --- a/src/hooks/useFlowEditorState.ts +++ b/src/hooks/useFlowEditorState.ts @@ -9,9 +9,14 @@ import { Dispatch } from 'redux'; export const useFlowEditorState = (initialData?: any) => { const [nodes, setNodes] = useState([]); const [edges, setEdges] = useState([]); - const { canvasDataMap, nodeStatusMap, isRunning } = useSelector((state: any) => state.ideContainer); + const { canvasDataMap, nodeStatusMap, isRunning, appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer); const dispatch = useDispatch(); + // 获取当前应用的运行状态 + const currentAppIsRunning = currentAppData?.id && appRuntimeData[currentAppData.id] + ? appRuntimeData[currentAppData.id].isRunning + : false; + // 添加编辑弹窗相关状态 const [editingNode, setEditingNode] = useState(null); const [isEditModalOpen, setIsEditModalOpen] = useState(false); @@ -33,11 +38,11 @@ export const useFlowEditorState = (initialData?: any) => { data: { ...node.data, status: nodeStatusMap[node.id] || 'waiting', - isStatusVisible: isRunning // 只有在运行时才显示状态指示器 + isStatusVisible: currentAppIsRunning // 只有在运行时才显示状态指示器 } })) ); - }, [nodeStatusMap, isRunning]); + }, [nodeStatusMap, currentAppIsRunning]); const updateCanvasDataMapDebounced = useRef( debounce((dispatch: Dispatch, canvasDataMap: any, id: string, nodes: Node[], edges: Edge[]) => { @@ -65,7 +70,7 @@ export const useFlowEditorState = (initialData?: any) => { setEdgeForNodeAdd, positionForNodeAdd, setPositionForNodeAdd, - isRunning, + isRunning: currentAppIsRunning, // 使用当前应用的运行状态 historyInitialized, setHistoryInitialized, historyTimeoutRef, diff --git a/src/pages/flowEditor/FlowEditorMain.tsx b/src/pages/flowEditor/FlowEditorMain.tsx index 2573032..e1d3eb4 100644 --- a/src/pages/flowEditor/FlowEditorMain.tsx +++ b/src/pages/flowEditor/FlowEditorMain.tsx @@ -24,6 +24,7 @@ import ActionBar from './components/actionBar'; import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines'; import { useHistory } from './components/historyContext'; import { NodeTypes } from '@xyflow/react'; +import { useSelector } from 'react-redux'; const edgeTypes = { custom: CustomEdge @@ -137,6 +138,12 @@ const FlowEditorMain: React.FC = (props) => { const { getGuidelines, clearGuidelines, AlignmentGuides } = useAlignmentGuidelines(); const { undo, redo, canUndo, canRedo } = useHistory(); const reactFlowId = useMemo(() => new Date().getTime().toString(), []); + + // 从Redux store中获取当前应用的运行状态 + const { appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer); + const currentAppIsRunning = currentAppData?.id && appRuntimeData[currentAppData.id] + ? appRuntimeData[currentAppData.id].isRunning + : false; // 监听键盘事件实现快捷键 useEffect(() => { @@ -178,17 +185,17 @@ const FlowEditorMain: React.FC = (props) => { onContextMenu={(e) => e.preventDefault()}> ({ ...node, draggable: !isRunning }))} // 运行时禁用节点拖拽 + nodes={nodes.map(node => ({ ...node, draggable: !currentAppIsRunning }))} // 运行时禁用节点拖拽 edges={edges} nodeTypes={nodeTypes} edgeTypes={edgeTypes} snapToGrid={true} snapGrid={[2, 2]} - nodesConnectable={!isRunning} // 运行时禁用节点连接 - nodesDraggable={!isRunning} // 运行时禁用节点拖拽 - elementsSelectable={!isRunning} // 运行时禁用元素选择 - connectOnClick={!isRunning} // 运行时禁用点击连接 - disableKeyboardA11y={isRunning} // 运行时禁用键盘交互 + nodesConnectable={!currentAppIsRunning} // 运行时禁用节点连接 + nodesDraggable={!currentAppIsRunning} // 运行时禁用节点拖拽 + elementsSelectable={!currentAppIsRunning} // 运行时禁用元素选择 + connectOnClick={!currentAppIsRunning} // 运行时禁用点击连接 + disableKeyboardA11y={currentAppIsRunning} // 运行时禁用键盘交互 onBeforeDelete={async ({ nodes }) => { // 检查是否有开始或结束节点 const hasStartOrEndNode = nodes.some(node => node.type === 'start' || node.type === 'end'); @@ -203,7 +210,7 @@ const FlowEditorMain: React.FC = (props) => { ); // 允许删除操作继续进行 - return !isRunning; // 运行时禁止删除节点 + return !currentAppIsRunning; // 运行时禁止删除节点 }} onNodesDelete={(deleted) => { // 检查是否有循环节点 @@ -262,21 +269,21 @@ const FlowEditorMain: React.FC = (props) => { setIsEditModalOpen(false); }} - onNodesChange={!isRunning ? onNodesChange : undefined} // 运行时禁用节点变更 - onEdgesChange={!isRunning ? onEdgesChange : undefined} // 运行时禁用边变更 - onConnect={!isRunning ? onConnect : undefined} // 运行时禁用连接 - onReconnect={!isRunning ? onReconnect : undefined} // 运行时禁用重新连接 - onDragOver={!isRunning ? onDragOver : undefined} // 运行时禁用拖拽 - onDrop={!isRunning ? onDrop : undefined} // 运行时禁用放置 - onNodeDrag={!isRunning ? onNodeDrag : undefined} // 运行时禁用节点拖拽 + onNodesChange={!currentAppIsRunning ? onNodesChange : undefined} // 运行时禁用节点变更 + onEdgesChange={!currentAppIsRunning ? onEdgesChange : undefined} // 运行时禁用边变更 + onConnect={!currentAppIsRunning ? onConnect : undefined} // 运行时禁用连接 + onReconnect={!currentAppIsRunning ? onReconnect : undefined} // 运行时禁用重新连接 + onDragOver={!currentAppIsRunning ? onDragOver : undefined} // 运行时禁用拖拽 + onDrop={!currentAppIsRunning ? onDrop : undefined} // 运行时禁用放置 + onNodeDrag={!currentAppIsRunning ? onNodeDrag : undefined} // 运行时禁用节点拖拽 connectionLineType={ConnectionLineType.SmoothStep} connectionLineComponent={CustomConnectionLine} - onNodeDragStop={!isRunning ? onNodeDragStop : undefined} // 运行时禁用节点拖拽停止 - onNodeContextMenu={!isRunning ? onNodeContextMenu : undefined} // 运行时禁用节点上下文菜单 - onNodeDoubleClick={!isRunning ? onNodeDoubleClick : undefined} // 运行时禁用节点双击 - onEdgeContextMenu={!isRunning ? onEdgeContextMenu : undefined} // 运行时禁用边上下文菜单 - onPaneClick={!isRunning ? onPaneClick : undefined} // 运行时禁用面板点击 - onPaneContextMenu={!isRunning ? onPaneContextMenu : undefined} // 运行时禁用面板上下文菜单 + onNodeDragStop={!currentAppIsRunning ? onNodeDragStop : undefined} // 运行时禁用节点拖拽停止 + onNodeContextMenu={!currentAppIsRunning ? onNodeContextMenu : undefined} // 运行时禁用节点上下文菜单 + onNodeDoubleClick={!currentAppIsRunning ? onNodeDoubleClick : undefined} // 运行时禁用节点双击 + onEdgeContextMenu={!currentAppIsRunning ? onEdgeContextMenu : undefined} // 运行时禁用边上下文菜单 + onPaneClick={!currentAppIsRunning ? onPaneClick : undefined} // 运行时禁用面板点击 + onPaneContextMenu={!currentAppIsRunning ? onPaneContextMenu : undefined} // 运行时禁用面板上下文菜单 onEdgeMouseEnter={(_event, edge) => { setEdges((eds) => eds.map(e => { if (e.id === edge.id) { @@ -294,8 +301,8 @@ const FlowEditorMain: React.FC = (props) => { })); }} fitView - selectionOnDrag={!isRunning} // 运行时禁用拖拽选择 - selectionMode={!isRunning ? SelectionMode.Partial : undefined} // 运行时禁用选择模式 + selectionOnDrag={!currentAppIsRunning} // 运行时禁用拖拽选择 + selectionMode={!currentAppIsRunning ? SelectionMode.Partial : undefined} // 运行时禁用选择模式 > @@ -307,14 +314,14 @@ const FlowEditorMain: React.FC = (props) => { canUndo={canUndo} canRedo={canRedo} onRun={handleRun} - isRunning={isRunning} + isRunning={currentAppIsRunning} > {/*节点右键上下文*/} - {!isRunning && menu && menu.type === 'node' && ( + {!currentAppIsRunning && menu && menu.type === 'node' && (
= (props) => { )} {/*边右键上下文*/} - {!isRunning && menu && menu.type === 'edge' && ( + {!currentAppIsRunning && menu && menu.type === 'edge' && (
= (props) => { )} {/*画布右键上下文*/} - {!isRunning && menu && menu.type === 'pane' && ( + {!currentAppIsRunning && menu && menu.type === 'pane' && (
= (props) => { {/*统一的添加节点菜单*/} - {!isRunning && (edgeForNodeAdd || positionForNodeAdd) && ( + {!currentAppIsRunning && (edgeForNodeAdd || positionForNodeAdd) && (
= ({ onRun, isRunning = false }) => { - const { logBarStatus } = useSelector((state) => state.ideContainer); + const { logBarStatus, appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer); const dispatch = useDispatch(); + + // 获取当前应用的运行状态 + const currentAppIsRunning = currentAppData?.id && appRuntimeData[currentAppData.id] + ? appRuntimeData[currentAppData.id].isRunning + : false; const changeLogBarStatus = () => { dispatch(updateLogBarStatus(!logBarStatus)); }; const handleRun = () => { - onRun?.(!isRunning); + onRun?.(!currentAppIsRunning); }; return ( @@ -50,9 +55,9 @@ const ActionBar: React.FC = ({ icon={} onClick={() => handleRun()} style={{ padding: '0 8px', backgroundColor: '#fff' }} - status={isRunning ? 'danger' : undefined} + status={currentAppIsRunning ? 'danger' : undefined} > - {isRunning ? '停止' : '运行'} + {currentAppIsRunning ? '停止' : '运行'}