Compare commits

..

No commits in common. '7642fdd9958e304ebe1048c6987998f959386e54' and '84dcde237f8baf9f9ee92b666ae57e30fb088d28' have entirely different histories.

@ -1,113 +0,0 @@
# 新增 Flow 节点模板(基于当前重构版本)
这份模板按你现在的代码结构整理,目标是:**新增节点只改最少文件**。
---
## 1. 最小接入步骤
新增一个普通节点(非 LOOP通常只要 3 步:
1. 新建节点组件(可先复用 `LocalNode` 风格)
2. 在 `nodeRegistry` 注册类型映射
3. 在节点配置源(`localNodeData`)增加定义
> `useFlowCallbacks` 已统一走 `resolveNodeDefinition + buildRuntimeNode + ensureNodeTypeRegistered`,不需要再在多个入口手写创建逻辑。
---
## 2. 模板代码
### 2.1 新建节点组件
**路径示例**`src/components/FlowEditor/node/httpNode/HttpNode.tsx`
```tsx
import React from 'react';
import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode';
const HttpNode = (props: any) => {
// 第一版可以直接复用 LocalNode 渲染行为
return <LocalNode {...props} />;
};
export default HttpNode;
```
---
### 2.2 注册节点类型映射(关键)
**文件**`src/utils/flow/nodeRegistry.ts`
```ts
import HttpNode from '@/components/FlowEditor/node/httpNode/HttpNode';
export const resolveNodeComponent = (nodeType: string) => {
switch (nodeType) {
// ...已有 case
case 'HTTP':
return HttpNode;
default:
return LocalNode;
}
};
```
---
### 2.3 增加节点定义(用于菜单与创建)
**文件**`src/pages/flowEditor/sideBar/config/localNodeData.ts`
```ts
export const localNodeData = [
// ...已有节点
{
nodeType: 'HTTP',
nodeName: 'HTTP请求',
data: {
title: 'HTTP请求',
type: 'HTTP',
parameters: {
apiIns: [{ name: 'start', desc: '', dataType: '', defaultValue: '' }],
apiOuts: [{ name: 'done', desc: '', dataType: '', defaultValue: '' }],
dataIns: [],
dataOuts: [],
},
component: {
type: 'HTTP',
customDef: '{}',
},
},
},
];
```
---
## 3. 可选:节点专用编辑器
如果你希望配置面板是独立 UI再补
1. 新建编辑器组件(`src/components/FlowEditor/nodeEditors/...`
2. 在编辑器路由/映射处按 `nodeType` 挂载
不做这一步也能先跑通新增节点。
---
## 4. 当前推荐的接入方式
新增节点时不要再手写分散逻辑,优先复用:
- `resolveNodeDefinition(...)`
- `buildRuntimeNode(...)`
- `ensureNodeTypeRegistered(...)`
这些已覆盖:
- 侧栏拖拽到画布onDrop
- 画布空白处添加addNodeOnPane
- 在边上插入节点addNodeOnEdge

@ -1,149 +0,0 @@
# 新增 LOOP 类节点模板(基于当前重构版本)
这份模板用于你现在这套 Flow Core`loopFactory + nodeRegistry + useFlowCallbacks`)下,新增“成对节点/分支类”能力。
> 适用场景:像 `LOOP_START/LOOP_END` 这种需要一次创建多个节点和连边的节点族。
---
## 1. 推荐做法(先看)
不要在 `useFlowCallbacks` 里直接手写大段节点/边构造。按现在的模式:
1. 在 `src/utils/flow/` 新增工厂(如 `xxxFactory.ts`
2. 暴露:
- 节点对(或节点组)构造函数
- 组内连接边构造函数
- 外部插入连接边构造函数(可选)
3. 在 `useFlowCallbacks` 只负责调用工厂 + setNodes/setEdges + snapshot
---
## 2. 工厂模板
**路径示例**`src/utils/flow/retryFactory.ts`
```ts
import { Edge } from '@xyflow/react';
export const createRetryNodePair = (position: { x: number; y: number }) => {
const retryStartNode: any = {
id: `RETRY_START-${Date.now()}`,
type: 'RETRY',
position: { x: position.x, y: position.y },
data: {
title: '重试开始',
type: 'RETRY_START',
parameters: {
apiIns: [{ name: 'start', desc: '', dataType: '', defaultValue: '' }],
apiOuts: [{ name: 'done', desc: '', dataType: '', defaultValue: '' }],
dataIns: [],
dataOuts: [],
},
component: {},
},
};
const retryEndNode: any = {
id: `RETRY_END-${Date.now()}`,
type: 'RETRY',
position: { x: position.x + 400, y: position.y },
data: {
title: '重试结束',
type: 'RETRY_END',
parameters: {
apiIns: [
{ name: 'continue', desc: '', dataType: '', defaultValue: '' },
],
apiOuts: [{ name: 'break', desc: '', dataType: '', defaultValue: '' }],
dataIns: [],
dataOuts: [],
},
component: {
type: 'RETRY_END',
customDef: JSON.stringify({ retryStartNodeId: retryStartNode.id }),
retryStartNodeId: retryStartNode.id,
},
},
};
retryStartNode.data.component = {
type: 'RETRY_START',
customDef: JSON.stringify({ retryEndNodeId: retryEndNode.id }),
};
return { retryStartNode, retryEndNode };
};
export const createRetryGroupEdge = (
retryStartId: string,
retryEndId: string
): Edge => ({
id: `${retryStartId}-${retryEndId}-group`,
source: retryStartId,
target: retryEndId,
sourceHandle: `${retryStartId}-group`,
targetHandle: `${retryEndId}-group`,
type: 'custom',
});
```
---
## 3. useFlowCallbacks 接入模板
```ts
import {
createRetryNodePair,
createRetryGroupEdge,
} from '@/utils/flow/retryFactory';
import { ensureNodeTypeRegistered } from '@/utils/flow/nodeRegistry';
import { dispatchFlowSnapshotAsync } from '@/utils/flow/snapshot';
const addRetryNodeWithStartEnd = useCallback(
(position: { x: number; y: number }) => {
const { retryStartNode, retryEndNode } = createRetryNodePair(position);
const groupEdge = createRetryGroupEdge(retryStartNode.id, retryEndNode.id);
ensureNodeTypeRegistered('RETRY', '重试');
setNodes((nds) => {
const newNodes = [...nds, retryStartNode, retryEndNode];
dispatchFlowSnapshotAsync({
nodes: [...newNodes],
edges: [...edges, groupEdge],
});
return newNodes;
});
setEdges((eds) => [...eds, groupEdge]);
},
[edges]
);
```
---
## 4. 类型映射模板
**文件**`src/utils/flow/nodeRegistry.ts`
```ts
case 'RETRY':
return LoopNode; // 或你自己的专用节点组件
```
> 早期可复用 `LoopNode/LocalNode`,后续再拆专用展示组件。
---
## 5. 新增“边上插入该类节点”的模板(可选)
如需在边上插入节点组,建议像 `LOOP` 一样提供工厂函数:
- `createXxxNodePair(position)`
- `createXxxGroupEdge(startId, endId)`
- `createXxxInsertConnectionEdges({ sourceId, sourceHandle, targetId, targetHandle, ... })`
这样 `addNodeOnEdge` 里只拼装调用,不再手写边结构。

File diff suppressed because it is too large Load Diff

@ -3,7 +3,6 @@ import { Node, Edge } from '@xyflow/react';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { useSelector, useDispatch, shallowEqual } from 'react-redux'; import { useSelector, useDispatch, shallowEqual } from 'react-redux';
import { updateCanvasDataMap } from '@/store/ideContainer'; import { updateCanvasDataMap } from '@/store/ideContainer';
import { getCurrentAppKey } from '@/utils/flow/runtime';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
@ -12,22 +11,29 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => {
const [edges, setEdges] = useState<Edge[]>([]); const [edges, setEdges] = useState<Edge[]>([]);
// 使用 shallowEqual 比较器来避免不必要的重新渲染 // 使用 shallowEqual 比较器来避免不必要的重新渲染
const ideContainerState = useSelector( const ideContainerState = useSelector((state: any) => ({
(state: any) => ({
canvasDataMap: state.ideContainer.canvasDataMap, canvasDataMap: state.ideContainer.canvasDataMap,
appRuntimeData: state.ideContainer.appRuntimeData, appRuntimeData: state.ideContainer.appRuntimeData,
currentAppData: state.ideContainer.currentAppData, currentAppData: state.ideContainer.currentAppData,
}), }), shallowEqual);
shallowEqual
);
const { canvasDataMap, appRuntimeData, currentAppData } = ideContainerState; const { canvasDataMap, appRuntimeData, currentAppData } = ideContainerState;
const dispatch = useDispatch(); const dispatch = useDispatch();
// 辅助函数:获取当前应用/子流程的唯一标识符
const getCurrentAppKey = (currentAppData: any) => {
if (!currentAppData) return null;
// 如果是子流程key包含'sub'使用key作为标识符
if (currentAppData.key && currentAppData.key.includes('sub')) {
return currentAppData.key;
}
// 否则使用id
return currentAppData.id;
};
// 获取当前应用的运行状态 // 获取当前应用的运行状态
const currentAppKey = getCurrentAppKey(currentAppData); const currentAppKey = getCurrentAppKey(currentAppData);
const currentAppIsRunning = const currentAppIsRunning = currentAppKey && appRuntimeData[currentAppKey]
currentAppKey && appRuntimeData[currentAppKey]
? appRuntimeData[currentAppKey].isRunning ? appRuntimeData[currentAppKey].isRunning
: false; : false;
@ -38,10 +44,7 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => {
// 添加节点选择弹窗状态 // 添加节点选择弹窗状态
const [edgeForNodeAdd, setEdgeForNodeAdd] = useState<Edge | null>(null); const [edgeForNodeAdd, setEdgeForNodeAdd] = useState<Edge | null>(null);
const [positionForNodeAdd, setPositionForNodeAdd] = useState<{ const [positionForNodeAdd, setPositionForNodeAdd] = useState<{ x: number, y: number } | null>(null);
x: number;
y: number;
} | null>(null);
// 在组件顶部添加历史记录相关状态 // 在组件顶部添加历史记录相关状态
const [historyInitialized, setHistoryInitialized] = useState(false); const [historyInitialized, setHistoryInitialized] = useState(false);
@ -50,18 +53,16 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => {
// 更新节点状态将从store获取的状态应用到节点上 // 更新节点状态将从store获取的状态应用到节点上
useEffect(() => { useEffect(() => {
// 获取当前应用对应的节点状态映射 // 获取当前应用对应的节点状态映射
const currentNodeStatusMap = const currentNodeStatusMap = currentAppKey && appRuntimeData[currentAppKey]
currentAppKey && appRuntimeData[currentAppKey]
? appRuntimeData[currentAppKey].nodeStatusMap ? appRuntimeData[currentAppKey].nodeStatusMap
: {}; : {};
// 检查 initialData 中是否包含节点状态(用于查看历史实例) // 检查 initialData 中是否包含节点状态(用于查看历史实例)
const hasInitialDataStatus = const hasInitialDataStatus = initialData?.components &&
initialData?.components &&
Object.values(initialData.components).some((comp: any) => comp.status); Object.values(initialData.components).some((comp: any) => comp.status);
setNodes((prevNodes) => { setNodes(prevNodes =>{
return prevNodes.map((node) => { return prevNodes.map(node => {
// 如果是只读模式(历史实例查看),优先使用节点自身的历史状态 // 如果是只读模式(历史实例查看),优先使用节点自身的历史状态
// 如果是正常运行模式,只使用运行时状态 // 如果是正常运行模式,只使用运行时状态
let nodeStatus = 'waiting'; let nodeStatus = 'waiting';
@ -69,11 +70,8 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => {
if (readOnly) { if (readOnly) {
// 只读模式:使用历史状态 // 只读模式:使用历史状态
nodeStatus = (node.data.status as string) || 'waiting'; nodeStatus = node.data.status as string || 'waiting';
showStatus = showStatus = hasInitialDataStatus as boolean || node.data.isStatusVisible as boolean || false;
(hasInitialDataStatus as boolean) ||
(node.data.isStatusVisible as boolean) ||
false;
} else { } else {
// 正常模式:只使用运行时状态 // 正常模式:只使用运行时状态
nodeStatus = currentNodeStatusMap[node.id] || 'waiting'; nodeStatus = currentNodeStatusMap[node.id] || 'waiting';
@ -85,39 +83,21 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => {
data: { data: {
...node.data, ...node.data,
status: nodeStatus, status: nodeStatus,
isStatusVisible: showStatus, isStatusVisible: showStatus
}, }
}; };
}); });
}); });
}, [ }, [appRuntimeData, currentAppKey, currentAppIsRunning, initialData?.id, initialData?.components, nodes.length, readOnly]);
appRuntimeData,
currentAppKey,
currentAppIsRunning,
initialData?.id,
initialData?.components,
nodes.length,
readOnly,
]);
const updateCanvasDataMapDebounced = useRef( const updateCanvasDataMapDebounced = useRef(
debounce( debounce((dispatch: Dispatch<any>, canvasDataMap: any, id: string, nodes: Node[], edges: Edge[]) => {
( dispatch(updateCanvasDataMap({
dispatch: Dispatch<any>,
canvasDataMap: any,
id: string,
nodes: Node[],
edges: Edge[]
) => {
dispatch(
updateCanvasDataMap({
...canvasDataMap, ...canvasDataMap,
[id]: { nodes, edges }, [id]: { nodes, edges }
}) }));
); }, 500)
},
500
)
).current; ).current;
return { return {
@ -147,6 +127,6 @@ export const useFlowEditorState = (initialData?: any, readOnly?: boolean) => {
dispatch, dispatch,
// Initial data // Initial data
initialData: initialData, initialData: initialData
}; };
}; };

@ -27,7 +27,6 @@ import { NodeTypes } from '@xyflow/react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import HandlerBar from '@/pages/flowEditor/components/handlerBar'; import HandlerBar from '@/pages/flowEditor/components/handlerBar';
import PublishFlowModal from '@/pages/flowEditor/components/publishFlowModal'; import PublishFlowModal from '@/pages/flowEditor/components/publishFlowModal';
import { getCurrentAppKey } from '@/utils/flow/runtime';
const edgeTypes = { const edgeTypes = {
custom: CustomEdge custom: CustomEdge
@ -53,10 +52,8 @@ interface FlowEditorMainProps {
setIsDelete: React.Dispatch<React.SetStateAction<boolean>>; setIsDelete: React.Dispatch<React.SetStateAction<boolean>>;
edgeForNodeAdd: Edge | null; edgeForNodeAdd: Edge | null;
setEdgeForNodeAdd: React.Dispatch<React.SetStateAction<Edge | null>>; setEdgeForNodeAdd: React.Dispatch<React.SetStateAction<Edge | null>>;
positionForNodeAdd: { x: number; y: number } | null; positionForNodeAdd: { x: number, y: number } | null;
setPositionForNodeAdd: React.Dispatch< setPositionForNodeAdd: React.Dispatch<React.SetStateAction<{ x: number, y: number } | null>>;
React.SetStateAction<{ x: number; y: number } | null>
>;
isRunning: boolean; isRunning: boolean;
initialData: any; initialData: any;
canvasDataMap: any; canvasDataMap: any;
@ -84,11 +81,7 @@ interface FlowEditorMainProps {
copyNode: (node: Node) => void; copyNode: (node: Node) => void;
pasteNode: (position: { x: number; y: number }) => void; pasteNode: (position: { x: number; y: number }) => void;
addNodeOnEdge: (nodeType: string, node: any) => void; addNodeOnEdge: (nodeType: string, node: any) => void;
addNodeOnPane: ( addNodeOnPane: (nodeType: string, position: { x: number, y: number }, node?: any) => void;
nodeType: string,
position: { x: number; y: number },
node?: any
) => void;
handleAddNode: (nodeType: string, node: any) => void; handleAddNode: (nodeType: string, node: any) => void;
saveFlowDataToServer: () => void; saveFlowDataToServer: () => void;
handleRun: (running: boolean) => void; handleRun: (running: boolean) => void;
@ -152,8 +145,7 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
handleReRun handleReRun
} = props; } = props;
const { getGuidelines, clearGuidelines, AlignmentGuides } = const { getGuidelines, clearGuidelines, AlignmentGuides } = useAlignmentGuidelines();
useAlignmentGuidelines();
const { undo, redo, canUndo, canRedo } = useHistory(); const { undo, redo, canUndo, canRedo } = useHistory();
const reactFlowId = useMemo(() => new Date().getTime().toString(), []); const reactFlowId = useMemo(() => new Date().getTime().toString(), []);
@ -170,7 +162,7 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
if (isVisible) { if (isVisible) {
// 显示节点 - 从隐藏节点集合中移除 // 显示节点 - 从隐藏节点集合中移除
setHiddenNodes((prev) => { setHiddenNodes(prev => {
const newSet = new Set(prev); const newSet = new Set(prev);
newSet.delete(appId); newSet.delete(appId);
return newSet; return newSet;
@ -178,34 +170,62 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
} }
else { else {
// 隐藏节点 - 添加到隐藏节点集合 // 隐藏节点 - 添加到隐藏节点集合
setHiddenNodes((prev) => new Set(prev).add(appId)); setHiddenNodes(prev => new Set(prev).add(appId));
} }
}; };
document.addEventListener( document.addEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener);
'toggleNodeVisibility',
handleToggleNodeVisibility as EventListener
);
return () => { return () => {
document.removeEventListener( document.removeEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener);
'toggleNodeVisibility',
handleToggleNodeVisibility as EventListener
);
}; };
}, []); }, []);
// 从Redux store中获取当前应用的运行状态 // 从Redux store中获取当前应用的运行状态
const { appRuntimeData, currentAppData } = useSelector( const { appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer);
(state: any) => state.ideContainer
); // 辅助函数:获取当前应用/子流程的唯一标识符
const getCurrentAppKey = () => {
if (!currentAppData) return null;
// 如果是子流程key包含'sub'使用key作为标识符
if (currentAppData.key && currentAppData.key.includes('sub')) {
return currentAppData.key;
}
// 否则使用id
return currentAppData.id;
};
const currentAppKey = getCurrentAppKey(currentAppData); const currentAppKey = getCurrentAppKey();
const currentAppIsRunning = const currentAppIsRunning = currentAppKey && appRuntimeData[currentAppKey]
currentAppKey && appRuntimeData[currentAppKey]
? appRuntimeData[currentAppKey].isRunning ? appRuntimeData[currentAppKey].isRunning
: false; : false;
// 监听自定义事件以隐藏/显示节点
useEffect(() => {
const handleToggleNodeVisibility = (event: CustomEvent) => {
const { appId, isVisible } = event.detail;
if (isVisible) {
// 显示节点 - 从隐藏节点集合中移除
setHiddenNodes(prev => {
const newSet = new Set(prev);
newSet.delete(appId);
return newSet;
});
}
else {
// 隐藏节点 - 添加到隐藏节点集合
setHiddenNodes(prev => new Set(prev).add(appId));
}
};
document.addEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener);
return () => {
document.removeEventListener('toggleNodeVisibility', handleToggleNodeVisibility as EventListener);
};
}, []);
// 监听键盘事件实现快捷键 // 监听键盘事件实现快捷键
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
@ -255,7 +275,7 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
// Ctrl/Cmd+C 复制选中的节点 // Ctrl/Cmd+C 复制选中的节点
if (isModifierKey && e.key === 'c' && canEdit) { if (isModifierKey && e.key === 'c' && canEdit) {
// 获取当前选中的节点 // 获取当前选中的节点
const selectedNode = nodes.find((node) => node.selected); const selectedNode = nodes.find(node => node.selected);
if (selectedNode) { if (selectedNode) {
// 不允许复制开始和结束节点 // 不允许复制开始和结束节点
if (selectedNode.type === 'start' || selectedNode.type === 'end') { if (selectedNode.type === 'start' || selectedNode.type === 'end') {
@ -292,18 +312,7 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
return () => { return () => {
document.removeEventListener('keydown', handleKeyDown); document.removeEventListener('keydown', handleKeyDown);
}; };
}, [ }, [undo, redo, canUndo, canRedo, currentAppIsRunning, useDefault, nodes, copyNode, pasteNode, reactFlowInstance]);
undo,
redo,
canUndo,
canRedo,
currentAppIsRunning,
useDefault,
nodes,
copyNode,
pasteNode,
reactFlowInstance
]);
// 处理流程发布 // 处理流程发布
const handlePublish = () => { const handlePublish = () => {
@ -329,9 +338,7 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
}, [nodes, edges]); }, [nodes, edges]);
return ( return (
<div <div ref={reactFlowWrapper} style={{ width: '100%', height: '100%', position: 'relative' }}
ref={reactFlowWrapper}
style={{ width: '100%', height: '100%', position: 'relative' }}
onContextMenu={(e) => e.preventDefault()} onContextMenu={(e) => e.preventDefault()}
// 添加点击事件处理器,用于关闭添加节点菜单 // 添加点击事件处理器,用于关闭添加节点菜单
onClick={() => { onClick={() => {
@ -339,11 +346,10 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
setEdgeForNodeAdd(null); setEdgeForNodeAdd(null);
setPositionForNodeAdd(null); setPositionForNodeAdd(null);
} }
}} }}>
>
<ReactFlow <ReactFlow
id={reactFlowId} id={reactFlowId}
nodes={nodes.map((node) => { nodes={nodes.map(node => {
// 检查节点是否应该被隐藏 // 检查节点是否应该被隐藏
const isHidden = hiddenNodes.has(node.id); const isHidden = hiddenNodes.has(node.id);
// 应用透明度样式 // 应用透明度样式
@ -358,13 +364,12 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
} }
}; };
})} })}
edges={edges.map((edge) => { edges={edges.map(edge => {
// 检查边连接的节点是否被隐藏 // 检查边连接的节点是否被隐藏
const isSourceHidden = hiddenNodes.has(edge.source); const isSourceHidden = hiddenNodes.has(edge.source);
const isTargetHidden = hiddenNodes.has(edge.target); const isTargetHidden = hiddenNodes.has(edge.target);
// 如果源节点或目标节点被隐藏,则边也应用透明度 // 如果源节点或目标节点被隐藏,则边也应用透明度
const style = const style = (isSourceHidden || isTargetHidden) ? { opacity: 0.3 } : {};
isSourceHidden || isTargetHidden ? { opacity: 0.3 } : {};
// 更新边的数据,确保选择框也应用透明度 // 更新边的数据,确保选择框也应用透明度
return { return {
@ -375,9 +380,7 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
}, },
data: { data: {
...edge.data, ...edge.data,
...(isSourceHidden || isTargetHidden ...(isSourceHidden || isTargetHidden ? { hidden: true, opacity: 0.3 } : { opacity: 1 })
? { hidden: true, opacity: 0.3 }
: { opacity: 1 })
} }
}; };
})} })}
@ -407,17 +410,14 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
} }
// 检查是否有开始或结束节点 // 检查是否有开始或结束节点
const hasStartOrEndNode = nodes.some( const hasStartOrEndNode = nodes.some(node => node.type === 'start' || node.type === 'end');
(node) => node.type === 'start' || node.type === 'end'
);
if (hasStartOrEndNode) { if (hasStartOrEndNode) {
console.warn('开始和结束节点不允许删除'); console.warn('开始和结束节点不允许删除');
return false; // 阻止删除操作 return false; // 阻止删除操作
} }
// 检查是否有循环节点这里只是检查实际删除逻辑在onNodesDelete中处理 // 检查是否有循环节点这里只是检查实际删除逻辑在onNodesDelete中处理
const loopNodes = nodes.filter( const loopNodes = nodes.filter(node =>
(node) =>
node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END' node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END'
); );
@ -437,8 +437,7 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
} }
// 检查是否有循环节点 // 检查是否有循环节点
const loopNodes = deleted.filter( const loopNodes = deleted.filter(node =>
(node) =>
node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END' node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END'
); );
@ -447,20 +446,15 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
let nodesToRemove = [...deleted]; let nodesToRemove = [...deleted];
// 为每个循环节点找到其配对节点 // 为每个循环节点找到其配对节点
loopNodes.forEach((loopNode) => { loopNodes.forEach(loopNode => {
const component = loopNode.data?.component as const component = loopNode.data?.component as { customDef?: string } | undefined;
| { customDef?: string }
| undefined; if (loopNode.data?.type === 'LOOP_START' && component?.customDef) {
if (
loopNode.data?.type === 'LOOP_START' &&
component?.customDef
) {
try { try {
const customDef = JSON.parse(component.customDef); const customDef = JSON.parse(component.customDef);
const relatedNodeId = customDef.loopEndNodeId; const relatedNodeId = customDef.loopEndNodeId;
// 添加关联的结束节点到删除列表 // 添加关联的结束节点到删除列表
const relatedNode = nodes.find((n) => n.id === relatedNodeId); const relatedNode = nodes.find(n => n.id === relatedNodeId);
if (relatedNode) { if (relatedNode) {
nodesToRemove.push(relatedNode); nodesToRemove.push(relatedNode);
} }
@ -468,15 +462,12 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
console.error('解析循环开始节点数据失败:', e); console.error('解析循环开始节点数据失败:', e);
} }
} }
else if ( else if (loopNode.data?.type === 'LOOP_END' && component?.customDef) {
loopNode.data?.type === 'LOOP_END' &&
component?.customDef
) {
try { try {
const customDef = JSON.parse(component.customDef); const customDef = JSON.parse(component.customDef);
const relatedNodeId = customDef.loopStartNodeId; const relatedNodeId = customDef.loopStartNodeId;
// 添加关联的开始节点到删除列表 // 添加关联的开始节点到删除列表
const relatedNode = nodes.find((n) => n.id === relatedNodeId); const relatedNode = nodes.find(n => n.id === relatedNodeId);
if (relatedNode) { if (relatedNode) {
nodesToRemove.push(relatedNode); nodesToRemove.push(relatedNode);
} }
@ -487,123 +478,56 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
}); });
// 去重 // 去重
nodesToRemove = nodesToRemove.filter( nodesToRemove = nodesToRemove.filter((node, index, self) =>
(node, index, self) => index === self.findIndex(n => n.id === node.id)
index === self.findIndex((n) => n.id === node.id)
); );
// 删除所有相关节点和边 // 删除所有相关节点和边
setNodes((nds) => setNodes((nds) => nds.filter((n) => !nodesToRemove.find((d) => d.id === n.id)));
nds.filter((n) => !nodesToRemove.find((d) => d.id === n.id))
);
// 删除与这些节点相关的所有边 // 删除与这些节点相关的所有边
const nodeIdsToRemove = nodesToRemove.map((node) => node.id); const nodeIdsToRemove = nodesToRemove.map(node => node.id);
setEdges((eds) => setEdges((eds) => eds.filter((e) =>
eds.filter( !nodeIdsToRemove.includes(e.source) && !nodeIdsToRemove.includes(e.target)
(e) => ));
!nodeIdsToRemove.includes(e.source) &&
!nodeIdsToRemove.includes(e.target)
)
);
} }
else { else {
// 普通节点删除 // 普通节点删除
setNodes((nds) => setNodes((nds) => nds.filter((n) => !deleted.find((d) => d.id === n.id)));
nds.filter((n) => !deleted.find((d) => d.id === n.id))
);
} }
setIsEditModalOpen(false); setIsEditModalOpen(false);
}} }}
onNodesChange={ onNodesChange={readOnly ? undefined : (!currentAppIsRunning ? onNodesChange : undefined)} // readOnly 或运行时禁用节点变更
readOnly onEdgesChange={readOnly ? undefined : (!currentAppIsRunning ? onEdgesChange : undefined)} // readOnly 或运行时禁用边变更
? undefined onConnect={readOnly ? undefined : (!currentAppIsRunning ? onConnect : undefined)} // readOnly 或运行时禁用连接
: !currentAppIsRunning onReconnect={readOnly ? undefined : (!currentAppIsRunning ? onReconnect : undefined)} // readOnly 或运行时禁用重新连接
? onNodesChange onDragOver={readOnly ? undefined : (!currentAppIsRunning ? onDragOver : undefined)} // readOnly 或运行时禁用拖拽
: undefined onDrop={readOnly ? undefined : (!currentAppIsRunning ? onDrop : undefined)} // readOnly 或运行时禁用放置
} // readOnly 或运行时禁用节点变更 onNodeDrag={readOnly ? undefined : (!currentAppIsRunning ? onNodeDrag : undefined)} // readOnly 或运行时禁用节点拖拽
onEdgesChange={
readOnly
? undefined
: !currentAppIsRunning
? onEdgesChange
: undefined
} // readOnly 或运行时禁用边变更
onConnect={
readOnly ? undefined : !currentAppIsRunning ? onConnect : undefined
} // readOnly 或运行时禁用连接
onReconnect={
readOnly ? undefined : !currentAppIsRunning ? onReconnect : undefined
} // readOnly 或运行时禁用重新连接
onDragOver={
readOnly ? undefined : !currentAppIsRunning ? onDragOver : undefined
} // readOnly 或运行时禁用拖拽
onDrop={
readOnly ? undefined : !currentAppIsRunning ? onDrop : undefined
} // readOnly 或运行时禁用放置
onNodeDrag={
readOnly ? undefined : !currentAppIsRunning ? onNodeDrag : undefined
} // readOnly 或运行时禁用节点拖拽
connectionLineType={ConnectionLineType.SmoothStep} connectionLineType={ConnectionLineType.SmoothStep}
connectionLineComponent={CustomConnectionLine} connectionLineComponent={CustomConnectionLine}
onNodeDragStop={ onNodeDragStop={readOnly ? undefined : (!currentAppIsRunning ? onNodeDragStop : undefined)} // readOnly 或运行时禁用节点拖拽停止
readOnly onNodeContextMenu={readOnly ? undefined : ((useDefault || currentAppIsRunning) ? onNodeContextMenu : undefined)} // readOnly 或应用编排模式下禁用节点上下文菜单
? undefined onNodeDoubleClick={readOnly ? undefined : (!currentAppIsRunning ? onNodeDoubleClick : undefined)} // readOnly 或运行时禁用节点双击
: !currentAppIsRunning onEdgeContextMenu={readOnly ? undefined : ((!currentAppIsRunning && useDefault) ? onEdgeContextMenu : undefined)} // readOnly 或运行时或应用编排模式下禁用边上下文菜单
? onNodeDragStop onPaneClick={(useDefault || currentAppIsRunning) ? onPaneClick : undefined} // 运行时禁用面板点击
: undefined onPaneContextMenu={readOnly ? undefined : ((!currentAppIsRunning && useDefault) ? onPaneContextMenu : undefined)} // readOnly 或运行时或应用编排模式下禁用面板上下文菜单
} // readOnly 或运行时禁用节点拖拽停止
onNodeContextMenu={
readOnly
? undefined
: useDefault || currentAppIsRunning
? onNodeContextMenu
: undefined
} // readOnly 或应用编排模式下禁用节点上下文菜单
onNodeDoubleClick={
readOnly
? undefined
: !currentAppIsRunning
? onNodeDoubleClick
: undefined
} // readOnly 或运行时禁用节点双击
onEdgeContextMenu={
readOnly
? undefined
: !currentAppIsRunning && useDefault
? onEdgeContextMenu
: undefined
} // readOnly 或运行时或应用编排模式下禁用边上下文菜单
onPaneClick={
useDefault || currentAppIsRunning ? onPaneClick : undefined
} // 运行时禁用面板点击
onPaneContextMenu={
readOnly
? undefined
: !currentAppIsRunning && useDefault
? onPaneContextMenu
: undefined
} // readOnly 或运行时或应用编排模式下禁用面板上下文菜单
onEdgeMouseEnter={(_event, edge) => { onEdgeMouseEnter={(_event, edge) => {
setEdges((eds) => setEdges((eds) => eds.map(e => {
eds.map((e) => {
if (e.id === edge.id) { if (e.id === edge.id) {
return { ...e, data: { ...e.data, hovered: true } }; return { ...e, data: { ...e.data, hovered: true } };
} }
return e; return e;
}) }));
);
}} }}
onEdgeMouseLeave={(_event, edge) => { onEdgeMouseLeave={(_event, edge) => {
setEdges((eds) => setEdges((eds) => eds.map(e => {
eds.map((e) => {
if (e.id === edge.id) { if (e.id === edge.id) {
return { ...e, data: { ...e.data, hovered: false } }; return { ...e, data: { ...e.data, hovered: false } };
} }
return e; return e;
}) }));
);
}} }}
fitView fitView
selectionOnDrag={!currentAppIsRunning} // 运行时禁用拖拽选择 selectionOnDrag={!currentAppIsRunning} // 运行时禁用拖拽选择
@ -626,16 +550,14 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
></ActionBar> ></ActionBar>
</Panel> </Panel>
)} )}
{useDefault && !readOnly && ( {useDefault && !readOnly && <Panel position="top-right">
<Panel position="top-right">
<HandlerBar <HandlerBar
onPublish={handlePublish} onPublish={handlePublish}
isRunning={currentAppIsRunning} isRunning={currentAppIsRunning}
setNodes={setNodes} setNodes={setNodes}
setEdges={setEdges} setEdges={setEdges}
/> />
</Panel> </Panel>}
)}
<AlignmentGuides /> <AlignmentGuides />
</ReactFlow> </ReactFlow>
@ -650,7 +572,7 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
}} }}
> >
<NodeContextMenu <NodeContextMenu
node={nodes.find((n) => n.id === menu.id)!} node={nodes.find(n => n.id === menu.id)!}
onDelete={deleteNode} onDelete={deleteNode}
onEdit={editNode} onEdit={editNode}
onCopy={copyNode} onCopy={copyNode}
@ -672,7 +594,7 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
}} }}
> >
<EdgeContextMenu <EdgeContextMenu
edge={edges.find((e) => e.id === menu.id)!} edge={edges.find(e => e.id === menu.id)!}
onDelete={deleteEdge} onDelete={deleteEdge}
onEdit={editEdge} onEdit={editEdge}
onAddNode={(edge) => { onAddNode={(edge) => {
@ -696,11 +618,7 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
> >
<PaneContextMenu <PaneContextMenu
position={menu.position!} position={menu.position!}
onAddNode={( onAddNode={(nodeType: string, position: { x: number, y: number }, node: any) => {
nodeType: string,
position: { x: number; y: number },
node: any
) => {
addNodeOnPane(nodeType, position, node); addNodeOnPane(nodeType, position, node);
setIsEditModalOpen(false); setIsEditModalOpen(false);
setMenu(null); // 关闭上下文菜单 setMenu(null); // 关闭上下文菜单
@ -720,18 +638,12 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
/> />
{/*统一的添加节点菜单 - 仅在默认模式且非运行时显示*/} {/*统一的添加节点菜单 - 仅在默认模式且非运行时显示*/}
{!currentAppIsRunning && {!currentAppIsRunning && useDefault && (edgeForNodeAdd || positionForNodeAdd) && (
useDefault &&
(edgeForNodeAdd || positionForNodeAdd) && (
<div <div
style={{ style={{
position: 'absolute', position: 'absolute',
top: edgeForNodeAdd top: edgeForNodeAdd ? (edgeForNodeAdd.data?.y as number || 0) : (positionForNodeAdd?.y || 0),
? (edgeForNodeAdd.data?.y as number) || 0 left: edgeForNodeAdd ? ((edgeForNodeAdd.data?.x as number || 0) + 20) : (positionForNodeAdd?.x || 0),
: positionForNodeAdd?.y || 0,
left: edgeForNodeAdd
? ((edgeForNodeAdd.data?.x as number) || 0) + 20
: positionForNodeAdd?.x || 0,
zIndex: 1000, zIndex: 1000,
transform: 'none' transform: 'none'
}} }}

@ -7,11 +7,10 @@ import {
IconPause, IconPause,
IconSync, IconSync,
IconUndo, IconUndo,
IconRedo, IconRedo
} from '@arco-design/web-react/icon'; } from '@arco-design/web-react/icon';
import { updateLogBarStatus } from '@/store/ideContainer'; import { updateLogBarStatus } from '@/store/ideContainer';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { getCurrentAppKey } from '@/utils/flow/runtime';
const ButtonGroup = Button.Group; const ButtonGroup = Button.Group;
@ -38,23 +37,30 @@ const ActionBar: React.FC<ActionBarProps> = ({
onRun, onRun,
onPause, onPause,
onReRun, onReRun,
isRunning = false, isRunning = false
}) => { }) => {
const { logBarStatus, appRuntimeData, currentAppData } = useSelector( const { logBarStatus, appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer);
(state: any) => state.ideContainer
);
const dispatch = useDispatch(); const dispatch = useDispatch();
// 辅助函数:获取当前应用/子流程的唯一标识符
const getCurrentAppKey = () => {
if (!currentAppData) return null;
// 如果是子流程key包含'sub'使用key作为标识符
if (currentAppData.key && currentAppData.key.includes('sub')) {
return currentAppData.key;
}
// 否则使用id
return currentAppData.id;
};
// 获取当前应用的运行状态 // 获取当前应用的运行状态
const currentAppKey = getCurrentAppKey(currentAppData); const currentAppKey = getCurrentAppKey();
const currentAppIsRunning = const currentAppIsRunning = currentAppKey && appRuntimeData[currentAppKey]
currentAppKey && appRuntimeData[currentAppKey]
? appRuntimeData[currentAppKey].isRunning ? appRuntimeData[currentAppKey].isRunning
: false; : false;
// 获取当前应用的暂停状态(如果有的话) // 获取当前应用的暂停状态(如果有的话)
const currentAppIsPaused = const currentAppIsPaused = currentAppKey && appRuntimeData[currentAppKey]
currentAppKey && appRuntimeData[currentAppKey]
? appRuntimeData[currentAppKey].isPaused ? appRuntimeData[currentAppKey].isPaused
: false; : false;
@ -78,9 +84,7 @@ const ActionBar: React.FC<ActionBarProps> = ({
return ( return (
<div className="action-bar"> <div className="action-bar">
<Button onClick={onSave} type="primary" shape="round" icon={<IconSave />}> <Button onClick={onSave} type="primary" shape="round" icon={<IconSave />}></Button>
</Button>
{useDefault && ( {useDefault && (
<> <>
<ButtonGroup style={{ marginLeft: 8 }}> <ButtonGroup style={{ marginLeft: 8 }}>

@ -5,7 +5,6 @@ import { updateLogBarStatus } from '@/store/ideContainer';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { getNodeData } from '@/api/appIns'; import { getNodeData } from '@/api/appIns';
import RunTimeData from './components/runTimeData'; import RunTimeData from './components/runTimeData';
import { getCurrentAppKey } from '@/utils/flow/runtime';
const TabPane = Tabs.TabPane; const TabPane = Tabs.TabPane;
@ -29,17 +28,17 @@ const data = [
{ {
key: '1', key: '1',
title: '运行日志', title: '运行日志',
content: '运行时日志...', content: '运行时日志...'
}, },
{ {
key: '2', key: '2',
title: '校验日志', title: '校验日志',
content: '校验日志...', content: '校验日志...'
}, },
{ {
key: '3', key: '3',
title: '运行数据', title: '运行数据',
content: '运行数据日志...', content: '运行数据日志...'
}, },
// { // {
// key: '4', // key: '4',
@ -57,18 +56,28 @@ const LogBar: React.FC<LogBarProps> = () => {
const [logContainerHeight, setLogContainerHeight] = useState('250px'); // 添加日志容器高度状态 const [logContainerHeight, setLogContainerHeight] = useState('250px'); // 添加日志容器高度状态
const [runtimeData, setRuntimeData] = useState<RuntimeData>({}); // 添加运行数据状态 const [runtimeData, setRuntimeData] = useState<RuntimeData>({}); // 添加运行数据状态
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { logBarStatus, appRuntimeData, currentAppData } = useSelector( const { logBarStatus, appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer);
(state: any) => state.ideContainer
);
const dispatch = useDispatch(); const dispatch = useDispatch();
// 辅助函数:获取当前应用/子流程的唯一标识符
const getCurrentAppKey = () => {
if (!currentAppData) return null;
// 如果是子流程key包含'sub'使用key作为标识符
if (currentAppData.key && currentAppData.key.includes('sub')) {
return currentAppData.key;
}
// 否则使用id
return currentAppData.id;
};
// 处理 Tab 点击事件 // 处理 Tab 点击事件
const handleTabClick = (key: string) => { const handleTabClick = (key: string) => {
// 如果点击当前激活的 tab则切换收起状态 // 如果点击当前激活的 tab则切换收起状态
if (key === activeTab) { if (key === activeTab) {
dispatch(updateLogBarStatus(!logBarStatus)); dispatch(updateLogBarStatus(!logBarStatus));
} else { }
else {
// 如果点击的是其他 tab则切换到该 tab 并展开 // 如果点击的是其他 tab则切换到该 tab 并展开
setActiveTab(key); setActiveTab(key);
dispatch(updateLogBarStatus(true)); dispatch(updateLogBarStatus(true));
@ -78,24 +87,20 @@ const LogBar: React.FC<LogBarProps> = () => {
// 当 collapsed 状态改变时,直接更新元素的样式 // 当 collapsed 状态改变时,直接更新元素的样式
useEffect(() => { useEffect(() => {
if (resizeBoxRef.current) { if (resizeBoxRef.current) {
resizeBoxRef.current.style.height = logBarStatus resizeBoxRef.current.style.height = logBarStatus ? logContainerHeight : '0px';
? logContainerHeight
: '0px';
} }
}, [logBarStatus, logContainerHeight]); }, [logBarStatus, logContainerHeight]);
// 处理 ResizeBox 手动调整大小事件 // 处理 ResizeBox 手动调整大小事件
const handleResize = ( const handleResize = (e: MouseEvent, size: {
e: MouseEvent,
size: {
width: number; width: number;
height: number; height: number;
} }) => {
) => {
// 当高度接近收起状态的高度时,同步更新 logBarStatus 状态 // 当高度接近收起状态的高度时,同步更新 logBarStatus 状态
if (size.height <= 40) { if (size.height <= 40) {
dispatch(updateLogBarStatus(false)); dispatch(updateLogBarStatus(false));
} else { }
else {
dispatch(updateLogBarStatus(true)); dispatch(updateLogBarStatus(true));
// 更新日志容器高度状态 // 更新日志容器高度状态
setLogContainerHeight(`${size.height}px`); setLogContainerHeight(`${size.height}px`);
@ -113,10 +118,10 @@ const LogBar: React.FC<LogBarProps> = () => {
id: Date.now(), id: Date.now(),
type, type,
message, message,
timestamp, timestamp
}; };
setValidationLogs((prev) => [...prev, newLog]); setValidationLogs(prev => [...prev, newLog]);
// 自动切换到校验日志tab并展开logBar // 自动切换到校验日志tab并展开logBar
// setActiveTab('2'); // setActiveTab('2');
@ -128,24 +133,24 @@ const LogBar: React.FC<LogBarProps> = () => {
id: Date.now(), id: Date.now(),
type, type,
message, message,
timestamp, timestamp
}; };
setruntimeLogs((prev) => [...prev, newLog]); setruntimeLogs(prev => [...prev, newLog]);
// 自动切换到运行日志tab并展开logBar // 自动切换到运行日志tab并展开logBar
dispatch(updateLogBarStatus(true)); dispatch(updateLogBarStatus(true));
// 同时将日志添加到对应应用的运行日志中 // 同时将日志添加到对应应用的运行日志中
// 如果提供了 appId优先使用提供的 appId否则使用当前激活的应用 // 如果提供了 appId优先使用提供的 appId否则使用当前激活的应用
const targetAppKey = appId || getCurrentAppKey(currentAppData); const targetAppKey = appId || getCurrentAppKey();
if (targetAppKey) { if (targetAppKey) {
dispatch({ dispatch({
type: 'ideContainer/addRuntimeLog', type: 'ideContainer/addRuntimeLog',
payload: { payload: {
log: newLog, log: newLog,
appId: targetAppKey, appId: targetAppKey
}, }
}); });
} }
} }
@ -156,35 +161,28 @@ const LogBar: React.FC<LogBarProps> = () => {
// 清理事件监听器 // 清理事件监听器
return () => { return () => {
document.removeEventListener( document.removeEventListener('logMessage', handleLogMessage as EventListener);
'logMessage',
handleLogMessage as EventListener
);
}; };
}, [dispatch, currentAppData]); }, [dispatch, currentAppData]);
// 获取当前应用的运行状态 // 获取当前应用的运行状态
const currentAppKey = getCurrentAppKey(currentAppData); const currentAppKey = getCurrentAppKey();
const isRunning = currentAppKey && appRuntimeData[currentAppKey]?.isRunning; const isRunning = currentAppKey && appRuntimeData[currentAppKey]?.isRunning;
// 实现轮询获取运行数据 - 只在应用运行时轮询 // 实现轮询获取运行数据 - 只在应用运行时轮询
useEffect(() => { useEffect(() => {
let intervalId: NodeJS.Timeout | null = null; let intervalId: NodeJS.Timeout | null = null;
const appKey = getCurrentAppKey(currentAppData); const appKey = getCurrentAppKey();
// 只有在应用正在运行且有 runId 时才开始轮询 // 只有在应用正在运行且有 runId 时才开始轮询
if ( if (appKey && appRuntimeData[appKey]?.isRunning && appRuntimeData[appKey]?.runId) {
appKey &&
appRuntimeData[appKey]?.isRunning &&
appRuntimeData[appKey]?.runId
) {
const fetchRuntimeData = async () => { const fetchRuntimeData = async () => {
try { try {
setLoading(true); setLoading(true);
const response = await getNodeData(appRuntimeData[appKey].runId); const response = await getNodeData(appRuntimeData[appKey].runId);
setRuntimeData((prev) => ({ setRuntimeData(prev => ({
...prev, ...prev,
[appKey]: response.data, [appKey]: response.data
})); }));
} catch (error) { } catch (error) {
console.error('获取运行数据失败:', error); console.error('获取运行数据失败:', error);
@ -210,10 +208,10 @@ const LogBar: React.FC<LogBarProps> = () => {
// 当应用停止运行时,清除运行数据 // 当应用停止运行时,清除运行数据
useEffect(() => { useEffect(() => {
const appKey = getCurrentAppKey(currentAppData); const appKey = getCurrentAppKey();
if (appKey && !appRuntimeData[appKey]?.isRunning) { if (appKey && !appRuntimeData[appKey]?.isRunning) {
// 清除当前应用的运行数据 // 清除当前应用的运行数据
setRuntimeData((prev) => { setRuntimeData(prev => {
const newData = { ...prev }; const newData = { ...prev };
delete newData[appKey]; delete newData[appKey];
return newData; return newData;
@ -224,25 +222,12 @@ const LogBar: React.FC<LogBarProps> = () => {
// 渲染校验日志内容 // 渲染校验日志内容
const renderValidationLogs = () => { const renderValidationLogs = () => {
return ( return (
<div <div style={{ padding: '10px', height: 'calc(100% - 40px)', overflowY: 'auto' }}>
style={{
padding: '10px',
height: 'calc(100% - 40px)',
overflowY: 'auto',
}}
>
{validationLogs.length === 0 ? ( {validationLogs.length === 0 ? (
<p></p> <p></p>
) : ( ) : (
validationLogs.map((log) => ( validationLogs.map(log => (
<div <div key={log.id} style={{ marginBottom: '8px', padding: '4px', borderBottom: '1px solid #eee' }}>
key={log.id}
style={{
marginBottom: '8px',
padding: '4px',
borderBottom: '1px solid #eee',
}}
>
<div style={{ fontSize: '12px', color: '#999' }}> <div style={{ fontSize: '12px', color: '#999' }}>
{new Date(log.timestamp).toLocaleString()} {new Date(log.timestamp).toLocaleString()}
</div> </div>
@ -259,32 +244,18 @@ const LogBar: React.FC<LogBarProps> = () => {
// 渲染运行时日志内容 // 渲染运行时日志内容
const renderRuntimeLogs = () => { const renderRuntimeLogs = () => {
// 获取当前应用的运行日志 // 获取当前应用的运行日志
const currentAppKey = getCurrentAppKey(currentAppData); const currentAppKey = getCurrentAppKey();
const currentAppLogs = const currentAppLogs = currentAppKey && appRuntimeData[currentAppKey]
currentAppKey && appRuntimeData[currentAppKey]
? appRuntimeData[currentAppKey].logs || [] ? appRuntimeData[currentAppKey].logs || []
: []; : [];
return ( return (
<div <div style={{ padding: '10px', height: 'calc(100% - 40px)', overflowY: 'auto' }}>
style={{
padding: '10px',
height: 'calc(100% - 40px)',
overflowY: 'auto',
}}
>
{currentAppLogs.length === 0 ? ( {currentAppLogs.length === 0 ? (
<p></p> <p></p>
) : ( ) : (
currentAppLogs.map((log: LogMessage) => ( currentAppLogs.map((log: LogMessage) => (
<div <div key={log.id} style={{ marginBottom: '8px', padding: '4px', borderBottom: '1px solid #eee' }}>
key={log.id}
style={{
marginBottom: '8px',
padding: '4px',
borderBottom: '1px solid #eee',
}}
>
<div style={{ fontSize: '12px', color: '#999' }}> <div style={{ fontSize: '12px', color: '#999' }}>
{new Date(log.timestamp).toLocaleString()} {new Date(log.timestamp).toLocaleString()}
</div> </div>
@ -300,19 +271,11 @@ const LogBar: React.FC<LogBarProps> = () => {
// 渲染运行数据内容 // 渲染运行数据内容
const renderRuntimeData = () => { const renderRuntimeData = () => {
const currentAppKey = getCurrentAppKey(currentAppData); const currentAppKey = getCurrentAppKey();
const currentAppDataContent = currentAppKey const currentAppDataContent = currentAppKey ? runtimeData[currentAppKey] : null;
? runtimeData[currentAppKey]
: null;
return ( return (
<div <div style={{ padding: '10px', height: 'calc(100% - 40px)', overflowY: 'auto' }}>
style={{
padding: '10px',
height: 'calc(100% - 40px)',
overflowY: 'auto',
}}
>
{!currentAppDataContent ? ( {!currentAppDataContent ? (
<p></p> <p></p>
) : ( ) : (
@ -329,7 +292,7 @@ const LogBar: React.FC<LogBarProps> = () => {
className={styles.logBar} className={styles.logBar}
directions={['top']} directions={['top']}
style={{ style={{
height: logBarStatus ? logContainerHeight : '0px', height: logBarStatus ? logContainerHeight : '0px'
}} }}
onMoving={handleResize} onMoving={handleResize}
> >
@ -341,13 +304,10 @@ const LogBar: React.FC<LogBarProps> = () => {
> >
{tabs.map((x) => ( {tabs.map((x) => (
<TabPane destroyOnHide key={x.key} title={x.title}> <TabPane destroyOnHide key={x.key} title={x.title}>
{x.key === '1' {x.key === '1' ? renderRuntimeLogs() :
? renderRuntimeLogs() x.key === '2' ? renderValidationLogs() :
: x.key === '2' x.key === '3' ? renderRuntimeData() : // 添加运行数据渲染
? renderValidationLogs() x.content}
: x.key === '3'
? renderRuntimeData() // 添加运行数据渲染
: x.content}
</TabPane> </TabPane>
))} ))}
</Tabs> </Tabs>

@ -1,8 +1,4 @@
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import {
createDefaultAppRuntimeState,
getCurrentAppKey,
} from '@/utils/flow/runtime';
// 定义初始状态类型 // 定义初始状态类型
interface IDEContainerState { interface IDEContainerState {
@ -20,25 +16,22 @@ interface IDEContainerState {
nodeStatusMap: Record<string, string>; // 节点状态映射 nodeStatusMap: Record<string, string>; // 节点状态映射
isRunning: boolean; // 是否正在运行 isRunning: boolean; // 是否正在运行
// 应用运行状态和日志数据按应用ID隔离存储 // 应用运行状态和日志数据按应用ID隔离存储
appRuntimeData: Record< appRuntimeData: Record<string, {
string,
{
nodeStatusMap: Record<string, string>; nodeStatusMap: Record<string, string>;
isRunning: boolean; isRunning: boolean;
isPaused: boolean; isPaused: boolean;
logs: any[]; logs: any[];
runId: string; runId: string;
eventSendNodeList: any[]; // [{nodeID:topic}] eventSendNodeList: any[], // [{nodeID:topic}]
eventlisteneList: any[]; // [{nodeID:topic}] eventlisteneList: any[] // [{nodeID:topic}]
} }>;
>;
gloBalVarList: any; gloBalVarList: any;
// 组件开发相关状态 // 组件开发相关状态
componentCoding: { componentCoding: {
localProjectPath: string; // code-server路径 localProjectPath: string; // code-server路径
name: string; // 组件名称 name: string; // 组件名称
projectId: string; // 组件标识 projectId: string; // 组件标识
id: string; // 组件id id: string,// 组件id
}; };
} }
@ -63,8 +56,20 @@ const initialState: IDEContainerState = {
localProjectPath: '', localProjectPath: '',
name: '', name: '',
projectId: '', projectId: '',
id: '', id: ''
}, }
};
// 辅助函数:获取当前应用/子流程的唯一标识符
// 对于子流程,使用 key如 sub_xxx对于主流程使用 id
const getCurrentAppKey = (currentAppData: any) => {
if (!currentAppData) return null;
// 如果是子流程key包含'sub'使用key作为标识符
if (currentAppData.key && currentAppData.key.includes('sub')) {
return currentAppData.key;
}
// 否则使用id
return currentAppData.id;
}; };
// 创建切片 // 创建切片
@ -85,10 +90,7 @@ const ideContainerSlice = createSlice({
state.canvasDataMap = { ...state.canvasDataMap, ...action.payload }; state.canvasDataMap = { ...state.canvasDataMap, ...action.payload };
}, },
updateProjectComponentData(state, action) { updateProjectComponentData(state, action) {
state.projectComponentData = { state.projectComponentData = { ...state.projectComponentData, ...action.payload };
...state.projectComponentData,
...action.payload,
};
}, },
updateCurrentAppData(state, action) { updateCurrentAppData(state, action) {
state.currentAppData = action.payload; state.currentAppData = action.payload;
@ -96,9 +98,7 @@ const ideContainerSlice = createSlice({
// 切换应用时,同步全局 nodeStatusMap 为新应用的节点状态 // 切换应用时,同步全局 nodeStatusMap 为新应用的节点状态
const newAppKey = getCurrentAppKey(action.payload); const newAppKey = getCurrentAppKey(action.payload);
if (newAppKey && state.appRuntimeData[newAppKey]) { if (newAppKey && state.appRuntimeData[newAppKey]) {
state.nodeStatusMap = { state.nodeStatusMap = { ...state.appRuntimeData[newAppKey].nodeStatusMap };
...state.appRuntimeData[newAppKey].nodeStatusMap,
};
state.isRunning = state.appRuntimeData[newAppKey].isRunning; state.isRunning = state.appRuntimeData[newAppKey].isRunning;
} else { } else {
// 如果新应用没有运行时数据,清空节点状态 // 如果新应用没有运行时数据,清空节点状态
@ -140,7 +140,15 @@ const ideContainerSlice = createSlice({
// 更新目标应用的节点状态 // 更新目标应用的节点状态
if (targetAppKey) { if (targetAppKey) {
if (!state.appRuntimeData[targetAppKey]) { if (!state.appRuntimeData[targetAppKey]) {
state.appRuntimeData[targetAppKey] = createDefaultAppRuntimeState(); state.appRuntimeData[targetAppKey] = {
nodeStatusMap: {},
isRunning: false,
isPaused: false,
logs: [],
runId: '',
eventSendNodeList: [],
eventlisteneList: []
};
} }
state.appRuntimeData[targetAppKey].nodeStatusMap[nodeId] = status; state.appRuntimeData[targetAppKey].nodeStatusMap[nodeId] = status;
} }
@ -163,7 +171,15 @@ const ideContainerSlice = createSlice({
const appKey = getCurrentAppKey(state.currentAppData); const appKey = getCurrentAppKey(state.currentAppData);
if (appKey) { if (appKey) {
if (!state.appRuntimeData[appKey]) { if (!state.appRuntimeData[appKey]) {
state.appRuntimeData[appKey] = createDefaultAppRuntimeState(); state.appRuntimeData[appKey] = {
nodeStatusMap: {},
isRunning: false,
isPaused: false,
logs: [],
runId: '',
eventSendNodeList: [],
eventlisteneList: []
};
} }
state.appRuntimeData[appKey].isRunning = payload; state.appRuntimeData[appKey].isRunning = payload;
} }
@ -173,7 +189,15 @@ const ideContainerSlice = createSlice({
const appKey = getCurrentAppKey(state.currentAppData); const appKey = getCurrentAppKey(state.currentAppData);
if (appKey) { if (appKey) {
if (!state.appRuntimeData[appKey]) { if (!state.appRuntimeData[appKey]) {
state.appRuntimeData[appKey] = createDefaultAppRuntimeState(); state.appRuntimeData[appKey] = {
nodeStatusMap: {},
isRunning: false,
isPaused: false,
logs: [],
runId: '',
eventSendNodeList: [],
eventlisteneList: []
};
} }
state.appRuntimeData[appKey].isPaused = payload; state.appRuntimeData[appKey].isPaused = payload;
} }
@ -182,7 +206,15 @@ const ideContainerSlice = createSlice({
updateRuntimeId: (state, { payload }) => { updateRuntimeId: (state, { payload }) => {
const appKey = getCurrentAppKey(state.currentAppData); const appKey = getCurrentAppKey(state.currentAppData);
if (!state.appRuntimeData[appKey]) { if (!state.appRuntimeData[appKey]) {
state.appRuntimeData[appKey] = createDefaultAppRuntimeState(); state.appRuntimeData[appKey] = {
nodeStatusMap: {},
isRunning: false,
isPaused: false,
logs: [],
runId: '',
eventSendNodeList: [],
eventlisteneList: []
};
} }
state.appRuntimeData[appKey].runId = payload; state.appRuntimeData[appKey].runId = payload;
}, },
@ -190,18 +222,31 @@ const ideContainerSlice = createSlice({
updateEventNodeList: (state, { payload }) => { updateEventNodeList: (state, { payload }) => {
const appKey = getCurrentAppKey(state.currentAppData); const appKey = getCurrentAppKey(state.currentAppData);
if (!state.appRuntimeData[appKey]) { if (!state.appRuntimeData[appKey]) {
state.appRuntimeData[appKey] = createDefaultAppRuntimeState();
}
state.appRuntimeData[appKey] = { state.appRuntimeData[appKey] = {
...state.appRuntimeData[appKey], nodeStatusMap: {},
...payload, isRunning: false,
isPaused: false,
logs: [],
runId: '',
eventSendNodeList: [],
eventlisteneList: []
}; };
}
state.appRuntimeData[appKey] = { ...state.appRuntimeData[appKey], ...payload };
}, },
// 添加运行日志 // 添加运行日志
addRuntimeLog: (state, { payload }) => { addRuntimeLog: (state, { payload }) => {
const { log, appId } = payload; const { log, appId } = payload;
if (!state.appRuntimeData[appId]) { if (!state.appRuntimeData[appId]) {
state.appRuntimeData[appId] = createDefaultAppRuntimeState(); state.appRuntimeData[appId] = {
nodeStatusMap: {},
isRunning: false,
isPaused: false,
logs: [],
runId: '',
eventSendNodeList: [],
eventlisteneList: []
};
} }
state.appRuntimeData[appId].logs.push(log); state.appRuntimeData[appId].logs.push(log);
}, },
@ -215,8 +260,8 @@ const ideContainerSlice = createSlice({
// 更新组件编码路径 // 更新组件编码路径
updateComponentCodingPath(state, action) { updateComponentCodingPath(state, action) {
state.componentCoding = { ...action.payload }; state.componentCoding = { ...action.payload };
}, }
}, }
}); });
// 导出动作 creators // 导出动作 creators
@ -241,7 +286,7 @@ export const {
updateEventNodeList, updateEventNodeList,
addRuntimeLog, addRuntimeLog,
clearRuntimeLogs, clearRuntimeLogs,
updateComponentCodingPath, updateComponentCodingPath
} = ideContainerSlice.actions; } = ideContainerSlice.actions;
// 默认导出 reducer // 默认导出 reducer

@ -1,8 +1,14 @@
import { nodeTypeMap, registerNodeType } from '@/components/FlowEditor/node'; import { nodeTypeMap, registerNodeType } from '@/components/FlowEditor/node';
import store from '@/store/index'; import store from '@/store/index';
import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode';
import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode'; import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode';
import SwitchNode from '@/components/FlowEditor/node/switchNode/SwitchNode';
import BasicNode from '@/components/FlowEditor/node/basicNode/BasicNode';
import ImageNode from '@/components/FlowEditor/node/imageNode/ImageNode';
import CodeNode from '@/components/FlowEditor/node/codeNode/CodeNode';
import RestNode from '@/components/FlowEditor/node/restNode/RestNode';
import { updateEventNodeList } from '@/store/ideContainer'; import { updateEventNodeList } from '@/store/ideContainer';
import { resolveNodeComponent } from '@/utils/flow/nodeRegistry';
/** /**
* flow editor nodes edges * flow editor nodes edges
@ -31,14 +37,12 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
title: '开始', title: '开始',
parameters: { parameters: {
apiIns: [], apiIns: [],
apiOuts: [ apiOuts: [{ name: 'start', desc: '', dataType: '', defaultValue: '' }],
{ name: 'start', desc: '', dataType: '', defaultValue: '' },
],
dataIns: [], dataIns: [],
dataOuts: [], dataOuts: []
},
type: 'start',
}, },
type: 'start'
}
}, },
{ {
id: `end-${timestamp}`, id: `end-${timestamp}`,
@ -47,18 +51,16 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
data: { data: {
title: '结束', title: '结束',
parameters: { parameters: {
apiIns: [ apiIns: [{ name: 'end', desc: '', dataType: '', defaultValue: '' }],
{ name: 'end', desc: '', dataType: '', defaultValue: '' },
],
apiOuts: [], apiOuts: [],
dataIns: [], dataIns: [],
dataOuts: [], dataOuts: []
},
type: 'end',
},
}, },
type: 'end'
}
}
], ],
edges: [], edges: []
}; };
} }
// 否则返回空数组 // 否则返回空数组
@ -78,55 +80,44 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
try { try {
const customDef = JSON.parse(nodeConfig.component.customDef); const customDef = JSON.parse(nodeConfig.component.customDef);
// 使用展开运算符创建新数组,避免修改冻结对象 // 使用展开运算符创建新数组,避免修改冻结对象
eventlisteneList.splice(eventlisteneList.length, 0, { eventlisteneList.splice(eventlisteneList.length, 0, { [nodeId]: customDef.topic });
[nodeId]: customDef.topic,
});
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} else if ( }
nodeId.includes('EVENTSEND') && else if (nodeId.includes('EVENTSEND') && !nodeId.includes('EVENTSEND_SYNC')) {
!nodeId.includes('EVENTSEND_SYNC')
) {
try { try {
const customDef = JSON.parse(nodeConfig.component.customDef); const customDef = JSON.parse(nodeConfig.component.customDef);
// 使用展开运算符创建新数组,避免修改冻结对象 // 使用展开运算符创建新数组,避免修改冻结对象
eventSendNodeList.splice(eventSendNodeList.length, 0, { eventSendNodeList.splice(eventSendNodeList.length, 0, { [nodeId]: customDef.topic });
[nodeId]: customDef.topic,
});
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} }
if (eventlisteneList.length > 0 || eventSendNodeList.length > 0) if (eventlisteneList.length > 0 || eventSendNodeList.length > 0) store.dispatch(updateEventNodeList({
store.dispatch(
updateEventNodeList({
eventSendNodeList: [...eventSendNodeList], eventSendNodeList: [...eventSendNodeList],
eventlisteneList: [...eventlisteneList], eventlisteneList: [...eventlisteneList]
}) }));
);
else { else {
store.dispatch( store.dispatch(updateEventNodeList({
updateEventNodeList({
eventSendNodeList: [], eventSendNodeList: [],
eventlisteneList: [], eventlisteneList: []
}) }));
);
} }
// 确定节点类型 // 确定节点类型
let nodeType = 'BASIC'; let nodeType = 'BASIC';
if (nodeId.includes('start')) { if (nodeId.includes('start')) {
nodeType = 'start'; nodeType = 'start';
} else if (nodeId.includes('end')) { }
else if (nodeId.includes('end')) {
nodeType = 'end'; nodeType = 'end';
} else if ( }
nodeConfig.component?.type === 'LOOP_START' || else if (nodeConfig.component?.type === 'LOOP_START' || nodeConfig.component?.type === 'LOOP_END') {
nodeConfig.component?.type === 'LOOP_END'
) {
nodeType = 'LOOP'; nodeType = 'LOOP';
} else { }
else {
nodeType = nodeConfig.component?.type || 'BASIC'; nodeType = nodeConfig.component?.type || 'BASIC';
} }
@ -144,10 +135,10 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
apiIns: getNodeApiIns(nodeId, nodeConfig, currentProjectCompData), apiIns: getNodeApiIns(nodeId, nodeConfig, currentProjectCompData),
apiOuts: getNodeApiOuts(nodeId, nodeConfig, currentProjectCompData), apiOuts: getNodeApiOuts(nodeId, nodeConfig, currentProjectCompData),
dataIns: nodeConfig.dataIns || [], dataIns: nodeConfig.dataIns || [],
dataOuts: nodeConfig.dataOuts || [], dataOuts: nodeConfig.dataOuts || []
},
type: nodeConfig.component?.type || nodeType,
}, },
type: nodeConfig.component?.type || nodeType
}
}; };
// 添加组件标识信息 // 添加组件标识信息
@ -166,25 +157,16 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
// 注册循环节点类型 // 注册循环节点类型
if (nodeType === 'LOOP') { if (nodeType === 'LOOP') {
const nodeMap = Array.from(Object.values(nodeTypeMap).map((key) => key)); const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
if (!nodeMap.includes('LOOP')) { if (!nodeMap.includes('LOOP')) {
registerNodeType('LOOP', LoopNode, '循环'); registerNodeType('LOOP', LoopNode, '循环');
} }
} }
// 注册其他节点类型 // 注册其他节点类型
const nodeMap = Array.from(Object.values(nodeTypeMap).map((key) => key)); const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
if ( if (!nodeMap.includes(nodeType) && nodeType !== 'start' && nodeType !== 'end' && nodeType !== 'LOOP') {
!nodeMap.includes(nodeType) && registerNodeType(nodeType, getNodeComponent(nodeType), nodeConfig.componentName);
nodeType !== 'start' &&
nodeType !== 'end' &&
nodeType !== 'LOOP'
) {
registerNodeType(
nodeType,
resolveNodeComponent(nodeType),
nodeConfig.componentName
);
} }
nodes.push(node); nodes.push(node);
@ -194,15 +176,7 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
const addedEdges = new Set<string>(); const addedEdges = new Set<string>();
// 创建一个映射来存储所有连接信息 // 创建一个映射来存储所有连接信息
const connections = new Map< const connections = new Map<string, { source: string; target: string; sourceHandle: string; targetHandle: string }>();
string,
{
source: string;
target: string;
sourceHandle: string;
targetHandle: string;
}
>();
// 遍历所有节点,收集连接信息 // 遍历所有节点,收集连接信息
for (const entry of nodeEntries) { for (const entry of nodeEntries) {
@ -213,7 +187,7 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
if (nodeConfig.apiDownstream && Array.isArray(nodeConfig.apiDownstream)) { if (nodeConfig.apiDownstream && Array.isArray(nodeConfig.apiDownstream)) {
nodeConfig.apiDownstream.forEach((targetArray: string[]) => { nodeConfig.apiDownstream.forEach((targetArray: string[]) => {
if (Array.isArray(targetArray)) { if (Array.isArray(targetArray)) {
targetArray.forEach((target) => { targetArray.forEach(target => {
if (typeof target === 'string' && target.includes('$$')) { if (typeof target === 'string' && target.includes('$$')) {
const [targetNodeId, targetHandle] = target.split('$$'); const [targetNodeId, targetHandle] = target.split('$$');
const connectionKey = `${nodeId}-${targetNodeId}`; const connectionKey = `${nodeId}-${targetNodeId}`;
@ -225,16 +199,17 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
if (existing) { if (existing) {
connections.set(connectionKey, { connections.set(connectionKey, {
...existing, ...existing,
targetHandle: targetHandle, targetHandle: targetHandle
}); });
} }
} else { }
else {
// 创建新的连接信息 // 创建新的连接信息
connections.set(connectionKey, { connections.set(connectionKey, {
source: nodeId, source: nodeId,
target: targetNodeId, target: targetNodeId,
sourceHandle: '', // 将根据节点信息填充 sourceHandle: '', // 将根据节点信息填充
targetHandle: targetHandle, targetHandle: targetHandle
}); });
} }
} }
@ -247,7 +222,7 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
if (nodeConfig.apiUpstream && Array.isArray(nodeConfig.apiUpstream)) { if (nodeConfig.apiUpstream && Array.isArray(nodeConfig.apiUpstream)) {
nodeConfig.apiUpstream.forEach((sourceArray: string[]) => { nodeConfig.apiUpstream.forEach((sourceArray: string[]) => {
if (Array.isArray(sourceArray)) { if (Array.isArray(sourceArray)) {
sourceArray.forEach((source) => { sourceArray.forEach(source => {
if (typeof source === 'string' && source.includes('$$')) { if (typeof source === 'string' && source.includes('$$')) {
const [sourceNodeId, sourceHandle] = source.split('$$'); const [sourceNodeId, sourceHandle] = source.split('$$');
const connectionKey = `${sourceNodeId}-${nodeId}`; const connectionKey = `${sourceNodeId}-${nodeId}`;
@ -259,16 +234,17 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
if (existing) { if (existing) {
connections.set(connectionKey, { connections.set(connectionKey, {
...existing, ...existing,
sourceHandle: sourceHandle, sourceHandle: sourceHandle
}); });
} }
} else { }
else {
// 创建新的连接信息 // 创建新的连接信息
connections.set(connectionKey, { connections.set(connectionKey, {
source: sourceNodeId, source: sourceNodeId,
target: nodeId, target: nodeId,
sourceHandle: sourceHandle, sourceHandle: sourceHandle,
targetHandle: '', // 将根据节点信息填充 targetHandle: '' // 将根据节点信息填充
}); });
} }
} }
@ -293,13 +269,9 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
if (!finalSourceHandle) { if (!finalSourceHandle) {
if (source === 'start') { if (source === 'start') {
finalSourceHandle = 'start'; finalSourceHandle = 'start';
} else if ( }
sourceNode && else if (sourceNode && sourceNode.data && sourceNode.data.parameters &&
sourceNode.data && sourceNode.data.parameters.apiOuts && sourceNode.data.parameters.apiOuts.length > 0) {
sourceNode.data.parameters &&
sourceNode.data.parameters.apiOuts &&
sourceNode.data.parameters.apiOuts.length > 0
) {
// 查找匹配的目标句柄 // 查找匹配的目标句柄
const matchingApiOut = sourceNode.data.parameters.apiOuts.find( const matchingApiOut = sourceNode.data.parameters.apiOuts.find(
(apiOut: any) => apiOut.name === targetHandle (apiOut: any) => apiOut.name === targetHandle
@ -307,18 +279,17 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
if (matchingApiOut) { if (matchingApiOut) {
finalSourceHandle = matchingApiOut.name; finalSourceHandle = matchingApiOut.name;
} else { }
else {
// 如果没有精确匹配使用第一个apiOut // 如果没有精确匹配使用第一个apiOut
finalSourceHandle = sourceNode.data.parameters.apiOuts[0].name; finalSourceHandle = sourceNode.data.parameters.apiOuts[0].name;
} }
} else if ( }
sourceNode && else if (sourceNode && sourceNode.component && sourceNode.component.type) {
sourceNode.component &&
sourceNode.component.type
) {
// 根据节点类型获取正确的源句柄 // 根据节点类型获取正确的源句柄
finalSourceHandle = getNodeApiOutHandle(source, sourceNode); finalSourceHandle = getNodeApiOutHandle(source, sourceNode);
} else { }
else {
// 默认句柄 // 默认句柄
finalSourceHandle = 'done'; finalSourceHandle = 'done';
} }
@ -330,13 +301,9 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
if (!finalTargetHandle) { if (!finalTargetHandle) {
if (target === 'end') { if (target === 'end') {
finalTargetHandle = 'end'; finalTargetHandle = 'end';
} else if ( }
targetNode && else if (targetNode && targetNode.data && targetNode.data.parameters &&
targetNode.data && targetNode.data.parameters.apiIns && targetNode.data.parameters.apiIns.length > 0) {
targetNode.data.parameters &&
targetNode.data.parameters.apiIns &&
targetNode.data.parameters.apiIns.length > 0
) {
// 查找匹配的源句柄 // 查找匹配的源句柄
const matchingApiIn = targetNode.data.parameters.apiIns.find( const matchingApiIn = targetNode.data.parameters.apiIns.find(
(apiIn: any) => apiIn.name === sourceHandle (apiIn: any) => apiIn.name === sourceHandle
@ -344,11 +311,13 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
if (matchingApiIn) { if (matchingApiIn) {
finalTargetHandle = matchingApiIn.name; finalTargetHandle = matchingApiIn.name;
} else { }
else {
// 如果没有精确匹配使用第一个apiIn // 如果没有精确匹配使用第一个apiIn
finalTargetHandle = targetNode.data.parameters.apiIns[0].name; finalTargetHandle = targetNode.data.parameters.apiIns[0].name;
} }
} else { }
else {
// 默认句柄 // 默认句柄
finalTargetHandle = 'start'; finalTargetHandle = 'start';
} }
@ -369,8 +338,8 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
type: 'custom', type: 'custom',
lineType: 'api', lineType: 'api',
data: { data: {
lineType: 'api', lineType: 'api'
}, }
}); });
} }
} }
@ -387,12 +356,9 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
// 第一个元素是源节点和句柄信息 // 第一个元素是源节点和句柄信息
const [sourceInfo, targetInfo] = connectionGroup; const [sourceInfo, targetInfo] = connectionGroup;
if ( if (typeof sourceInfo === 'string' && sourceInfo.includes('@@') &&
typeof sourceInfo === 'string' && typeof targetInfo === 'string' && targetInfo.includes('@@')) {
sourceInfo.includes('@@') &&
typeof targetInfo === 'string' &&
targetInfo.includes('@@')
) {
const [sourceNodeId, sourceHandle] = sourceInfo.split('@@'); const [sourceNodeId, sourceHandle] = sourceInfo.split('@@');
const [targetNodeId, targetHandle] = targetInfo.split('@@'); const [targetNodeId, targetHandle] = targetInfo.split('@@');
@ -412,8 +378,8 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
type: 'custom', type: 'custom',
lineType: 'data', lineType: 'data',
data: { data: {
lineType: 'data', lineType: 'data'
}, }
}); });
} }
} }
@ -437,12 +403,12 @@ export const revertFlowData = (nodes: any[], edges: any[]) => {
const flowData: any = { const flowData: any = {
id: 'main', id: 'main',
nodeConfigs: [], nodeConfigs: [],
lineConfigs: [], lineConfigs: []
}; };
// 转换节点数据 // 转换节点数据
if (nodes && nodes.length > 0) { if (nodes && nodes.length > 0) {
flowData.nodeConfigs = nodes.map((node) => { flowData.nodeConfigs = nodes.map(node => {
// 确定 nodeId 和 nodeName // 确定 nodeId 和 nodeName
const nodeId = node.id || node.name; const nodeId = node.id || node.name;
const nodeName = node.data?.title || nodeId; const nodeName = node.data?.title || nodeId;
@ -452,20 +418,21 @@ export const revertFlowData = (nodes: any[], edges: any[]) => {
// 特殊处理 start 和 end 节点 // 特殊处理 start 和 end 节点
if (nodeId.includes('start')) { if (nodeId.includes('start')) {
nodeType = 'start'; nodeType = 'start';
} else if (nodeId.includes('end')) { }
else if (nodeId.includes('end')) {
nodeType = 'end'; nodeType = 'end';
} }
// 构造 x6 数据(位置信息) // 构造 x6 数据(位置信息)
const x6 = JSON.stringify({ const x6 = JSON.stringify({
position: node.position, position: node.position
}); });
// 构造 nodeConfig 对象 // 构造 nodeConfig 对象
const nodeConfig: any = { const nodeConfig: any = {
nodeId, nodeId,
nodeName, nodeName,
x6, x6
}; };
// 处理 component 信息 // 处理 component 信息
@ -473,20 +440,18 @@ export const revertFlowData = (nodes: any[], edges: any[]) => {
nodeConfig.component = { nodeConfig.component = {
type: nodeType, type: nodeType,
compIdentifier: node.data.component.compIdentifier || '', compIdentifier: node.data.component.compIdentifier || '',
compInstanceIdentifier: compInstanceIdentifier: node.data.component.compInstanceIdentifier || '',
node.data.component.compInstanceIdentifier || '', compId: node.data.compId || ''
compId: node.data.compId || '',
}; };
if (node.data.component?.customDef) if (node.data.component?.customDef) nodeConfig.component.customDef = node.data.component.customDef;
nodeConfig.component.customDef = node.data.component.customDef; }
} else if (nodeType !== 'start' && nodeType !== 'end') { else if (nodeType !== 'start' && nodeType !== 'end') {
// 对于非 start/end 节点,添加基本的 component 信息 // 对于非 start/end 节点,添加基本的 component 信息
nodeConfig.component = { nodeConfig.component = {
type: nodeType, type: nodeType
}; };
} }
if (['BASIC', 'SUB'].includes(nodeType)) if (['BASIC', 'SUB'].includes(nodeType)) nodeConfig.component.compId = node.data.compId || '';
nodeConfig.component.compId = node.data.compId || '';
// 处理参数信息 // 处理参数信息
const parameters = node.data?.parameters || {}; const parameters = node.data?.parameters || {};
@ -498,7 +463,7 @@ export const revertFlowData = (nodes: any[], edges: any[]) => {
desc: input.desc, desc: input.desc,
dataType: input.dataType, dataType: input.dataType,
defaultValue: input.defaultValue, defaultValue: input.defaultValue,
arrayType: input.arrayType || null, arrayType: input.arrayType || null
})); }));
} }
@ -509,7 +474,7 @@ export const revertFlowData = (nodes: any[], edges: any[]) => {
desc: output.desc, desc: output.desc,
dataType: output.dataType, dataType: output.dataType,
defaultValue: output.defaultValue, defaultValue: output.defaultValue,
arrayType: output.arrayType || null, arrayType: output.arrayType || null
})); }));
} }
@ -521,34 +486,22 @@ export const revertFlowData = (nodes: any[], edges: any[]) => {
if (edges && edges.length > 0) { if (edges && edges.length > 0) {
flowData.lineConfigs = edges.map((edge, index) => { flowData.lineConfigs = edges.map((edge, index) => {
// 查找源节点和目标节点以确定连线类型 // 查找源节点和目标节点以确定连线类型
const sourceNode = nodes.find((node) => node.id === edge.source); const sourceNode = nodes.find(node => node.id === edge.source);
const targetNode = nodes.find((node) => node.id === edge.target); const targetNode = nodes.find(node => node.id === edge.target);
let lineType = 'DATA'; // 默认为DATA类型 let lineType = 'DATA'; // 默认为DATA类型
// 判断是否为CONVERT类型的连线 // 判断是否为CONVERT类型的连线
if ( if (targetNode && ['JSONCONVERT', 'JSON2STR', 'STR2JSON'].includes(targetNode.type)) {
targetNode &&
['JSONCONVERT', 'JSON2STR', 'STR2JSON'].includes(targetNode.type)
) {
lineType = 'CONVERT'; lineType = 'CONVERT';
} }
// 判断是否为API类型的连线 // 判断是否为API类型的连线
else if ( else if (edge.sourceHandle && (edge.sourceHandle === 'apiOuts' ||
edge.sourceHandle && sourceNode?.data?.parameters?.apiOuts?.some((out: any) => (out.name || out.id) === edge.sourceHandle))) {
(edge.sourceHandle === 'apiOuts' ||
sourceNode?.data?.parameters?.apiOuts?.some(
(out: any) => (out.name || out.id) === edge.sourceHandle
))
) {
lineType = 'API'; lineType = 'API';
} else if ( }
edge.targetHandle && else if (edge.targetHandle && (edge.targetHandle === 'apiIns' ||
(edge.targetHandle === 'apiIns' || targetNode?.data?.parameters?.apiIns?.some((inp: any) => (inp.name || inp.id) === edge.targetHandle))) {
targetNode?.data?.parameters?.apiIns?.some(
(inp: any) => (inp.name || inp.id) === edge.targetHandle
))
) {
lineType = 'API'; lineType = 'API';
} }
@ -557,12 +510,12 @@ export const revertFlowData = (nodes: any[], edges: any[]) => {
lineType, // 添加lineType属性 lineType, // 添加lineType属性
prev: { prev: {
nodeId: edge.source, nodeId: edge.source,
endpointId: edge.sourceHandle || 'done', // 默认使用 'done' endpointId: edge.sourceHandle || 'done' // 默认使用 'done'
}, },
next: { next: {
nodeId: edge.target, nodeId: edge.target,
endpointId: edge.targetHandle || 'start', // 默认使用 'start' endpointId: edge.targetHandle || 'start' // 默认使用 'start'
}, }
}; };
}); });
} }
@ -577,24 +530,20 @@ export const revertFlowData = (nodes: any[], edges: any[]) => {
* @param complexKV - 使id {ID/nodeIdsub_ID} * @param complexKV - 使id {ID/nodeIdsub_ID}
* @returns convertFlowData * @returns convertFlowData
*/ */
export const reverseConvertFlowData = ( export const reverseConvertFlowData = (nodes: any[], edges: any[], complexKV: any) => {
nodes: any[],
edges: any[],
complexKV: any
) => {
// 初始化返回的数据结构 // 初始化返回的数据结构
const flowData: any = {}; const flowData: any = {};
// 转换节点数据 // 转换节点数据
if (nodes && nodes.length > 0) { if (nodes && nodes.length > 0) {
nodes.forEach((node) => { nodes.forEach(node => {
const nodeId = node.id; const nodeId = node.id;
// 构造节点配置对象 // 构造节点配置对象
const nodeConfig: any = { const nodeConfig: any = {
id: nodeId, id: nodeId,
componentName: node.data?.title || nodeId, componentName: node.data?.title || nodeId,
position: node.position || { x: 0, y: 0 }, position: node.position || { x: 0, y: 0 }
}; };
// 处理 component 信息 // 处理 component 信息
@ -621,14 +570,16 @@ export const reverseConvertFlowData = (
dataIns: node.data.parameters.dataIns, dataIns: node.data.parameters.dataIns,
dataOuts: node.data.parameters.dataOuts, dataOuts: node.data.parameters.dataOuts,
subflowId: subflowId, subflowId: subflowId,
name: node.data.title, name: node.data.title
}), })
}; };
} else if (node.data?.component) { }
else if (node.data?.component) {
nodeConfig.component = { ...node.data.component }; nodeConfig.component = { ...node.data.component };
} else { }
else {
nodeConfig.component = { nodeConfig.component = {
type: node.type, type: node.type
}; };
} }
@ -638,28 +589,32 @@ export const reverseConvertFlowData = (
// 处理 apiIns输入API // 处理 apiIns输入API
if (parameters.apiIns && parameters.apiIns.length > 0) { if (parameters.apiIns && parameters.apiIns.length > 0) {
nodeConfig.apiIns = parameters.apiIns; nodeConfig.apiIns = parameters.apiIns;
} else { }
else {
nodeConfig.apiIns = []; nodeConfig.apiIns = [];
} }
// 处理 apiOuts输出API // 处理 apiOuts输出API
if (parameters.apiOuts && parameters.apiOuts.length > 0) { if (parameters.apiOuts && parameters.apiOuts.length > 0) {
nodeConfig.apiOuts = parameters.apiOuts; nodeConfig.apiOuts = parameters.apiOuts;
} else { }
else {
nodeConfig.apiOuts = []; nodeConfig.apiOuts = [];
} }
// 处理 dataIns输入数据 // 处理 dataIns输入数据
if (parameters.dataIns && parameters.dataIns.length > 0) { if (parameters.dataIns && parameters.dataIns.length > 0) {
nodeConfig.dataIns = parameters.dataIns; nodeConfig.dataIns = parameters.dataIns;
} else { }
else {
nodeConfig.dataIns = []; nodeConfig.dataIns = [];
} }
// 处理 dataOuts输出数据 // 处理 dataOuts输出数据
if (parameters.dataOuts && parameters.dataOuts.length > 0) { if (parameters.dataOuts && parameters.dataOuts.length > 0) {
nodeConfig.dataOuts = parameters.dataOuts; nodeConfig.dataOuts = parameters.dataOuts;
} else { }
else {
nodeConfig.dataOuts = []; nodeConfig.dataOuts = [];
} }
@ -677,45 +632,33 @@ export const reverseConvertFlowData = (
// 处理连接关系 // 处理连接关系
if (edges && edges.length > 0) { if (edges && edges.length > 0) {
// 分析边的连接关系 // 分析边的连接关系
edges.forEach((edge) => { edges.forEach(edge => {
const sourceNode = edge.source; const sourceNode = edge.source;
const targetNode = edge.target; const targetNode = edge.target;
const sourceHandle = edge.sourceHandle || 'done'; const sourceHandle = edge.sourceHandle || 'done';
const targetHandle = edge.targetHandle || 'start'; const targetHandle = edge.targetHandle || 'start';
// 确定连接类型API 还是 DATA // 确定连接类型API 还是 DATA
const sourceNodeData = nodes.find((n) => n.id === sourceNode); const sourceNodeData = nodes.find(n => n.id === sourceNode);
const targetNodeData = nodes.find((n) => n.id === targetNode); const targetNodeData = nodes.find(n => n.id === targetNode);
const isApiConnection = const isApiConnection =
sourceNodeData?.data?.parameters?.apiOuts?.some( (sourceNodeData?.data?.parameters?.apiOuts?.some((out: any) => (out.name || out.id) === sourceHandle)) ||
(out: any) => (out.name || out.id) === sourceHandle (targetNodeData?.data?.parameters?.apiIns?.some((inp: any) => (inp.name || inp.id) === targetHandle)) ||
) || sourceHandle === 'start' || targetHandle === 'end' ||
targetNodeData?.data?.parameters?.apiIns?.some( sourceHandle === 'end' || targetHandle === 'start';
(inp: any) => (inp.name || inp.id) === targetHandle
) ||
sourceHandle === 'start' ||
targetHandle === 'end' ||
sourceHandle === 'end' ||
targetHandle === 'start';
if (isApiConnection) { if (isApiConnection) {
// API 连接 // API 连接
// 添加下游连接 // 添加下游连接
flowData[sourceNode].apiDownstream.push([ flowData[sourceNode].apiDownstream.push([`${targetNode}$$${targetHandle}`]);
`${targetNode}$$${targetHandle}`,
]);
// 添加上游连接 // 添加上游连接
flowData[targetNode].apiUpstream.push([ flowData[targetNode].apiUpstream.push([`${sourceNode}$$${sourceHandle}`]);
`${sourceNode}$$${sourceHandle}`, }
]); else {
} else {
// 数据连接 // 数据连接
const dataConnection = [ const dataConnection = [`${sourceNode}@@${sourceHandle}`, `${targetNode}@@${targetHandle}`];
`${sourceNode}@@${sourceHandle}`,
`${targetNode}@@${targetHandle}`,
];
flowData[sourceNode].dataDownstream.push(dataConnection); flowData[sourceNode].dataDownstream.push(dataConnection);
flowData[targetNode].dataUpstream.push(dataConnection); flowData[targetNode].dataUpstream.push(dataConnection);
} }
@ -726,67 +669,59 @@ export const reverseConvertFlowData = (
}; };
// 获取节点的API输入参数 // 获取节点的API输入参数
const getNodeApiIns = ( const getNodeApiIns = (nodeId: string, nodeConfig: any, currentProjectCompData: any[]) => {
nodeId: string,
nodeConfig: any,
currentProjectCompData: any[]
) => {
// JSON2STR 和 STR2JSON 不需要 API 输入 // JSON2STR 和 STR2JSON 不需要 API 输入
if ( if (nodeConfig.component?.type === 'JSON2STR' || nodeConfig.component?.type === 'STR2JSON') {
nodeConfig.component?.type === 'JSON2STR' ||
nodeConfig.component?.type === 'STR2JSON'
) {
return []; return [];
} }
// 对于特定类型的节点使用预定义值 // 对于特定类型的节点使用预定义值
if (nodeConfig.component?.type === 'LOOP_START') { if (nodeConfig.component?.type === 'LOOP_START') {
return [{ name: 'start', desc: '', dataType: '', defaultValue: '' }]; return [{ name: 'start', desc: '', dataType: '', defaultValue: '' }];
} else if (nodeConfig.component?.type === 'LOOP_END') { }
else if (nodeConfig.component?.type === 'LOOP_END') {
return [{ name: 'continue', desc: '', dataType: '', defaultValue: '' }]; return [{ name: 'continue', desc: '', dataType: '', defaultValue: '' }];
} else if (nodeId.includes('end')) { }
else if (nodeId.includes('end')) {
return [{ name: 'end', desc: '', dataType: '', defaultValue: '' }]; return [{ name: 'end', desc: '', dataType: '', defaultValue: '' }];
} else if (nodeConfig.component?.type === 'SUB') { }
else if (nodeConfig.component?.type === 'SUB') {
return [{ name: 'start', desc: '', dataType: '', defaultValue: '' }]; return [{ name: 'start', desc: '', dataType: '', defaultValue: '' }];
} else { }
const comp = currentProjectCompData.filter((item) => { else {
const comp = currentProjectCompData.filter(item => {
return (item.id || item?.comp?.id) === nodeConfig?.component?.compId; return (item.id || item?.comp?.id) === nodeConfig?.component?.compId;
}); });
if (comp && comp.length > 0) { if (comp && comp.length > 0) {
const apiIns = comp[0]?.def?.apis || comp[0]?.comp?.def?.apis || []; const apiIns = comp[0]?.def?.apis || comp[0]?.comp?.def?.apis || [];
return apiIns.map((v) => { return apiIns.map(v => {
return { return {
...v, ...v,
name: v.id, name: v.id,
desc: v.desc, desc: v.desc,
dataType: v?.dataType || '', dataType: v?.dataType || '',
defaultValue: v?.defaultValue || '', defaultValue: v?.defaultValue || ''
}; };
}); });
} else { }
else {
return [{ name: 'start', desc: '', dataType: '', defaultValue: '' }]; return [{ name: 'start', desc: '', dataType: '', defaultValue: '' }];
} }
} }
}; };
// 获取节点的API输出参数 // 获取节点的API输出参数
const getNodeApiOuts = ( const getNodeApiOuts = (nodeId: string, nodeConfig: any, currentProjectCompData: any[]) => {
nodeId: string,
nodeConfig: any,
currentProjectCompData: any[]
) => {
// JSON2STR 和 STR2JSON 不需要 API 输出 // JSON2STR 和 STR2JSON 不需要 API 输出
if ( if (nodeConfig.component?.type === 'JSON2STR' || nodeConfig.component?.type === 'STR2JSON') {
nodeConfig.component?.type === 'JSON2STR' ||
nodeConfig.component?.type === 'STR2JSON'
) {
return []; return [];
} }
// 对于特定类型的节点使用预定义值 // 对于特定类型的节点使用预定义值
if (nodeConfig.component?.type === 'LOOP_START') { if (nodeConfig.component?.type === 'LOOP_START') {
return [{ name: 'done', desc: '', dataType: '', defaultValue: '' }]; return [{ name: 'done', desc: '', dataType: '', defaultValue: '' }];
} else if (nodeConfig.component?.type === 'LOOP_END') { }
else if (nodeConfig.component?.type === 'LOOP_END') {
// 从customDef中获取apiOutIds数组 // 从customDef中获取apiOutIds数组
try { try {
const customDef = JSON.parse(nodeConfig.component?.customDef || '{}'); const customDef = JSON.parse(nodeConfig.component?.customDef || '{}');
@ -796,14 +731,15 @@ const getNodeApiOuts = (
const breakIndex = apiOutIds.indexOf('break'); const breakIndex = apiOutIds.indexOf('break');
if (breakIndex !== -1) { if (breakIndex !== -1) {
// 返回从"break"开始的所有项 // 返回从"break"开始的所有项
return apiOutIds.slice(breakIndex).map((id) => ({ return apiOutIds.slice(breakIndex).map(id => ({
name: id, name: id,
id: id, id: id,
desc: id, desc: id,
dataType: '', dataType: '',
defaultValue: '', defaultValue: ''
})); }));
} else { }
else {
// 如果没有找到"break",则返回默认值 // 如果没有找到"break",则返回默认值
return [{ name: 'break', desc: '', dataType: '', defaultValue: '' }]; return [{ name: 'break', desc: '', dataType: '', defaultValue: '' }];
} }
@ -811,7 +747,8 @@ const getNodeApiOuts = (
// 解析失败时返回默认值 // 解析失败时返回默认值
return [{ name: 'break', desc: '', dataType: '', defaultValue: '' }]; return [{ name: 'break', desc: '', dataType: '', defaultValue: '' }];
} }
} else if (nodeConfig.component?.type === 'SWITCH') { }
else if (nodeConfig.component?.type === 'SWITCH') {
// 从customDef中获取apiOutIds数组 // 从customDef中获取apiOutIds数组
try { try {
const customDef = JSON.parse(nodeConfig.component?.customDef || '{}'); const customDef = JSON.parse(nodeConfig.component?.customDef || '{}');
@ -821,14 +758,15 @@ const getNodeApiOuts = (
const breakIndex = apiOutIds.indexOf('default'); const breakIndex = apiOutIds.indexOf('default');
if (breakIndex !== -1) { if (breakIndex !== -1) {
// 返回从"break"开始的所有项 // 返回从"break"开始的所有项
return apiOutIds.slice(breakIndex).map((id) => ({ return apiOutIds.slice(breakIndex).map(id => ({
name: id, name: id,
id: id, id: id,
desc: id, desc: id,
dataType: '', dataType: '',
defaultValue: '', defaultValue: ''
})); }));
} else { }
else {
// 如果没有找到"break",则返回默认值 // 如果没有找到"break",则返回默认值
return [{ name: 'default', desc: '', dataType: '', defaultValue: '' }]; return [{ name: 'default', desc: '', dataType: '', defaultValue: '' }];
} }
@ -836,25 +774,26 @@ const getNodeApiOuts = (
// 解析失败时返回默认值 // 解析失败时返回默认值
return [{ name: 'done', desc: '', dataType: '', defaultValue: '' }]; return [{ name: 'done', desc: '', dataType: '', defaultValue: '' }];
} }
} else if (nodeId.includes('start')) { }
else if (nodeId.includes('start')) {
return [{ name: 'start', desc: '', dataType: '', defaultValue: '' }]; return [{ name: 'start', desc: '', dataType: '', defaultValue: '' }];
} else if (nodeId.includes('end')) { }
else if (nodeId.includes('end')) {
return []; return [];
} else if (nodeConfig.component?.type === 'SUB') { }
else if (nodeConfig.component?.type === 'SUB') {
return [{ name: 'done', desc: '', dataType: '', defaultValue: '' }]; return [{ name: 'done', desc: '', dataType: '', defaultValue: '' }];
} else { }
const comp = currentProjectCompData.filter( else {
(item) => item.id === nodeConfig?.component?.compId const comp = currentProjectCompData.filter(item => item.id === nodeConfig?.component?.compId);
);
if (comp && comp.length > 0) { if (comp && comp.length > 0) {
return [ return [{
{
...comp[0].def?.apiOut, ...comp[0].def?.apiOut,
dataType: '', dataType: '',
defaultValue: '', defaultValue: ''
}, }];
]; }
} else { else {
return [{ name: 'done', desc: '', dataType: '', defaultValue: '' }]; return [{ name: 'done', desc: '', dataType: '', defaultValue: '' }];
} }
} }
@ -864,11 +803,14 @@ const getNodeApiOuts = (
const getNodeApiOutHandle = (nodeId: string, nodeConfig: any) => { const getNodeApiOutHandle = (nodeId: string, nodeConfig: any) => {
if (nodeConfig.component?.type === 'LOOP_START') { if (nodeConfig.component?.type === 'LOOP_START') {
return 'done'; return 'done';
} else if (nodeConfig.component?.type === 'LOOP_END') { }
else if (nodeConfig.component?.type === 'LOOP_END') {
return 'break'; return 'break';
} else if (nodeId.includes('start')) { }
else if (nodeId.includes('start')) {
return 'start'; return 'start';
} else if (nodeId.includes('end')) { }
else if (nodeId.includes('end')) {
return 'end'; return 'end';
} }
return 'done'; return 'done';
@ -883,17 +825,13 @@ const getCurrentProjectStoreData = () => {
// 处理projectCompDto中的数据 // 处理projectCompDto中的数据
if (compData.projectCompDto) { if (compData.projectCompDto) {
const { const { mineComp = [], pubComp = [], teamWorkComp = [] } = compData.projectCompDto;
mineComp = [],
pubComp = [],
teamWorkComp = [],
} = compData.projectCompDto;
// 添加mineComp数据 // 添加mineComp数据
mineComp.forEach((item: any) => { mineComp.forEach((item: any) => {
result.push({ result.push({
...item, ...item,
type: 'mineComp', type: 'mineComp'
}); });
}); });
@ -901,7 +839,7 @@ const getCurrentProjectStoreData = () => {
pubComp.forEach((item: any) => { pubComp.forEach((item: any) => {
result.push({ result.push({
...item, ...item,
type: 'pubComp', type: 'pubComp'
}); });
}); });
@ -909,7 +847,7 @@ const getCurrentProjectStoreData = () => {
teamWorkComp.forEach((item: any) => { teamWorkComp.forEach((item: any) => {
result.push({ result.push({
...item, ...item,
type: 'teamWorkComp', type: 'teamWorkComp'
}); });
}); });
} }
@ -922,7 +860,7 @@ const getCurrentProjectStoreData = () => {
mineFlow.forEach((item: any) => { mineFlow.forEach((item: any) => {
result.push({ result.push({
...item, ...item,
type: 'mineFlow', type: 'mineFlow'
}); });
}); });
@ -930,7 +868,7 @@ const getCurrentProjectStoreData = () => {
pubFlow.forEach((item: any) => { pubFlow.forEach((item: any) => {
result.push({ result.push({
...item, ...item,
type: 'pubFlow', type: 'pubFlow'
}); });
}); });
} }
@ -943,13 +881,7 @@ const getCurrentProjectStoreData = () => {
const compLibsData = sessionStorage.getItem(compLibsKey); const compLibsData = sessionStorage.getItem(compLibsKey);
if (compLibsData) { if (compLibsData) {
const { const { myLibs = [], pubLibs = [], teamLibs = [], myFlow = [], pubFlow = [] } = JSON.parse(compLibsData);
myLibs = [],
pubLibs = [],
teamLibs = [],
myFlow = [],
pubFlow = [],
} = JSON.parse(compLibsData);
// 处理 myLibs我的组件库 // 处理 myLibs我的组件库
if (Array.isArray(myLibs)) { if (Array.isArray(myLibs)) {
@ -958,7 +890,7 @@ const getCurrentProjectStoreData = () => {
lib.children.forEach((item: any) => { lib.children.forEach((item: any) => {
result.push({ result.push({
...item, ...item,
type: 'mineComp', type: 'mineComp'
}); });
}); });
} }
@ -972,7 +904,7 @@ const getCurrentProjectStoreData = () => {
lib.children.forEach((item: any) => { lib.children.forEach((item: any) => {
result.push({ result.push({
...item, ...item,
type: 'pubComp', type: 'pubComp'
}); });
}); });
} }
@ -986,7 +918,7 @@ const getCurrentProjectStoreData = () => {
lib.children.forEach((item: any) => { lib.children.forEach((item: any) => {
result.push({ result.push({
...item, ...item,
type: 'teamWorkComp', type: 'teamWorkComp'
}); });
}); });
} }
@ -998,7 +930,7 @@ const getCurrentProjectStoreData = () => {
myFlow.forEach((item: any) => { myFlow.forEach((item: any) => {
result.push({ result.push({
...item, ...item,
type: 'mineFlow', type: 'mineFlow'
}); });
}); });
} }
@ -1008,7 +940,7 @@ const getCurrentProjectStoreData = () => {
pubFlow.forEach((item: any) => { pubFlow.forEach((item: any) => {
result.push({ result.push({
...item, ...item,
type: 'pubFlow', type: 'pubFlow'
}); });
}); });
} }
@ -1019,3 +951,22 @@ const getCurrentProjectStoreData = () => {
} }
return result; return result;
}; };
// 根据节点类型获取对应的节点组件
const getNodeComponent = (nodeType: string) => {
switch (nodeType) {
case 'BASIC':
case 'SUB':
return BasicNode;
case 'SWITCH':
return SwitchNode;
case 'IMAGE':
return ImageNode;
case 'CODE':
return CodeNode;
case 'REST':
return RestNode;
default:
return LocalNode;
}
};

@ -1,54 +0,0 @@
import { Edge } from '@xyflow/react';
export const resolveInsertedNodeHandles = (node: any) => {
let sourceHandle = 'done';
let targetHandle = 'start';
if (node?.data?.parameters) {
const { apiOuts, apiIns } = node.data.parameters;
if (apiOuts && apiOuts.length > 0) {
sourceHandle = apiOuts[0].name || apiOuts[0].id || sourceHandle;
}
if (apiIns && apiIns.length > 0) {
targetHandle = apiIns[0].name || apiIns[0].id || targetHandle;
}
}
return { sourceHandle, targetHandle };
};
export const buildInsertedNodeEdges = (params: {
baseEdges: Edge[];
removedEdgeId: string;
sourceId: string;
sourceHandle?: string;
targetId: string;
targetHandle?: string;
insertedNodeId: string;
insertedNodeSourceHandle: string;
insertedNodeTargetHandle: string;
}): Edge[] => [
...params.baseEdges.filter((e) => e.id !== params.removedEdgeId),
{
id: `e${params.sourceId}-${params.insertedNodeId}`,
source: params.sourceId,
target: params.insertedNodeId,
sourceHandle: params.sourceHandle,
targetHandle: params.insertedNodeTargetHandle,
type: 'custom',
lineType: 'api',
data: { lineType: 'api' },
} as Edge,
{
id: `e${params.insertedNodeId}-${params.targetId}`,
source: params.insertedNodeId,
target: params.targetId,
sourceHandle: params.insertedNodeSourceHandle,
targetHandle: params.targetHandle,
type: 'custom',
lineType: 'api',
data: { lineType: 'api' },
} as Edge,
];

@ -1,104 +0,0 @@
import { Edge } from '@xyflow/react';
export const createLoopNodePair = (position: { x: number; y: number }) => {
const loopStartNode: any = {
id: `LOOP_START-${Date.now()}`,
type: 'LOOP',
position: { x: position.x, y: position.y },
data: {
title: '循环开始',
type: 'LOOP_START',
parameters: {
apiIns: [{ name: 'start', desc: '', dataType: '', defaultValue: '' }],
apiOuts: [{ name: 'done', desc: '', dataType: '', defaultValue: '' }],
dataIns: [],
dataOuts: [],
},
component: {},
},
};
const loopEndNode: any = {
id: `LOOP_END-${Date.now()}`,
type: 'LOOP',
position: { x: position.x + 400, y: position.y },
data: {
title: '循环结束',
type: 'LOOP_END',
parameters: {
apiIns: [
{ name: 'continue', desc: '', dataType: '', defaultValue: '' },
],
apiOuts: [{ name: 'break', desc: '', dataType: '', defaultValue: '' }],
dataIns: [
{
arrayType: null,
dataType: 'INTEGER',
defaultValue: 10,
desc: '最大循环次数',
id: 'maxTime',
},
],
dataOuts: [],
},
component: {
type: 'LOOP_END',
customDef: JSON.stringify({
apiOutIds: ['continue', 'break'],
conditions: [],
loopStartNodeId: loopStartNode.id,
}),
loopStartNodeId: loopStartNode.id,
},
},
};
loopStartNode.data.component = {
type: 'LOOP_START',
customDef: JSON.stringify({ loopEndNodeId: loopEndNode.id }),
};
return { loopStartNode, loopEndNode };
};
export const createLoopGroupEdge = (
loopStartId: string,
loopEndId: string
): Edge => ({
id: `${loopStartId}-${loopEndId}-group`,
source: loopStartId,
target: loopEndId,
sourceHandle: `${loopStartId}-group`,
targetHandle: `${loopEndId}-group`,
type: 'custom',
});
export const createLoopInsertConnectionEdges = (params: {
sourceId: string;
sourceHandle: string;
targetId: string;
targetHandle: string;
loopStartId: string;
loopEndId: string;
}): Edge[] => [
{
id: `e${params.sourceId}-${params.loopStartId}`,
source: params.sourceId,
target: params.loopStartId,
sourceHandle: params.sourceHandle,
targetHandle: 'start',
type: 'custom',
lineType: 'api',
data: { lineType: 'api' },
} as Edge,
{
id: `e${params.loopEndId}-${params.targetId}`,
source: params.loopEndId,
target: params.targetId,
sourceHandle: 'break',
targetHandle: params.targetHandle,
type: 'custom',
lineType: 'api',
data: { lineType: 'api' },
} as Edge,
];

@ -1,79 +0,0 @@
import { Node } from '@xyflow/react';
interface FlowNodeDefinition {
nodeName: string;
data: any;
id?: string;
flowHousVO?: {
id?: string;
};
}
type EventIdMode = 'id' | 'eventIdOptional';
export const createFlowNode = (
nodeType: string,
nodeDefinition: FlowNodeDefinition,
position: { x: number; y: number }
): Node => {
const node: any = {
id: `${nodeType}-${Date.now()}`,
type: nodeType,
position,
data: {
...nodeDefinition.data,
title: nodeDefinition.nodeName,
type: nodeType,
},
};
if (nodeDefinition.id || nodeDefinition?.flowHousVO?.id) {
node.data.compId = nodeDefinition.id || nodeDefinition?.flowHousVO?.id;
}
return node;
};
export const attachFlowNodeComponent = (
node: any,
nodeType: string,
nodeDefinition: FlowNodeDefinition,
eventList: any[],
eventIdMode: EventIdMode
) => {
if (nodeType === 'SWITCH') {
node.data.component = {
customDef: JSON.stringify({
apiOutIds: ['default'],
conditions: [],
}),
};
} else if (nodeType === 'SUB') {
node.data.component = {
type: nodeType,
compId: node.data.compId,
};
} else if (nodeType === 'EVENTSEND' || nodeType === 'EVENTLISTENE') {
const emptyEvent = eventList.find((item) =>
item.topic.includes('**empty**')
);
node.data.component = {
type: nodeType,
customDef: {
eventId:
eventIdMode === 'eventIdOptional'
? emptyEvent?.eventId ?? null
: emptyEvent.id,
name: emptyEvent.name,
topic: emptyEvent.topic,
},
};
} else {
node.data.component = {
type: nodeType,
compId: nodeDefinition.id,
};
}
return node;
};

@ -1,61 +0,0 @@
import {
createFlowNode,
attachFlowNodeComponent,
} from '@/utils/flow/nodeFactory';
import {
ensureNodeTypeRegistered,
resolveNodeComponent,
} from '@/utils/flow/nodeRegistry';
type EventIdMode = 'id' | 'eventIdOptional';
export const resolveNodeDefinition = (
nodeList: any[],
nodeType: string,
fallbackNode?: any
) => {
return nodeList.find((n) => n.nodeType === nodeType) || fallbackNode || null;
};
export const buildRuntimeNode = (params: {
nodeType: string;
nodeDefinition: any;
position: { x: number; y: number };
eventList: any[];
eventIdMode: EventIdMode;
}) => {
const { nodeType, nodeDefinition, position, eventList, eventIdMode } = params;
const newNode = attachFlowNodeComponent(
createFlowNode(nodeType, nodeDefinition, position),
nodeType,
nodeDefinition,
eventList,
eventIdMode
);
ensureNodeTypeRegistered(
nodeType,
nodeDefinition.nodeName,
resolveNodeComponent(nodeType)
);
return newNode;
};
export const buildDroppedNode = (
nodeData: any,
position: { x: number; y: number }
) => {
ensureNodeTypeRegistered(nodeData.nodeType, nodeData.nodeName);
return {
id: `${nodeData.nodeType}-${Date.now()}`,
type: nodeData.nodeType,
position,
data: {
...nodeData.data,
title: nodeData.nodeName,
type: nodeData.nodeType,
},
};
};

@ -1,40 +0,0 @@
import BasicNode from '@/components/FlowEditor/node/basicNode/BasicNode';
import SwitchNode from '@/components/FlowEditor/node/switchNode/SwitchNode';
import ImageNode from '@/components/FlowEditor/node/imageNode/ImageNode';
import CodeNode from '@/components/FlowEditor/node/codeNode/CodeNode';
import RestNode from '@/components/FlowEditor/node/restNode/RestNode';
import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode';
import { nodeTypeMap, registerNodeType } from '@/components/FlowEditor/node';
export const resolveNodeComponent = (nodeType: string) => {
switch (nodeType) {
case 'BASIC':
case 'SUB':
return BasicNode;
case 'SWITCH':
return SwitchNode;
case 'IMAGE':
return ImageNode;
case 'CODE':
return CodeNode;
case 'REST':
return RestNode;
default:
return LocalNode;
}
};
export const ensureNodeTypeRegistered = (
nodeType: string,
nodeName: string,
component?: any
) => {
const nodeMap = Array.from(Object.values(nodeTypeMap).map((key) => key));
if (!nodeMap.includes(nodeType)) {
registerNodeType(
nodeType,
component || resolveNodeComponent(nodeType),
nodeName
);
}
};

@ -1,34 +0,0 @@
export interface FlowCurrentAppData {
id?: string;
key?: string;
}
export interface AppRuntimeState {
nodeStatusMap: Record<string, string>;
isRunning: boolean;
isPaused: boolean;
logs: any[];
runId: string;
eventSendNodeList: any[];
eventlisteneList: any[];
}
export const getCurrentAppKey = (
currentAppData: FlowCurrentAppData | null | undefined
) => {
if (!currentAppData) return null;
if (currentAppData.key && currentAppData.key.includes('sub')) {
return currentAppData.key;
}
return currentAppData.id || null;
};
export const createDefaultAppRuntimeState = (): AppRuntimeState => ({
nodeStatusMap: {},
isRunning: false,
isPaused: false,
logs: [],
runId: '',
eventSendNodeList: [],
eventlisteneList: [],
});

@ -1,22 +0,0 @@
import { Edge, Node } from '@xyflow/react';
interface FlowSnapshotDetail {
nodes: Node[];
edges: Edge[];
}
export const dispatchFlowSnapshot = (detail: FlowSnapshotDetail) => {
const event = new CustomEvent('takeSnapshot', {
detail,
});
document.dispatchEvent(event);
};
export const dispatchFlowSnapshotAsync = (
detail: FlowSnapshotDetail,
delay = 0
) => {
return setTimeout(() => {
dispatchFlowSnapshot(detail);
}, delay);
};

@ -1,5 +1,10 @@
import { defaultNodeTypes } from '@/components/FlowEditor/node/types/defaultType'; import { defaultNodeTypes } from '@/components/FlowEditor/node/types/defaultType';
import { resolveNodeComponent } from '@/utils/flow/nodeRegistry'; import BasicNode from '@/components/FlowEditor/node/basicNode/BasicNode';
import SwitchNode from '@/components/FlowEditor/node/switchNode/SwitchNode';
import ImageNode from '@/components/FlowEditor/node/imageNode/ImageNode';
import CodeNode from '@/components/FlowEditor/node/codeNode/CodeNode';
import RestNode from '@/components/FlowEditor/node/restNode/RestNode';
import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode';
// 获取handle类型 (api或data) // 获取handle类型 (api或data)
const getHandleType = (handleId: string, nodeParams: any) => { const getHandleType = (handleId: string, nodeParams: any) => {
@ -7,15 +12,8 @@ const getHandleType = (handleId: string, nodeParams: any) => {
const apiOuts = nodeParams.apiOuts || []; const apiOuts = nodeParams.apiOuts || [];
const apiIns = nodeParams.apiIns || []; const apiIns = nodeParams.apiIns || [];
if ( if (apiOuts.some((api: any) => (api?.eventId || api.name || api.id) === handleId) ||
apiOuts.some( apiIns.some((api: any) => (api?.eventId || api.name || api.id) === handleId) || (handleId.includes('loop'))) {
(api: any) => (api?.eventId || api.name || api.id) === handleId
) ||
apiIns.some(
(api: any) => (api?.eventId || api.name || api.id) === handleId
) ||
handleId.includes('loop')
) {
return 'api'; return 'api';
} }
@ -23,10 +21,8 @@ const getHandleType = (handleId: string, nodeParams: any) => {
const dataOuts = nodeParams.dataOuts || []; const dataOuts = nodeParams.dataOuts || [];
const dataIns = nodeParams.dataIns || []; const dataIns = nodeParams.dataIns || [];
if ( if (dataOuts.some((data: any) => (data.name || data.id) === handleId) ||
dataOuts.some((data: any) => (data.name || data.id) === handleId) || dataIns.some((data: any) => (data.name || data.id) === handleId)) {
dataIns.some((data: any) => (data.name || data.id) === handleId)
) {
return 'data'; return 'data';
} }
@ -35,12 +31,7 @@ const getHandleType = (handleId: string, nodeParams: any) => {
}; };
// 验证数据类型是否匹配 // 验证数据类型是否匹配
const validateDataType = ( const validateDataType = (sourceNode: defaultNodeTypes, targetNode: defaultNodeTypes, sourceHandleId: string, targetHandleId: string) => {
sourceNode: defaultNodeTypes,
targetNode: defaultNodeTypes,
sourceHandleId: string,
targetHandleId: string
) => {
const sourceParams = sourceNode.data?.parameters || {}; const sourceParams = sourceNode.data?.parameters || {};
const targetParams = targetNode.data?.parameters || {}; const targetParams = targetNode.data?.parameters || {};
@ -50,16 +41,13 @@ const validateDataType = (
const sourceDataOuts = sourceParams.dataOuts || []; const sourceDataOuts = sourceParams.dataOuts || [];
// 查找源handle的数据类型 // 查找源handle的数据类型
const sourceApi = sourceApiOuts.find( const sourceApi = sourceApiOuts.find((api: any) => api.name === sourceHandleId);
(api: any) => api.name === sourceHandleId const sourceData = sourceDataOuts.find((data: any) => data.name === sourceHandleId);
);
const sourceData = sourceDataOuts.find(
(data: any) => data.name === sourceHandleId
);
if (sourceApi) { if (sourceApi) {
sourceDataType = sourceApi.dataType || ''; sourceDataType = sourceApi.dataType || '';
} else if (sourceData) { }
else if (sourceData) {
sourceDataType = sourceData.dataType || ''; sourceDataType = sourceData.dataType || '';
} }
@ -69,16 +57,13 @@ const validateDataType = (
const targetDataIns = targetParams.dataIns || []; const targetDataIns = targetParams.dataIns || [];
// 查找目标handle的数据类型 // 查找目标handle的数据类型
const targetApi = targetApiIns.find( const targetApi = targetApiIns.find((api: any) => api.name === targetHandleId);
(api: any) => api.name === targetHandleId const targetData = targetDataIns.find((data: any) => data.name === targetHandleId);
);
const targetData = targetDataIns.find(
(data: any) => data.name === targetHandleId
);
if (targetApi) { if (targetApi) {
targetDataType = targetApi.dataType || ''; targetDataType = targetApi.dataType || '';
} else if (targetData) { }
else if (targetData) {
targetDataType = targetData.dataType || ''; targetDataType = targetData.dataType || '';
} }
@ -91,9 +76,29 @@ const validateDataType = (
return sourceDataType === targetDataType; return sourceDataType === targetDataType;
}; };
// 根据节点类型获取对应的节点组件 // 根据节点类型获取对应的节点组件
const getNodeComponent = (nodeType: string) => { const getNodeComponent = (nodeType: string) => {
return resolveNodeComponent(nodeType); switch (nodeType) {
case 'BASIC':
case 'SUB':
return BasicNode;
case 'SWITCH':
return SwitchNode;
case 'IMAGE':
return ImageNode;
case 'CODE':
return CodeNode;
case 'REST':
return RestNode;
default:
return LocalNode;
}
}; };
export { getHandleType, validateDataType, getNodeComponent };
export {
getHandleType,
validateDataType,
getNodeComponent
};
Loading…
Cancel
Save