diff --git a/src/components/FlowEditor/NodeStatusIndicator.tsx b/src/components/FlowEditor/NodeStatusIndicator.tsx
new file mode 100644
index 0000000..d3a1350
--- /dev/null
+++ b/src/components/FlowEditor/NodeStatusIndicator.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+import { NodeProps, useStore } from '@xyflow/react';
+import styles from './node/style/baseOther.module.less';
+
+// 定义节点状态类型
+export type NodeStatus = 'waiting' | 'running' | 'success' | 'failed';
+
+// 节点状态指示器组件
+const NodeStatusIndicator: React.FC<{ status: NodeStatus }> = ({ status }) => {
+ // 根据状态返回相应的指示器样式
+ const getStatusIndicator = () => {
+ switch (status) {
+ case 'waiting':
+ return
-
);
};
diff --git a/src/components/FlowEditor/node/basicNode/BasicNode.tsx b/src/components/FlowEditor/node/basicNode/BasicNode.tsx
index 6e04c51..22912c2 100644
--- a/src/components/FlowEditor/node/basicNode/BasicNode.tsx
+++ b/src/components/FlowEditor/node/basicNode/BasicNode.tsx
@@ -1,13 +1,11 @@
import React from 'react';
-// import styles from '@/pages/flowEditor/node/style/base.module.less';
+import { useStore } from '@xyflow/react';
import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
-import NodeContent from '@/pages/flowEditor/components/nodeContent';
import NodeContentOther from '@/pages/flowEditor/components/nodeContentOther';
-import { useStore } from '@xyflow/react';
-import { defaultNodeTypes } from '@/components/FlowEditor/node/types/defaultType';
-
+import NodeStatusIndicator, { NodeStatus } from '@/components/FlowEditor/NodeStatusIndicator';
+import { useStore as useFlowStore } from '@xyflow/react';
-const BasicNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => {
+const BasicNode = ({ data, id }: { data: any; id: string }) => {
const title = data.title || '基础节点';
// 获取节点选中状态 - 适配React Flow v12 API
@@ -15,13 +13,17 @@ const BasicNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => {
state.nodeLookup.get(id)?.selected || false
);
+ // 获取节点运行状态
+ const nodeStatus: NodeStatus = useFlowStore((state) =>
+ (state.nodeLookup.get(id)?.data?.status as NodeStatus) || 'waiting'
+ );
+
return (
);
diff --git a/src/components/FlowEditor/node/endNode/EndNode.tsx b/src/components/FlowEditor/node/endNode/EndNode.tsx
index 6eb7d3a..48bfae1 100644
--- a/src/components/FlowEditor/node/endNode/EndNode.tsx
+++ b/src/components/FlowEditor/node/endNode/EndNode.tsx
@@ -1,11 +1,10 @@
import React from 'react';
-// import styles from '@/pages/flowEditor/node/style/base.module.less';
import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
-import NodeContent from '@/pages/flowEditor/components/nodeContent';
+import NodeContentOther from '@/pages/flowEditor/components/nodeContentOther';
import { useStore } from '@xyflow/react';
import { defaultNodeTypes } from '@/components/FlowEditor/node/types/defaultType';
-import NodeContentOther from '@/pages/flowEditor/components/nodeContentOther';
-
+import NodeStatusIndicator, { NodeStatus } from '@/components/FlowEditor/NodeStatusIndicator';
+import { useStore as useFlowStore } from '@xyflow/react';
const EndNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => {
const title = data.title || '结束';
@@ -15,13 +14,17 @@ const EndNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => {
state.nodeLookup.get(id)?.selected || false
);
+ // 获取节点运行状态
+ const nodeStatus: NodeStatus = useFlowStore((state) =>
+ (state.nodeLookup.get(id)?.data?.status as NodeStatus) || 'waiting'
+ );
+
return (
);
diff --git a/src/components/FlowEditor/node/localNode/LocalNode.tsx b/src/components/FlowEditor/node/localNode/LocalNode.tsx
index e694256..0d543f3 100644
--- a/src/components/FlowEditor/node/localNode/LocalNode.tsx
+++ b/src/components/FlowEditor/node/localNode/LocalNode.tsx
@@ -1,11 +1,10 @@
import React from 'react';
import { useStore } from '@xyflow/react';
-// import styles from '@/pages/flowEditor/node/style/base.module.less';
import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
-import NodeContent from '@/pages/flowEditor/components/nodeContent';
import DynamicIcon from '@/components/DynamicIcon';
import NodeContentOther from '@/pages/flowEditor/components/nodeContentOther';
-
+import NodeStatusIndicator, { NodeStatus } from '@/components/FlowEditor/NodeStatusIndicator';
+import { useStore as useFlowStore } from '@xyflow/react';
const setIcon = (nodeType: string) => {
let type = 'IconApps';
@@ -67,13 +66,18 @@ const LocalNode = ({ data, id }: { data: any; id: string }) => {
state.nodeLookup.get(id)?.selected || false
);
+ // 获取节点运行状态
+ const nodeStatus: NodeStatus = useFlowStore((state) =>
+ (state.nodeLookup.get(id)?.data?.status as NodeStatus) || 'waiting'
+ );
+
return (
{setIcon(data.type)}
{title}
+
- {/*
*/}
);
diff --git a/src/components/FlowEditor/node/startNode/StartNode.tsx b/src/components/FlowEditor/node/startNode/StartNode.tsx
index cbc76ef..986a528 100644
--- a/src/components/FlowEditor/node/startNode/StartNode.tsx
+++ b/src/components/FlowEditor/node/startNode/StartNode.tsx
@@ -1,11 +1,10 @@
import React from 'react';
-// import styles from '@/pages/flowEditor/node/style/base.module.less';
import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
-import NodeContent from '@/pages/flowEditor/components/nodeContent';
+import NodeContentOther from '@/pages/flowEditor/components/nodeContentOther';
import { useStore } from '@xyflow/react';
import { defaultNodeTypes } from '@/components/FlowEditor/node/types/defaultType';
-import NodeContentOther from '@/pages/flowEditor/components/nodeContentOther';
-
+import NodeStatusIndicator, { NodeStatus } from '@/components/FlowEditor/NodeStatusIndicator';
+import { useStore as useFlowStore } from '@xyflow/react';
const StartNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => {
const title = data.title || '开始';
@@ -15,13 +14,17 @@ const StartNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => {
state.nodeLookup.get(id)?.selected || false
);
+ // 获取节点运行状态
+ const nodeStatus: NodeStatus = useFlowStore((state) =>
+ (state.nodeLookup.get(id)?.data?.status as NodeStatus) || 'waiting'
+ );
+
return (
);
diff --git a/src/components/FlowEditor/node/style/baseOther.module.less b/src/components/FlowEditor/node/style/baseOther.module.less
index d6c76d3..f499b98 100644
--- a/src/components/FlowEditor/node/style/baseOther.module.less
+++ b/src/components/FlowEditor/node/style/baseOther.module.less
@@ -13,6 +13,7 @@
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
color: #000000;
text-align: center;
+ position: relative;
}
.node-api-box,
@@ -92,4 +93,63 @@
min-height: 20px;
text-align: center;
}
+}
+
+// 节点状态指示器样式
+.node-status-indicator {
+ position: absolute;
+ top: -10px;
+ right: -10px;
+ width: 20px;
+ height: 20px;
+ z-index: 10;
+}
+
+.status-waiting {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background-color: #cccccc;
+ border: 2px solid #ffffff;
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
+}
+
+.status-running {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background-color: #1890ff;
+ border: 2px solid #ffffff;
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
+ animation: pulse 1.5s infinite;
+}
+
+.status-success {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background-color: #52c41a;
+ border: 2px solid #ffffff;
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
+}
+
+.status-failed {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background-color: #ff4d4f;
+ border: 2px solid #ffffff;
+ box-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
+}
+
+@keyframes pulse {
+ 0% {
+ box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.7);
+ }
+ 70% {
+ box-shadow: 0 0 0 6px rgba(24, 144, 255, 0);
+ }
+ 100% {
+ box-shadow: 0 0 0 0 rgba(24, 144, 255, 0);
+ }
}
\ No newline at end of file
diff --git a/src/hooks/useFlowCallbacks.ts b/src/hooks/useFlowCallbacks.ts
index 6075916..1873399 100644
--- a/src/hooks/useFlowCallbacks.ts
+++ b/src/hooks/useFlowCallbacks.ts
@@ -1,4 +1,4 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, useEffect } from 'react';
import {
applyNodeChanges,
applyEdgeChanges,
@@ -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 } from '@/store/ideContainer';
+import { updateCanvasDataMap, resetNodeStatus } from '@/store/ideContainer';
import {
validateAllNodes,
showValidationErrors,
@@ -919,6 +919,10 @@ export const useFlowCallbacks = (
socketId
};
runMainFlow(params);
+
+ // 重置节点状态
+ dispatch(resetNodeStatus());
+
}
else {
// 停止运行
@@ -959,4 +963,5 @@ export const useFlowCallbacks = (
saveFlowDataToServer,
handleRun
};
-};
\ No newline at end of file
+};
+export default useFlowCallbacks;
diff --git a/src/hooks/useFlowEditorState.ts b/src/hooks/useFlowEditorState.ts
index 04220ca..2285ff6 100644
--- a/src/hooks/useFlowEditorState.ts
+++ b/src/hooks/useFlowEditorState.ts
@@ -1,4 +1,4 @@
-import { useState, useRef } from 'react';
+import { useState, useRef, useEffect } from 'react';
import { Node, Edge } from '@xyflow/react';
import { debounce } from 'lodash';
import { useSelector, useDispatch } from 'react-redux';
@@ -9,7 +9,7 @@ import { Dispatch } from 'redux';
export const useFlowEditorState = (initialData?: any) => {
const [nodes, setNodes] = useState
([]);
const [edges, setEdges] = useState([]);
- const { canvasDataMap } = useSelector((state: any) => state.ideContainer);
+ const { canvasDataMap, nodeStatusMap } = useSelector((state: any) => state.ideContainer);
const dispatch = useDispatch();
// 添加编辑弹窗相关状态
@@ -28,6 +28,19 @@ export const useFlowEditorState = (initialData?: any) => {
const [historyInitialized, setHistoryInitialized] = useState(false);
const historyTimeoutRef = useRef(null);
+ // 更新节点状态,将从store获取的状态应用到节点上
+ useEffect(() => {
+ setNodes(prevNodes =>
+ prevNodes.map(node => ({
+ ...node,
+ data: {
+ ...node.data,
+ status: nodeStatusMap[node.id] || 'waiting'
+ }
+ }))
+ );
+ }, [nodeStatusMap]);
+
const updateCanvasDataMapDebounced = useRef(
debounce((dispatch: Dispatch, canvasDataMap: any, id: string, nodes: Node[], edges: Edge[]) => {
dispatch(updateCanvasDataMap({
diff --git a/src/pages/ideContainer/index.tsx b/src/pages/ideContainer/index.tsx
index a3c0cbb..0cd0b0b 100644
--- a/src/pages/ideContainer/index.tsx
+++ b/src/pages/ideContainer/index.tsx
@@ -30,7 +30,7 @@ import { getUserToken } from '@/api/user';
import useWebSocket from '@/hooks/useWebSocket';
import { Message } from '@arco-design/web-react';
import { queryEventItemBySceneId } from '@/api/event';
-
+import { updateNodeStatus } from '@/store/ideContainer';
type UrlParamsOptions = {
identity?: string;
@@ -79,6 +79,28 @@ function IDEContainer() {
const socketMessage = JSON.parse(event.data);
if (socketMessage?.socketId) dispatch(updateSocketId(socketMessage.socketId));
+ // 处理节点状态更新
+ if (socketMessage?.nodeLog) {
+ const { nodeId, state } = socketMessage.nodeLog;
+ // 将状态映射为前端使用的状态
+ let status = 'waiting';
+ switch (state) {
+ case 0: // 运行中
+ status = 'running';
+ break;
+ case 1: // 运行成功
+ status = 'success';
+ break;
+ case -1: // 运行失败
+ status = 'failed';
+ break;
+ default:// 等待运行
+ status = 'waiting';
+ break;
+ }
+ // 更新节点状态
+ dispatch(updateNodeStatus({ nodeId, status }));
+ }
}
});
diff --git a/src/store/ideContainer.ts b/src/store/ideContainer.ts
index ce32245..ad782c7 100644
--- a/src/store/ideContainer.ts
+++ b/src/store/ideContainer.ts
@@ -10,6 +10,7 @@ interface IDEContainerState {
eventList: any;
logBarStatus?: boolean;
socketId: string;
+ nodeStatusMap: Record; // 节点状态映射
}
const initialState: IDEContainerState = {
@@ -19,11 +20,13 @@ const initialState: IDEContainerState = {
canvasDataMap: {}, // 每个画布的缓存信息
projectComponentData: {}, // 工程下的组件列表
currentAppData: {}, // 当前选中的应用数据
- eventList:[], // 工程下的事件列表
+ eventList: [], // 工程下的事件列表
logBarStatus: false,
- socketId: '' // 工程的socketId
+ socketId: '', // 工程的socketId
+ nodeStatusMap: {} // 初始化节点状态映射
};
+// 创建切片
const ideContainerSlice = createSlice({
name: 'ideContainer',
initialState,
@@ -54,10 +57,20 @@ const ideContainerSlice = createSlice({
},
updateSocketId(state, action) {
state.socketId = action.payload;
+ },
+ // 更新节点状态
+ updateNodeStatus: (state, { payload }) => {
+ const { nodeId, status } = payload;
+ state.nodeStatusMap[nodeId] = status;
+ },
+ // 重置节点状态
+ resetNodeStatus: (state) => {
+ state.nodeStatusMap = {};
}
}
});
+// 导出动作 creators
export const {
updateInfo,
updateMenuData,
@@ -67,7 +80,10 @@ export const {
updateCurrentAppData,
updateEventList,
updateLogBarStatus,
- updateSocketId
+ updateSocketId,
+ updateNodeStatus,
+ resetNodeStatus
} = ideContainerSlice.actions;
+// 默认导出 reducer
export default ideContainerSlice.reducer;
\ No newline at end of file