feat: 在画布里展示图片

feature
鱼星 3 weeks ago
parent bea38a7e36
commit b01b9b5f69

@ -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 <DynamicIcon type="IconImage" style={{ fontSize: '16px', marginRight: '5px' }} />;
@ -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,7 +44,7 @@ const ImageNode = ({ data, id }: { data: any; id: string }) => {
{title}
<NodeStatusIndicator status={nodeStatus} isVisible={isStatusVisible} />
</div>
<NodeContentImage data={data} />
<NodeContentImage data={data} imageUrl={imageUrl} />
</div>
);
};

@ -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;

@ -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 需要对接接口*/}
{/*<div className={styles['node-image-box']}>*/}
{/* <Image*/}
{/* width={150}*/}
{/* src=""*/}
{/* alt="lamp"*/}
{/* />*/}
{/*</div>*/}
{imageUrl && (
<div className={styles['node-image-box']}>
<Image
width={150}
src={imageUrl}
alt="图片展示"
style={{ display: 'block' }}
/>
</div>
)}
{/* 根据节点类型渲染不同的句柄 */}
{renderRegularNodeHandles(dataIns, dataOuts, apiIns, apiOuts)}

@ -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<LogBarProps> = () => {
// 获取当前应用的运行状态
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<LogBarProps> = () => {
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<LogBarProps> = () => {
return newData;
});
}
}, [isRunning, currentAppData]);
}, [currentAppKey, currentAppIsRunning]);
// 渲染校验日志内容
const renderValidationLogs = () => {

@ -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;

@ -0,0 +1,40 @@
export interface RuntimeNodeData {
nodeId?: string;
input?: Record<string, any>;
[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<RuntimeNodeData[]>((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() : '';
};

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

Loading…
Cancel
Save