You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
flow-playform-react/src/hooks/useFlowCallbacks.ts

1642 lines
48 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import React, { useCallback, useEffect } from 'react';
import {
applyNodeChanges,
applyEdgeChanges,
addEdge,
reconnectEdge,
Node,
Edge,
} from '@xyflow/react';
import { getAppInfoNew, setMainFlowNew, setSubFlowNew } from '@/api/appRes';
import { Message } from '@arco-design/web-react';
import {
convertFlowData,
reverseConvertFlowData,
revertFlowData,
} from '@/utils/convertFlowData';
import { localNodeData } from '@/pages/flowEditor/sideBar/config/localNodeData';
import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines';
import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode';
import {
updateCanvasDataMap,
resetNodeStatus,
updateIsRunning,
updateIsPaused,
updateEventListOld,
addRuntimeLog,
clearRuntimeLogs,
updateRuntimeId,
updateFlowData,
} from '@/store/ideContainer';
import {
validateAllNodes,
showValidationErrors,
validateAllEdges,
} from '@/components/FlowEditor/nodeEditors/validators/nodeValidators';
import { getHandleType, validateDataType } from '@/utils/flowCommon';
import { projectFlowHandle } from '@/pages/flowEditor/utils/projectFlowHandle';
import { appFLowHandle } from '@/pages/flowEditor/utils/appFlowhandle';
import {
handelEventNodeList,
updateEvent,
upDatePublish,
} from '@/pages/flowEditor/utils/common';
import { getCurrentAppKey } from '@/utils/flow/runtime';
import {
ensureNodeTypeRegistered,
resolveNodeComponent,
} from '@/utils/flow/nodeRegistry';
import {
dispatchFlowSnapshot,
dispatchFlowSnapshotAsync,
} from '@/utils/flow/snapshot';
import {
createLoopGroupEdge,
createLoopInsertConnectionEdges,
createLoopNodePair,
} from '@/utils/flow/loopFactory';
import {
buildInsertedNodeEdges,
resolveInsertedNodeHandles,
} from '@/utils/flow/edgeInsertionFactory';
import {
buildRuntimeNode,
resolveNodeDefinition,
} from '@/utils/flow/nodeOnboarding';
import {
shouldPersistCanvas,
shouldUseCachedCanvas,
} from '@/utils/flow/canvasCache';
import { Dispatch } from 'redux';
import {
getAppListBySceneId,
runMainFlow,
runSubFlow,
stopApp,
pauseApp,
resumeApp,
reRunApp,
} from '@/api/apps';
import store from '@/store';
import {
updateAppEvent,
updateAppEventChannel,
updateAppFlowData,
} from '@/api/appEvent';
import { getUrlParams, sleep } from '@/utils/common';
import {
queryEventItemBySceneIdOld,
deleteEventSub,
deleteEventPub,
} from '@/api/event';
export const useFlowCallbacks = (
nodes: Node[],
setNodes: React.Dispatch<React.SetStateAction<Node[]>>,
edges: Edge[],
setEdges: React.Dispatch<React.SetStateAction<Edge[]>>,
useDefault: boolean,
reactFlowInstance: any,
canvasDataMap: any,
dispatch: Dispatch<any>,
updateCanvasDataMapDebounced: (
dispatch: Dispatch<any>,
canvasDataMap: any,
id: string,
nodes: Node[],
edges: Edge[]
) => void,
initialData: any,
historyTimeoutRef: React.MutableRefObject<NodeJS.Timeout | null>,
setHistoryInitialized: React.Dispatch<React.SetStateAction<boolean>>,
editingNode: Node | null,
setEditingNode: React.Dispatch<React.SetStateAction<Node | null>>,
setIsEditModalOpen: React.Dispatch<React.SetStateAction<boolean>>,
edgeForNodeAdd: Edge | null,
setEdgeForNodeAdd: React.Dispatch<React.SetStateAction<Edge | null>>,
positionForNodeAdd: { x: number; y: number } | null,
setPositionForNodeAdd: React.Dispatch<
React.SetStateAction<{ x: number; y: number } | null>
>,
setIsDelete: React.Dispatch<React.SetStateAction<boolean>>
) => {
const { getGuidelines, clearGuidelines } = useAlignmentGuidelines();
// 辅助函数:获取当前应用/子流程的唯一标识符
const getCurrentFlowAppKey = useCallback(() => {
const { currentAppData } = store.getState().ideContainer;
return getCurrentAppKey(currentAppData) || initialData?.appId;
}, [initialData]);
const refreshAppList = useCallback(() => {
document.dispatchEvent(new CustomEvent('refreshAppList'));
}, []);
// region 画布操作
// 节点变更处理,添加防抖机制
const onNodesChange = useCallback(
(changes: any) => {
// 深度克隆节点数组以避免修改冻结的对象
const clonedNodes = JSON.parse(JSON.stringify(nodes));
const newNodes = applyNodeChanges(changes, clonedNodes);
setNodes(newNodes);
// 如果需要在节点变化时执行某些操作,可以在这里添加
// 只有当变化是节点位置变化时才不立即记录历史
const isPositionChange = changes.some(
(change: any) => change.type === 'position' && change.dragging === false
);
// 如果是位置变化结束或者不是位置变化,则记录历史
if (
isPositionChange ||
!changes.some((change: any) => change.type === 'position')
) {
// 清除之前的定时器
if (historyTimeoutRef.current) {
clearTimeout(historyTimeoutRef.current);
}
// 设置新的定时器,延迟记录历史记录
historyTimeoutRef.current = dispatchFlowSnapshotAsync(
{ nodes: [...newNodes], edges: [...edges] },
100
);
}
},
[nodes, edges]
);
// 边变更处理
const onEdgesChange = useCallback(
(changes: any) => {
const newEdges = applyEdgeChanges(changes, edges);
setEdges(newEdges);
// 如果需要在边变化时执行某些操作,可以在这里添加
// 边的变化立即记录历史
dispatchFlowSnapshot({ nodes: [...nodes], edges: [...newEdges] });
},
[edges, nodes]
);
// 节点删除处理
const onNodesDelete = useCallback((deletedNodes: any) => {
setIsDelete(true);
}, []);
// 边连接处理
const onConnect = useCallback(
(params: any) => {
// 获取源节点和目标节点
const sourceNode = nodes.find((node) => node.id === params.source);
const targetNode = nodes.find((node) => node.id === params.target);
// 如果找不到节点,不创建连接
if (!sourceNode || !targetNode) {
return;
}
// 不允许链接到自己的节点上
if (sourceNode.id === targetNode.id) {
console.warn("不允许自旋链接");
return;
}
// 获取源节点和目标节点的参数信息
const sourceParams: any = sourceNode.data?.parameters || {};
const targetParams: any = targetNode.data?.parameters || {};
// 获取源handle和目标handle的类型 (api或data)
const sourceHandleType = getHandleType(params.sourceHandle, sourceParams);
const targetHandleType = getHandleType(params.targetHandle, targetParams);
// 验证连接类型是否匹配 (api只能连api, data只能连data)
if (sourceHandleType !== targetHandleType) {
console.warn('连接类型不匹配: ', sourceHandleType, targetHandleType);
return;
}
// 验证数据类型是否匹配
if (
!validateDataType(
sourceNode,
targetNode,
params.sourceHandle,
params.targetHandle
)
) {
console.warn('数据类型不匹配');
return;
}
// 如果验证通过,创建连接
setEdges((edgesSnapshot: Edge[]) => {
// 创建带有事件信息的连接
const edgeParams = { ...params, type: 'custom' };
// 添加lineType字段用于区分API连接和数据连接
edgeParams.data = {
...edgeParams.data,
lineType: sourceHandleType, // 'api' 或 'data'
};
// 对于数据类型的边需要额外验证dataIns和dataOuts中的数据类型是否一致
if (sourceHandleType === 'data') {
// 查找源节点的dataOuts中对应的数据
const sourceDataOut = (sourceParams.dataOuts || []).find(
(dataOut: any) =>
dataOut.name === params.sourceHandle ||
dataOut.id === params.sourceHandle
);
// 查找目标节点的dataIns中对应的数据
const targetDataIn = (targetParams.dataIns || []).find(
(dataIn: any) =>
dataIn.name === params.targetHandle ||
dataIn.id === params.targetHandle
);
// 验证数据类型是否一致
if (
sourceDataOut &&
targetDataIn &&
sourceDataOut.dataType !== targetDataIn.dataType
) {
console.warn(
'数据类型不匹配,源节点数据类型:',
sourceDataOut.dataType,
'目标节点数据类型:',
targetDataIn.dataType
);
Message.warning(
`数据类型不匹配,源节点数据类型: ${sourceDataOut.dataType},目标节点数据类型: ${targetDataIn.dataType}`
);
return edgesSnapshot; // 不创建连接
}
}
// 检查源节点和目标节点是否都有事件信息
const sourceApi = (sourceParams.apiOuts || []).find(
(api: any) =>
(api?.eventId || api.name || api.id) === params.sourceHandle
);
const targetApi = (targetParams.apiIns || []).find(
(api: any) =>
(api?.eventId || api.name || api.id) === params.targetHandle
);
// 如果源节点有事件topic信息
if (sourceApi && sourceApi.topic) {
// 如果目标节点的topic是**empty**或没有topic则使用源节点的事件信息
if (
!targetApi ||
!targetApi.topic ||
targetApi.topic.includes('**empty**')
) {
edgeParams.data = {
...edgeParams.data,
lineType: 'api',
displayData: {
name: sourceApi.eventName,
eventId: sourceApi.eventId,
topic: sourceApi.topic,
},
};
}
// 如果两个节点都有非empty的topic则以源节点为准
else if (
sourceApi.topic &&
targetApi.topic &&
!sourceApi.topic.includes('**empty**') &&
!targetApi.topic.includes('**empty**')
) {
edgeParams.data = {
...edgeParams.data,
lineType: 'api',
displayData: {
name: sourceApi.eventName,
eventId: sourceApi.eventId,
topic: sourceApi.topic,
},
};
}
}
// 如果源节点没有事件信息,但目标节点有
else if (
targetApi &&
targetApi.topic &&
!targetApi.topic.includes('**empty**')
) {
edgeParams.data = {
...edgeParams.data,
lineType: 'api',
displayData: {
name: targetApi.eventName,
eventId: targetApi.eventId,
topic: targetApi.topic,
},
};
}
const newEdges = addEdge(edgeParams, edgesSnapshot);
// 连接建立后记录历史
dispatchFlowSnapshotAsync({ nodes: [...nodes], edges: [...newEdges] });
return newEdges;
});
},
[nodes]
);
// 边重新连接处理
const onReconnect = useCallback(
(oldEdge: Edge, newConnection: any) => {
// 获取源节点和目标节点
const sourceNode = nodes.find((node) => node.id === newConnection.source);
const targetNode = nodes.find((node) => node.id === newConnection.target);
// 如果找不到节点,不创建连接
if (!sourceNode || !targetNode) {
return;
}
// 获取源节点和目标节点的参数信息
const sourceParams = sourceNode.data?.parameters || {};
const targetParams = targetNode.data?.parameters || {};
// 获取源handle和目标handle的类型 (api或data)
const sourceHandleType = getHandleType(
newConnection.sourceHandle,
sourceParams
);
const targetHandleType = getHandleType(
newConnection.targetHandle,
targetParams
);
// 验证连接类型是否匹配 (api只能连api, data只能连data)
if (sourceHandleType !== targetHandleType) {
console.warn('连接类型不匹配: ', sourceHandleType, targetHandleType);
return;
}
// 验证数据类型是否匹配
if (
!validateDataType(
sourceNode,
targetNode,
newConnection.sourceHandle,
newConnection.targetHandle
)
) {
console.warn('数据类型不匹配');
return;
}
// 如果验证通过,重新连接
setEdges((els) => reconnectEdge(oldEdge, newConnection, els));
},
[nodes]
);
// 拖动处理
const onDragOver = useCallback((event: React.DragEvent) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}, []);
const getNodeDefinition = useCallback(
(nodeType: string, fallbackNode?: any) =>
resolveNodeDefinition(localNodeData, nodeType, fallbackNode),
[]
);
const clearAddNodeContext = useCallback(() => {
setEdgeForNodeAdd(null);
setPositionForNodeAdd(null);
}, []);
// 添加循环节点及其开始和结束节点
const addLoopNodeWithStartEnd = useCallback(
(position: { x: number; y: number }) => {
const { loopStartNode, loopEndNode } = createLoopNodePair(position);
// 创建连接边(连接循环开始和结束节点的顶部连接点)
const newEdges = [createLoopGroupEdge(loopStartNode.id, loopEndNode.id)];
// 将未定义的节点动态追加进nodeTypes
ensureNodeTypeRegistered('LOOP', '循环', LoopNode);
setNodes((nds: Node[]) => {
const newNodes = [...nds, loopStartNode, loopEndNode];
// 添加节点后记录历史
dispatchFlowSnapshotAsync({
nodes: [...newNodes],
edges: [...edges, ...newEdges],
});
return newNodes;
});
setEdges((eds: Edge[]) => {
const updatedEdges = [...eds, ...newEdges];
// 添加边后记录历史
dispatchFlowSnapshotAsync({
nodes: [...nodes, loopStartNode, loopEndNode],
edges: [...updatedEdges],
});
return updatedEdges;
});
},
[nodes, edges]
);
const addNodeOnCanvas = useCallback(
(
nodeType: string,
nodeDefinition: any,
position: { x: number; y: number }
) => {
if (nodeType === 'LOOP') {
addLoopNodeWithStartEnd(position);
return;
}
const { eventList } = store.getState().ideContainer;
const newNode = buildRuntimeNode({
nodeType,
nodeDefinition,
position,
eventList,
eventIdMode: 'eventIdOptional',
});
setNodes((nds: Node[]) => {
const newNodes = nds.concat(newNode);
dispatchFlowSnapshotAsync({ nodes: [...newNodes], edges: [...edges] });
return newNodes;
});
},
[addLoopNodeWithStartEnd, edges]
);
// 侧边栏节点实例
const onDrop = useCallback(
(event: React.DragEvent) => {
event.preventDefault();
if (!reactFlowInstance) return;
const callBack = event.dataTransfer.getData('application/reactflow');
const nodeData = JSON.parse(callBack);
if (typeof nodeData.nodeType === 'undefined' || !nodeData.nodeType) {
return;
}
const position = reactFlowInstance.screenToFlowPosition({
x: event.clientX,
y: event.clientY,
});
const nodeDefinition = getNodeDefinition(nodeData.nodeType, nodeData);
if (!nodeDefinition) return;
addNodeOnCanvas(nodeData.nodeType, nodeDefinition, position);
},
[reactFlowInstance, getNodeDefinition, addNodeOnCanvas]
);
// 节点拖拽处理
const onNodeDrag = useCallback(
(_: any, node: Node) => {
// 获取对齐线
getGuidelines(node, nodes);
},
[nodes, getGuidelines]
);
// 节点拖拽结束处理
const onNodeDragStop = useCallback(() => {
// 清除对齐线
clearGuidelines();
}, [clearGuidelines]);
// endregion
// region 画布数据处理
// 初始化画布数据
const initializeCanvasData = useCallback(() => {
const appKey = getCurrentFlowAppKey();
const cachedCanvas = appKey ? canvasDataMap[appKey] : null;
if (shouldUseCachedCanvas({ cachedCanvas, initialData, useDefault })) {
const { edges, nodes } = cachedCanvas;
setNodes(nodes);
setEdges(edges);
} else {
// 首次进入
if (useDefault)
projectFlowHandle(
initialData,
useDefault,
setNodes,
setEdges,
dispatch,
canvasDataMap
);
else appFLowHandle(initialData, useDefault, setNodes, setEdges, dispatch);
}
// 标记历史记录已初始化
setHistoryInitialized(true);
}, [initialData, useDefault, canvasDataMap, getCurrentFlowAppKey]);
// 实时更新 canvasDataMap
const updateCanvasDataMapEffect = useCallback(() => {
const appKey = getCurrentFlowAppKey();
const { appRuntimeData } = store.getState().ideContainer;
const isCurrentAppRunning =
appKey && appRuntimeData[appKey]?.isRunning;
if (
appKey &&
!isCurrentAppRunning &&
shouldPersistCanvas({ nodes, edges })
) {
updateCanvasDataMapDebounced(
dispatch,
canvasDataMap,
appKey,
nodes,
edges
);
}
// 清理函数,在组件卸载时取消防抖
return () => {
// 取消防抖函数
};
}, [
nodes,
edges,
dispatch,
canvasDataMap,
getCurrentFlowAppKey,
]);
// 关闭编辑弹窗
const closeEditModal = useCallback(() => {
setIsEditModalOpen(false);
setEditingNode(null);
}, []);
// 保存节点编辑
const saveNodeEdit = useCallback(
(updatedData: any) => {
console.log('updatedData:', updatedData);
const updatedNodes = nodes.map((node) => {
if (node.id === editingNode?.id) {
return {
...node,
data: { ...node.data, ...updatedData },
};
}
return node;
});
setNodes(updatedNodes);
closeEditModal();
},
[nodes, editingNode, closeEditModal]
);
// 编辑节点
const editNode = useCallback((node: Node) => {
setEditingNode(node);
setIsEditModalOpen(true);
}, []);
// 编辑边
const editEdge = useCallback((edge: Edge) => {
// 这里可以实现边编辑逻辑
console.log('编辑边:', edge);
}, []);
// 复制节点(支持多节点和多边)
const copyNode = useCallback(
(node: Node) => {
// 获取所有选中的节点(包括当前节点)
const selectedNodes = nodes.filter((n) => n.selected || n.id === node.id);
// 过滤掉开始和结束节点
const nodesToCopy = selectedNodes.filter(
(n) => n.type !== 'start' && n.type !== 'end'
);
if (nodesToCopy.length === 0) {
console.warn('没有可复制的节点(开始和结束节点不能复制)');
return;
}
// 获取这些节点之间的边
const nodeIds = new Set(nodesToCopy.map((n) => n.id));
const edgesToCopy = edges.filter(
(e) => nodeIds.has(e.source) && nodeIds.has(e.target)
);
// 清除运行时状态
const cleanedNodes = nodesToCopy.map((n) => ({
...n,
selected: false,
dragging: false,
}));
const cleanedEdges = edgesToCopy.map((e) => ({
...e,
selected: false,
}));
// 存储复制的数据
const copiedData = {
nodes: cleanedNodes,
edges: cleanedEdges,
appId: initialData?.appId,
};
localStorage.setItem('copiedFlowData', JSON.stringify(copiedData));
console.log(
`已复制 ${nodesToCopy.length} 个节点和 ${edgesToCopy.length} 条边`
);
},
[nodes, edges, initialData?.appId]
);
// 粘贴节点(支持多节点和多边)
const pasteNode = useCallback(
(position: { x: number; y: number }) => {
// 尝试读取新格式的复制数据(多节点+边)
const copiedFlowDataStr = localStorage.getItem('copiedFlowData');
// 兼容旧格式的单节点复制
const copiedNodeStr = localStorage.getItem('copiedNode');
if (!copiedFlowDataStr && !copiedNodeStr) {
console.warn('没有找到复制的节点数据');
return;
}
try {
// 处理新格式(多节点+边)
if (copiedFlowDataStr) {
const copiedData = JSON.parse(copiedFlowDataStr);
// 检查是否为同一应用
if (
copiedData.appId &&
initialData?.appId &&
copiedData.appId !== initialData.appId
) {
console.warn('不能在不同应用之间粘贴节点');
return;
}
const { nodes: copiedNodes, edges: copiedEdges } = copiedData;
if (!copiedNodes || copiedNodes.length === 0) {
console.warn('没有可粘贴的节点');
return;
}
// 计算所有节点的边界框中心点
const minX = Math.min(...copiedNodes.map((n: Node) => n.position.x));
const minY = Math.min(...copiedNodes.map((n: Node) => n.position.y));
const maxX = Math.max(...copiedNodes.map((n: Node) => n.position.x));
const maxY = Math.max(...copiedNodes.map((n: Node) => n.position.y));
const centerX = (minX + maxX) / 2;
const centerY = (minY + maxY) / 2;
// 计算偏移量,使粘贴的节点组以鼠标位置为中心
const offsetX = position.x - centerX;
const offsetY = position.y - centerY;
// 创建旧ID到新ID的映射
const idMap = new Map<string, string>();
const timestamp = Date.now();
// 创建新节点
const newNodes = copiedNodes.map((node: Node, index: number) => {
const newId = `${node.type}-${timestamp}-${index}`;
idMap.set(node.id, newId);
const newNode = {
...node,
id: newId,
position: {
x: node.position.x + offsetX,
y: node.position.y + offsetY,
},
selected: false,
dragging: false,
appId: undefined,
};
// 注册节点类型
ensureNodeTypeRegistered(
newNode.type,
(newNode.data?.title as string) || newNode.type,
resolveNodeComponent(newNode.type)
);
return newNode;
});
// 创建新边使用新的节点ID
const newEdges = copiedEdges
.map((edge: Edge, index: number) => {
const newSourceId = idMap.get(edge.source);
const newTargetId = idMap.get(edge.target);
if (!newSourceId || !newTargetId) {
console.warn('边的源节点或目标节点未找到:', edge);
return null;
}
return {
...edge,
id: `e${newSourceId}-${newTargetId}-${timestamp}-${index}`,
source: newSourceId,
target: newTargetId,
selected: false,
};
})
.filter(Boolean); // 过滤掉null值
// 更新节点和边
setNodes((nds: Node[]) => {
const updatedNodes = [...nds, ...newNodes];
// 添加节点后记录历史
dispatchFlowSnapshotAsync({
nodes: [...updatedNodes],
edges: [...edges, ...newEdges],
});
return updatedNodes;
});
setEdges((eds: Edge[]) => {
const updatedEdges = [...eds, ...newEdges];
return updatedEdges;
});
console.log(
`已粘贴 ${newNodes.length} 个节点和 ${newEdges.length} 条边`
);
}
// 处理旧格式(单节点)- 保持向后兼容
else if (copiedNodeStr) {
const copiedNode = JSON.parse(copiedNodeStr);
// 检查是否为同一应用
if (
copiedNode.appId &&
initialData?.appId &&
copiedNode.appId !== initialData.appId
) {
console.warn('不能在不同应用之间粘贴节点');
return;
}
// 创建新节点更新ID和位置
const newNode = {
...copiedNode,
id: `${copiedNode.type}-${Date.now()}`,
position,
selected: false,
dragging: false,
appId: undefined,
};
// 特殊处理循环节点
if (copiedNode.type === 'LOOP') {
setNodes((nds: Node[]) => {
const newNodes = [...nds, newNode];
dispatchFlowSnapshotAsync({
nodes: [...newNodes],
edges: [...edges],
});
return newNodes;
});
return;
}
// 注册节点类型
ensureNodeTypeRegistered(
newNode.type,
newNode.data?.title || newNode.type,
resolveNodeComponent(newNode.type)
);
setNodes((nds: Node[]) => {
const newNodes = [...nds, newNode];
dispatchFlowSnapshotAsync({
nodes: [...newNodes],
edges: [...edges],
});
return newNodes;
});
}
} catch (error) {
console.error('粘贴节点时出错:', error);
}
},
[edges, initialData?.appId, nodes]
);
// endregion
// region 节点/边操作
// 删除节点函数
const deleteNode = useCallback(
(node: Node) => {
// 在应用编排模式下useDefault为false不允许删除节点
if (!useDefault) {
console.warn('在应用编排模式下不允许删除节点');
return;
}
// 开始和结束节点不允许删除
if (node.type === 'start' || node.type === 'end') {
console.warn('开始和结束节点不允许删除');
return;
}
// 处理循环节点删除逻辑
if (node.data?.type === 'LOOP_START' || node.data?.type === 'LOOP_END') {
// 获取关联的另一个循环节点
let relatedNodeId = null;
// 类型断言将component从unknown转换为具有customDef属性的对象
const component = node.data?.component as
| { customDef?: string }
| undefined;
if (node.data?.type === 'LOOP_START' && component?.customDef) {
try {
const customDef = JSON.parse(component.customDef);
relatedNodeId = customDef.loopEndNodeId;
} catch (e) {
console.error('解析循环开始节点数据失败:', e);
}
} else if (node.data?.type === 'LOOP_END' && component?.customDef) {
try {
const customDef = JSON.parse(component.customDef);
relatedNodeId = customDef.loopStartNodeId;
} catch (e) {
console.error('解析循环结束节点数据失败:', e);
}
}
// 删除两个节点及相关边
setNodes((nds: Node[]) => {
const updatedNodes = nds.filter(
(n) => n.id !== node.id && n.id !== relatedNodeId
);
return updatedNodes;
});
setEdges((eds: Edge[]) => {
const updatedEdges = eds.filter(
(e) =>
e.source !== node.id &&
e.target !== node.id &&
e.source !== relatedNodeId &&
e.target !== relatedNodeId
);
return updatedEdges;
});
// 删除节点后记录历史
setTimeout(() => {
const updatedNodes = nodes.filter(
(n) => n.id !== node.id && n.id !== relatedNodeId
);
const updatedEdges = edges.filter(
(e) =>
e.source !== node.id &&
e.target !== node.id &&
e.source !== relatedNodeId &&
e.target !== relatedNodeId
);
dispatchFlowSnapshot({
nodes: [...updatedNodes],
edges: [...updatedEdges],
});
}, 0);
return;
}
// 普通节点删除逻辑
setNodes((nds: Node[]) => nds.filter((n) => n.id !== node.id));
setEdges((eds: Edge[]) =>
eds.filter((e) => e.source !== node.id && e.target !== node.id)
);
// 删除节点后记录历史
setTimeout(() => {
dispatchFlowSnapshot({
nodes: [...nodes.filter((n) => n.id !== node.id)],
edges: [
...edges.filter(
(e) => e.source !== node.id && e.target !== node.id
),
],
});
}, 0);
},
[nodes, edges, useDefault]
);
// 删除边函数
const deleteEdge = useCallback(
(edge: Edge) => {
// 获取当前应用的运行状态
const { appRuntimeData, currentAppData } = store.getState().ideContainer;
const currentAppIsRunning =
currentAppData?.id && appRuntimeData[currentAppData.id]
? appRuntimeData[currentAppData.id].isRunning
: false;
// 在运行时禁止删除边
if (currentAppIsRunning) {
console.warn('在运行时不允许删除边');
return;
}
setEdges((eds: Edge[]) => eds.filter((e) => e.id !== edge.id));
// 删除边后记录历史
setTimeout(() => {
dispatchFlowSnapshot({
nodes: [...nodes],
edges: [...edges.filter((e) => e.id !== edge.id)],
});
}, 0);
},
[nodes, edges]
);
const getEdgeMidpointPosition = useCallback(
(sourceId: string, targetId: string) => {
const sourceNode = nodes.find((n) => n.id === sourceId);
const targetNode = nodes.find((n) => n.id === targetId);
if (!sourceNode || !targetNode) return null;
return {
x: (sourceNode.position.x + targetNode.position.x) / 2,
y: (sourceNode.position.y + targetNode.position.y) / 2,
};
},
[nodes]
);
// 在边上添加节点的具体实现
const addNodeOnEdge = useCallback(
(nodeType: string, node: any) => {
if (!edgeForNodeAdd || !reactFlowInstance) return;
// 查找节点定义
const nodeDefinition = getNodeDefinition(nodeType, node);
if (!nodeDefinition) return;
// 特殊处理循环节点,添加开始和结束节点
if (nodeType === 'LOOP') {
const position = getEdgeMidpointPosition(
edgeForNodeAdd.source,
edgeForNodeAdd.target
);
if (!position) return;
const { loopStartNode, loopEndNode } = createLoopNodePair(position);
// 创建连接边(连接循环开始和结束节点的顶部连接点)
const groupEdge = createLoopGroupEdge(loopStartNode.id, loopEndNode.id);
// 创建连接原有节点到循环开始节点,以及循环结束节点到目标节点的边
const connectionEdges = createLoopInsertConnectionEdges({
sourceId: edgeForNodeAdd.source,
sourceHandle: edgeForNodeAdd.sourceHandle,
targetId: edgeForNodeAdd.target,
targetHandle: edgeForNodeAdd.targetHandle,
loopStartId: loopStartNode.id,
loopEndId: loopEndNode.id,
});
// 将未定义的节点动态追加进nodeTypes
ensureNodeTypeRegistered('LOOP', '循环', LoopNode);
// 更新节点和边
setNodes((nds: Node[]) => {
const newNodes = [...nds, loopStartNode, loopEndNode];
// 添加节点后记录历史
dispatchFlowSnapshotAsync({
nodes: [...newNodes],
edges: [...edges, groupEdge, ...connectionEdges],
});
return newNodes;
});
setEdges((eds: Edge[]) => {
// 删除原来的边,添加新的边
const updatedEdges = [
...eds.filter((e) => e.id !== edgeForNodeAdd.id),
groupEdge,
...connectionEdges,
];
// 添加边后记录历史
dispatchFlowSnapshotAsync({
nodes: [...nodes, loopStartNode, loopEndNode],
edges: [...updatedEdges],
});
return updatedEdges;
});
// 关闭菜单
clearAddNodeContext();
return;
}
const position = getEdgeMidpointPosition(
edgeForNodeAdd.source,
edgeForNodeAdd.target
);
if (!position) return;
const { eventList } = store.getState().ideContainer;
const newNode = buildRuntimeNode({
nodeType,
nodeDefinition,
position,
eventList,
eventIdMode: 'id',
});
// 添加新节点
setNodes((nds: Node[]) => [...nds, newNode]);
// 删除旧边
setEdges((eds: Edge[]) => eds.filter((e) => e.id !== edgeForNodeAdd.id));
const { sourceHandle, targetHandle } =
resolveInsertedNodeHandles(newNode);
const newEdges = buildInsertedNodeEdges({
baseEdges: edges,
removedEdgeId: edgeForNodeAdd.id,
sourceId: edgeForNodeAdd.source,
sourceHandle: edgeForNodeAdd.sourceHandle,
targetId: edgeForNodeAdd.target,
targetHandle: edgeForNodeAdd.targetHandle,
insertedNodeId: newNode.id,
insertedNodeSourceHandle: sourceHandle,
insertedNodeTargetHandle: targetHandle,
});
setEdges(newEdges);
// 关闭菜单
clearAddNodeContext();
// 添加节点后记录历史
dispatchFlowSnapshotAsync({
nodes: [...nodes, newNode],
edges: [...newEdges],
});
},
[
edgeForNodeAdd,
reactFlowInstance,
edges,
addLoopNodeWithStartEnd,
getEdgeMidpointPosition,
nodes,
clearAddNodeContext,
]
);
// 在画布上添加节点
const addNodeOnPane = useCallback(
(nodeType: string, position: { x: number; y: number }, node?: any) => {
if (!reactFlowInstance) return;
// 查找节点定义
const nodeDefinition = getNodeDefinition(nodeType, node);
if (!nodeDefinition) return;
addNodeOnCanvas(nodeType, nodeDefinition, position);
},
[reactFlowInstance, getNodeDefinition, addNodeOnCanvas]
);
// 处理添加节点的统一方法
const handleAddNode = useCallback(
(nodeType: string, node: any) => {
// 如果是通过边添加节点
if (edgeForNodeAdd) {
addNodeOnEdge(nodeType, node);
}
// 如果是通过画布添加节点
else if (positionForNodeAdd) {
addNodeOnPane(nodeType, positionForNodeAdd, node);
}
// 清除状态
clearAddNodeContext();
},
[
edgeForNodeAdd,
positionForNodeAdd,
addNodeOnEdge,
addNodeOnPane,
clearAddNodeContext,
]
);
// endregion
const saveFlowDataToServer = useCallback(async () => {
if (useDefault) {
try {
// 首先校验所有节点数据是否完整
const nodeValidation = validateAllNodes(nodes);
if (!nodeValidation.isValid) {
showValidationErrors(nodeValidation.errors);
return;
}
// 然后校验所有连接线是否有效
const edgeValidation = validateAllEdges(edges, nodes);
if (!edgeValidation.isValid) {
showValidationErrors(edgeValidation.errors);
return;
}
// 转换会原始数据类型
const revertedData = revertFlowData(nodes, edges);
const upDatePublishCB = await upDatePublish(revertedData.nodeConfigs);
const newRevertedData = reverseConvertFlowData(
nodes,
edges,
upDatePublishCB
);
const { flowData, currentAppData, info } =
store.getState().ideContainer;
const { deleteEventSendNodeList, deleteEventlisteneList } =
handelEventNodeList(newRevertedData);
let params = {};
// 更新复合组件/子流程
if (currentAppData.key.includes('sub')) {
const appEventDefinition = updateEvent(
revertedData.nodeConfigs,
initialData.appId
);
params = {
...(currentAppData?.compData || {}),
components: newRevertedData,
appEventDefinition,
sceneId: info.id,
};
const res: any = await setSubFlowNew(
params,
currentAppData.parentAppId
);
if (res.code === 200) {
Message.success('保存成功');
// 更新事件枚举表
const res1: any = await queryEventItemBySceneIdOld(info.id);
if (res1.code === 200) dispatch(updateEventListOld(res1.data));
const appRes: any = await getAppInfoNew(currentAppData.parentAppId);
// 更新 flowData 中的数据
dispatch(
updateFlowData({ [currentAppData.parentAppId]: appRes.data })
);
// 同步更新主流程到 canvasDataMap使用父应用ID
if (appRes.data.main?.components) {
const { nodes: parentNodes, edges: parentEdges } =
convertFlowData(appRes.data.main.components, true);
dispatch(
updateCanvasDataMap({
...canvasDataMap,
[currentAppData.parentAppId]: {
nodes: parentNodes,
edges: parentEdges,
},
})
);
}
// 同步更新子流程到 canvasDataMap使用子流程key
dispatch(
updateCanvasDataMap({
...canvasDataMap,
[currentAppData.key]: { nodes, edges },
})
);
} else {
Message.error(res.message);
}
}
// 更新主流程
else {
const appEventDefinition = updateEvent(
revertedData.nodeConfigs,
initialData.appId
);
params = {
...(flowData[currentAppData.id]?.main || {}),
components: newRevertedData,
appEventDefinition,
sceneId: info.id,
};
const res: any = await setMainFlowNew(params, initialData.appId);
if (res.code === 200) {
Message.success('保存成功');
// 更新事件枚举表
const res1: any = await queryEventItemBySceneIdOld(info.id);
if (res1.code === 200) dispatch(updateEventListOld(res1.data));
// 更新缓存数据
dispatch(
updateCanvasDataMap({
...canvasDataMap,
[currentAppData.id]: { nodes, edges },
})
);
const appRes: any = await getAppInfoNew(currentAppData.id);
// 更新 flowData 中的数据
dispatch(updateFlowData({ [currentAppData.id]: appRes.data }));
// 同步更新到 canvasDataMap
if (appRes.data.main?.components) {
const { nodes, edges } = convertFlowData(
appRes.data.main.components,
true
);
setNodes(nodes);
setEdges(edges);
dispatch(
updateCanvasDataMap({
...canvasDataMap,
[currentAppData.id]: { nodes, edges },
})
);
}
} else {
Message.error(res.message);
}
}
// 事件节点变动数据有长度就通知后端,主流程和子流程(复合节点)通用
if (
deleteEventSendNodeList.length > 0 ||
deleteEventlisteneList.length > 0
) {
deleteEventSendNodeList.length > 0 &&
deleteEventPub({
appId: currentAppData.id,
topics: deleteEventSendNodeList,
});
deleteEventlisteneList.length > 0 &&
deleteEventSub({
appId: currentAppData.id,
topics: deleteEventlisteneList,
});
}
} catch (error) {
console.error('Error saving flow data:', error);
Message.error('保存失败');
}
} else {
const appFlowParams = {
appEventList: {},
eventEdges: [],
};
nodes.forEach((node) => {
appFlowParams.appEventList[node.id] = {
x: node.position.x,
y: node.position.y,
};
});
const eventMap = new Map();
edges.forEach((edge: any) => {
// 处理事件连线
appFlowParams.eventEdges.push({
id: edge.id,
source: edge.source,
target: edge.target,
lineType: 'data',
data: {
displayData: {
...edge.data.displayData,
},
},
});
// 应用组件的桩点id就是事件id
const sourceId = edge.sourceHandle;
const targetId = edge.targetHandle;
const topic = edge.data.displayData?.topic;
if (eventMap.has(topic)) {
// 如果topic已存在将eventId添加到数组中
eventMap.get(topic).eventId.push(sourceId);
eventMap.get(topic).eventId.push(targetId);
} else {
// 如果topic不存在创建新的条目
eventMap.set(topic, {
eventId: [sourceId, targetId],
topic: topic,
});
}
});
// 对eventId数组进行去重处理
const appEventParams = Array.from(eventMap.values()).map((item) => ({
...item,
eventId: Array.from(new Set(item.eventId)),
}));
try {
updateAppFlowData(appFlowParams);
if (appEventParams.length > 0) {
for (const item of appEventParams) {
if (item.topic) {
await sleep(500);
await updateAppEventChannel(item);
}
}
}
Message.success('保存成功');
} catch (error) {
console.error('保存失败:', error);
Message.error('保存失败: ' + error.message);
}
}
}, [nodes, edges, initialData?.appId]);
// 运行处理函数
const handleRun = useCallback(
async (running: boolean) => {
const { currentAppData, socketId, appRuntimeData } =
store.getState().ideContainer;
const appKey = getCurrentFlowAppKey();
if (running) {
// 子流程运行
if (currentAppData.key.includes('sub')) {
// 启动运行
const params = {
appId: currentAppData.parentAppId,
socketId,
subflowId: currentAppData.key,
};
const res: any = await runSubFlow(params);
if (res.code === 200) {
// 设置运行状态为true
dispatch(updateIsRunning(true));
// 重置节点状态
dispatch(resetNodeStatus());
// 更新运行ID
dispatch(updateRuntimeId(res.data));
refreshAppList();
// 开始运行时动画
setEdges((eds) =>
eds.map((edge) => ({
...edge,
data: {
...edge.data,
isRunning: true,
animationProgress: 0,
},
}))
);
} else {
Message.error(res.message);
}
}
// 主流程运行
else {
// 启动运行
const params = {
appId: currentAppData.id,
socketId,
};
const res: any = await runMainFlow(params);
if (res.code === 200) {
// 设置运行状态为true
dispatch(updateIsRunning(true));
// 重置节点状态
dispatch(resetNodeStatus());
// 更新运行ID
dispatch(updateRuntimeId(res.data));
refreshAppList();
// 开始运行时动画
setEdges((eds) =>
eds.map((edge) => ({
...edge,
data: {
...edge.data,
isRunning: true,
animationProgress: 0,
},
}))
);
} else {
Message.error(res.message);
}
}
} else {
// 设置运行状态为false
dispatch(updateIsRunning(false));
// 使用正确的 appKey 获取 runId
const runId =
appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : '';
if (runId) {
await stopApp(runId);
} else {
// 特殊停止逻辑,持久化运行的应用使用这里的入参
await stopApp(currentAppData.instanceId);
}
refreshAppList();
// 重置节点状态
dispatch(resetNodeStatus());
// 更新运行ID
dispatch(updateRuntimeId(''));
// 停止运行
setEdges((eds) =>
eds.map((edge) => ({
...edge,
data: {
...edge.data,
isRunning: false,
animationProgress: 0,
},
}))
);
// 清空当前应用的运行日志
if (appKey) {
dispatch(clearRuntimeLogs({ appId: appKey }));
}
}
},
[getCurrentFlowAppKey, refreshAppList]
);
// 暂停/恢复应用
const handlePause = useCallback(
async (isPaused: boolean) => {
const { currentAppData, appRuntimeData } = store.getState().ideContainer;
const appKey = getCurrentFlowAppKey();
if (!currentAppData) {
Message.warning('请先选择一个应用');
return;
}
// 获取runId
const runId =
appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : '';
if (!runId) {
Message.warning('应用未运行');
return;
}
try {
if (isPaused) {
// 当前已暂停,执行恢复操作
const res: any = await resumeApp({ id: runId });
if (res.code === 200) {
Message.success('应用已恢复');
// 更新暂停状态为 false
dispatch(updateIsPaused(false));
} else {
Message.error(res.msg || '恢复失败');
}
} else {
// 当前正在运行,执行暂停操作
const res: any = await pauseApp({ id: runId });
if (res.code === 200) {
Message.success('应用已暂停');
// 更新暂停状态为 true
dispatch(updateIsPaused(true));
} else {
Message.error(res.msg || '暂停失败');
}
}
} catch (error) {
console.error('暂停/恢复失败:', error);
Message.error('操作失败');
}
},
[getCurrentFlowAppKey]
);
// 重跑应用
const handleReRun = useCallback(async () => {
const { currentAppData, appRuntimeData, socketId } =
store.getState().ideContainer;
const appKey = getCurrentFlowAppKey();
if (!currentAppData) {
Message.warning('请先选择一个应用');
return;
}
// 获取runId (instanceId)
const instanceId =
appKey && appRuntimeData[appKey] ? appRuntimeData[appKey].runId : '';
if (!instanceId) {
Message.warning('应用未运行');
return;
}
try {
// 判断是主流程还是子流程
const appId =
currentAppData.key && currentAppData.key.includes('sub')
? currentAppData.parentAppId
: currentAppData.id;
const res: any = await reRunApp({
appId,
instanceId,
socketId,
});
if (res.code === 200) {
Message.success('应用重跑成功');
// 重置节点状态
dispatch(resetNodeStatus());
} else {
Message.error(res.msg || '重跑失败');
}
} catch (error) {
console.error('重跑失败:', error);
Message.error('重跑失败');
}
}, [getCurrentFlowAppKey]);
return {
// Event handlers
onNodesChange,
onEdgesChange,
onConnect,
onReconnect,
onDragOver,
onDrop,
onNodeDrag,
onNodeDragStop,
onNodesDelete,
// Menu handlers
closeEditModal,
saveNodeEdit,
deleteNode,
deleteEdge,
editNode,
editEdge,
copyNode,
pasteNode, // 添加粘贴节点功能
// Node operations
addNodeOnEdge,
addNodeOnPane,
handleAddNode,
// Initialization
initializeCanvasData,
updateCanvasDataMapEffect,
// Actions
saveFlowDataToServer,
handleRun,
handlePause,
handleReRun,
};
};
export default useFlowCallbacks;