From b01b9b5f69cfecf3340fcf2c1fb56edf3364c88d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B1=BC=E6=98=9F?= <1772580802@qq.com> Date: Tue, 2 Jun 2026 23:48:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9C=A8=E7=94=BB=E5=B8=83=E9=87=8C?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FlowEditor/node/imageNode/ImageNode.tsx | 13 +++++- .../node/style/baseOther.module.less | 22 +++++++++- .../components/nodeContentImage.tsx | 24 ++++++----- src/pages/ideContainer/logBar.tsx | 25 +++++++----- src/store/ideContainer.ts | 15 +++++++ src/utils/flow/imageRuntimeData.ts | 40 +++++++++++++++++++ src/utils/flow/runtime.ts | 2 + 7 files changed, 117 insertions(+), 24 deletions(-) create mode 100644 src/utils/flow/imageRuntimeData.ts diff --git a/src/components/FlowEditor/node/imageNode/ImageNode.tsx b/src/components/FlowEditor/node/imageNode/ImageNode.tsx index 46fa3ea..499008b 100644 --- a/src/components/FlowEditor/node/imageNode/ImageNode.tsx +++ b/src/components/FlowEditor/node/imageNode/ImageNode.tsx @@ -5,6 +5,9 @@ import DynamicIcon from '@/components/DynamicIcon'; import NodeContentImage from '@/pages/flowEditor/components/nodeContentImage'; import NodeStatusIndicator, { NodeStatus } from '@/components/FlowEditor/NodeStatusIndicator'; import { useStore as useFlowStore } from '@xyflow/react'; +import { useSelector } from 'react-redux'; +import { getCurrentAppKey } from '@/utils/flow/runtime'; +import { getRuntimeImageUrl } from '@/utils/flow/imageRuntimeData'; const setIcon = () => { return ; @@ -12,6 +15,12 @@ const setIcon = () => { const ImageNode = ({ data, id }: { data: any; id: string }) => { const title = data.title || '图片展示'; + const { currentAppData, appRuntimeData } = useSelector((state: any) => state.ideContainer); + const currentAppKey = getCurrentAppKey(currentAppData); + const runtimeNodeData = currentAppKey + ? appRuntimeData[currentAppKey]?.nodeData + : []; + const imageUrl = getRuntimeImageUrl(runtimeNodeData, id); // 获取节点选中状态 - 适配React Flow v12 API const isSelected = useStore((state) => @@ -35,9 +44,9 @@ const ImageNode = ({ data, id }: { data: any; id: string }) => { {title} - + ); }; -export default ImageNode; \ No newline at end of file +export default ImageNode; diff --git a/src/components/FlowEditor/node/style/baseOther.module.less b/src/components/FlowEditor/node/style/baseOther.module.less index f5466a8..c1d4a17 100644 --- a/src/components/FlowEditor/node/style/baseOther.module.less +++ b/src/components/FlowEditor/node/style/baseOther.module.less @@ -105,6 +105,26 @@ min-height: 10px; } + .node-image-box { + width: 150px; + padding: 6px; + margin: -1px auto 0; + background-color: #ffffff; + border: 1px solid #cccccc; + border-radius: 3px; + + :global(.arco-image) { + display: block; + max-width: 100%; + } + + :global(.arco-image-img) { + max-width: 100%; + max-height: 120px; + object-fit: contain; + } + } + .node-content-box { padding: 10px; margin: -1px auto; @@ -182,4 +202,4 @@ 100% { box-shadow: 0 0 0 0 rgba(24, 144, 255, 0); } -} \ No newline at end of file +} diff --git a/src/pages/flowEditor/components/nodeContentImage.tsx b/src/pages/flowEditor/components/nodeContentImage.tsx index 358198c..8680b66 100644 --- a/src/pages/flowEditor/components/nodeContentImage.tsx +++ b/src/pages/flowEditor/components/nodeContentImage.tsx @@ -1,6 +1,6 @@ import React from 'react'; import styles from '@/components/FlowEditor/node/style/baseOther.module.less'; -import { Handle, Position, useStore } from '@xyflow/react'; +import { Handle, Position } from '@xyflow/react'; import { Image } from '@arco-design/web-react'; import { formatDataType } from '@/utils/common'; @@ -103,7 +103,7 @@ const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[] }; -const NodeContent = ({ data }: { data: NodeContentData }) => { +const NodeContent = ({ data, imageUrl = '' }: { data: NodeContentData; imageUrl?: string }) => { const apiIns = data.parameters?.apiIns || []; const apiOuts = data.parameters?.apiOuts || []; const dataIns = data.parameters?.dataIns || []; @@ -178,14 +178,16 @@ const NodeContent = ({ data }: { data: NodeContentData }) => { )} - {/*图片展示 TODO 需要对接接口*/} - {/*
*/} - {/* */} - {/*
*/} + {imageUrl && ( +
+ 图片展示 +
+ )} {/* 根据节点类型渲染不同的句柄 */} {renderRegularNodeHandles(dataIns, dataOuts, apiIns, apiOuts)} @@ -193,4 +195,4 @@ const NodeContent = ({ data }: { data: NodeContentData }) => { ); }; -export default NodeContent; \ No newline at end of file +export default NodeContent; diff --git a/src/pages/ideContainer/logBar.tsx b/src/pages/ideContainer/logBar.tsx index aa3b4db..d24d148 100644 --- a/src/pages/ideContainer/logBar.tsx +++ b/src/pages/ideContainer/logBar.tsx @@ -1,7 +1,7 @@ import React, { useState, useRef, useEffect } from 'react'; import { ResizeBox, Tabs } from '@arco-design/web-react'; import styles from './style/logBar.module.less'; -import { updateLogBarStatus } from '@/store/ideContainer'; +import { updateLogBarStatus, updateRuntimeNodeData } from '@/store/ideContainer'; import { useSelector, useDispatch } from 'react-redux'; import { getNodeData } from '@/api/appIns'; import RunTimeData from './components/runTimeData'; @@ -165,27 +165,32 @@ const LogBar: React.FC = () => { // 获取当前应用的运行状态 const currentAppKey = getCurrentAppKey(currentAppData); - const isRunning = currentAppKey && appRuntimeData[currentAppKey]?.isRunning; + const currentAppIsRunning = currentAppKey && appRuntimeData[currentAppKey]?.isRunning; + const currentRunId = currentAppKey ? appRuntimeData[currentAppKey]?.runId : ''; // 实现轮询获取运行数据 - 只在应用运行时轮询 useEffect(() => { let intervalId: NodeJS.Timeout | null = null; - const appKey = getCurrentAppKey(currentAppData); + const appKey = currentAppKey; // 只有在应用正在运行且有 runId 时才开始轮询 if ( appKey && - appRuntimeData[appKey]?.isRunning && - appRuntimeData[appKey]?.runId + currentAppIsRunning && + currentRunId ) { const fetchRuntimeData = async () => { try { setLoading(true); - const response = await getNodeData(appRuntimeData[appKey].runId); + const response = await getNodeData(currentRunId); setRuntimeData((prev) => ({ ...prev, [appKey]: response.data, })); + dispatch(updateRuntimeNodeData({ + appId: appKey, + nodeData: response.data + })); } catch (error) { console.error('获取运行数据失败:', error); } finally { @@ -206,12 +211,12 @@ const LogBar: React.FC = () => { clearInterval(intervalId); } }; - }, [currentAppData, appRuntimeData]); + }, [currentAppKey, currentAppIsRunning, currentRunId, dispatch]); // 当应用停止运行时,清除运行数据 useEffect(() => { - const appKey = getCurrentAppKey(currentAppData); - if (appKey && !appRuntimeData[appKey]?.isRunning) { + const appKey = currentAppKey; + if (appKey && !currentAppIsRunning) { // 清除当前应用的运行数据 setRuntimeData((prev) => { const newData = { ...prev }; @@ -219,7 +224,7 @@ const LogBar: React.FC = () => { return newData; }); } - }, [isRunning, currentAppData]); + }, [currentAppKey, currentAppIsRunning]); // 渲染校验日志内容 const renderValidationLogs = () => { diff --git a/src/store/ideContainer.ts b/src/store/ideContainer.ts index 3dddcaa..c5af10d 100644 --- a/src/store/ideContainer.ts +++ b/src/store/ideContainer.ts @@ -28,6 +28,7 @@ interface IDEContainerState { isPaused: boolean; logs: any[]; runId: string; + nodeData: any[]; eventSendNodeList: any[]; // [{nodeID:topic}] eventlisteneList: any[]; // [{nodeID:topic}] } @@ -212,6 +213,19 @@ const ideContainerSlice = createSlice({ state.appRuntimeData[appId].logs = []; } }, + // 更新运行节点数据 + updateRuntimeNodeData: (state, { payload }) => { + const { appId, nodeData } = payload; + if (!appId) { + return; + } + if (!state.appRuntimeData[appId]) { + state.appRuntimeData[appId] = createDefaultAppRuntimeState(); + } + state.appRuntimeData[appId].nodeData = Array.isArray(nodeData) + ? nodeData + : []; + }, // 更新组件编码路径 updateComponentCodingPath(state, action) { state.componentCoding = { ...action.payload }; @@ -241,6 +255,7 @@ export const { updateEventNodeList, addRuntimeLog, clearRuntimeLogs, + updateRuntimeNodeData, updateComponentCodingPath, } = ideContainerSlice.actions; diff --git a/src/utils/flow/imageRuntimeData.ts b/src/utils/flow/imageRuntimeData.ts new file mode 100644 index 0000000..f7281b6 --- /dev/null +++ b/src/utils/flow/imageRuntimeData.ts @@ -0,0 +1,40 @@ +export interface RuntimeNodeData { + nodeId?: string; + input?: Record; + [key: string]: any; +} + +const isRuntimeNode = (item: any): item is RuntimeNodeData => { + return !!item && typeof item === 'object' && typeof item.nodeId === 'string'; +}; + +export const flattenRuntimeNodeData = (runtimeData: any): RuntimeNodeData[] => { + if (!Array.isArray(runtimeData)) { + return []; + } + + return runtimeData.reduce((nodes, item) => { + if (isRuntimeNode(item)) { + nodes.push(item); + return nodes; + } + + if (Array.isArray(item?.nodes)) { + nodes.push(...item.nodes.filter(isRuntimeNode)); + } + + return nodes; + }, []); +}; + +export const getRuntimeImageUrl = ( + runtimeData: any, + nodeId: string +): string => { + const nodeData = flattenRuntimeNodeData(runtimeData).find( + (item) => item.nodeId === nodeId + ); + const imageUrl = nodeData?.input?.in; + + return typeof imageUrl === 'string' ? imageUrl.trim() : ''; +}; diff --git a/src/utils/flow/runtime.ts b/src/utils/flow/runtime.ts index 0505e5b..57d2037 100644 --- a/src/utils/flow/runtime.ts +++ b/src/utils/flow/runtime.ts @@ -9,6 +9,7 @@ export interface AppRuntimeState { isPaused: boolean; logs: any[]; runId: string; + nodeData: any[]; eventSendNodeList: any[]; eventlisteneList: any[]; } @@ -29,6 +30,7 @@ export const createDefaultAppRuntimeState = (): AppRuntimeState => ({ isPaused: false, logs: [], runId: '', + nodeData: [], eventSendNodeList: [], eventlisteneList: [], });