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: [],
});