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

1424 lines
47 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 { nodeTypeMap, registerNodeType } from '@/components/FlowEditor/node';
import { convertFlowData, reverseConvertFlowData, revertFlowData } from '@/utils/convertFlowData';
import { localNodeData } from '@/pages/flowEditor/sideBar/config/localNodeData';
import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines';
import LocalNode from '@/components/FlowEditor/node/localNode/LocalNode';
import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode';
import {
updateCanvasDataMap,
resetNodeStatus,
updateIsRunning,
updateEventListOld,
addRuntimeLog,
clearRuntimeLogs,
updateRuntimeId, updateFlowData
} from '@/store/ideContainer';
import {
validateAllNodes,
showValidationErrors,
validateAllEdges
} from '@/components/FlowEditor/nodeEditors/validators/nodeValidators';
import {
getHandleType,
validateDataType,
getNodeComponent
} 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 { Dispatch } from 'redux';
import { runMainFlow, stopApp } from '@/api/apps';
import store from '@/store';
import { updateAppEvent, updateAppEventChannel, updateAppFlowData } from '@/api/appEvent';
import { 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();
// 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 = setTimeout(() => {
const event = new CustomEvent('takeSnapshot', {
detail: { nodes: [...newNodes], edges: [...edges] }
});
document.dispatchEvent(event);
}, 100);
}
}, [nodes, edges]);
// 边变更处理
const onEdgesChange = useCallback((changes: any) => {
const newEdges = applyEdgeChanges(changes, edges);
setEdges(newEdges);
// 如果需要在边变化时执行某些操作,可以在这里添加
// 边的变化立即记录历史
const event = new CustomEvent('takeSnapshot', {
detail: { nodes: [...nodes], edges: [...newEdges] }
});
document.dispatchEvent(event);
}, [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;
}
// 获取源节点和目标节点的参数信息
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);
// 连接建立后记录历史
setTimeout(() => {
const event = new CustomEvent('takeSnapshot', {
detail: { nodes: [...nodes], edges: [...newEdges] }
});
document.dispatchEvent(event);
}, 0);
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 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
});
// 特殊处理循环节点,添加开始和结束节点
if (nodeData.nodeType === 'LOOP') {
addLoopNodeWithStartEnd(position, nodeData);
return;
}
const newNode = {
id: `${nodeData.nodeType}-${Date.now()}`,
type: nodeData.nodeType,
position,
data: { ...nodeData.data, title: nodeData.nodeName, type: nodeData.nodeType }
};
// 将未定义的节点动态追加进nodeTypes
const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
// 目前默认添加的都是系统组件/本地组件
if (!nodeMap.includes(nodeData.nodeType)) {
registerNodeType(nodeData.nodeType, LocalNode, nodeData.nodeName);
}
setNodes((nds: Node[]) => {
const newNodes = nds.concat(newNode);
// 添加节点后记录历史
setTimeout(() => {
const event = new CustomEvent('takeSnapshot', {
detail: { nodes: [...newNodes], edges: [...edges] }
});
document.dispatchEvent(event);
}, 0);
return newNodes;
});
}, [reactFlowInstance, edges]);
// 添加循环节点及其开始和结束节点
const addLoopNodeWithStartEnd = useCallback((position: { x: number, y: number }, nodeData: any) => {
// 创建循环开始节点
const loopStartNode = {
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 = {
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 // 这里的参数是为了提供在组件内部处理数据是使用最后这个字段要序列化后放进customDef
}
}
};
loopStartNode.data.component = {
type: 'LOOP_START',
customDef: JSON.stringify({ loopEndNodeId: loopEndNode.id })
};
// 创建连接边(连接循环开始和结束节点的顶部连接点)
const newEdges = [
{
id: `${loopStartNode.id}-${loopEndNode.id}-group`,
source: loopStartNode.id,
target: loopEndNode.id,
sourceHandle: `${loopStartNode.id}-group`,
targetHandle: `${loopEndNode.id}-group`,
type: 'custom'
}
];
// 将未定义的节点动态追加进nodeTypes
const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
if (!nodeMap.includes('LOOP')) {
registerNodeType('LOOP', LoopNode, '循环');
}
setNodes((nds: Node[]) => {
const newNodes = [...nds, loopStartNode, loopEndNode];
// 添加节点后记录历史
setTimeout(() => {
const event = new CustomEvent('takeSnapshot', {
detail: { nodes: [...newNodes], edges: [...edges, ...newEdges] }
});
document.dispatchEvent(event);
}, 0);
return newNodes;
});
setEdges((eds: Edge[]) => {
const updatedEdges = [...eds, ...newEdges];
// 添加边后记录历史
setTimeout(() => {
const event = new CustomEvent('takeSnapshot', {
detail: { nodes: [...nodes, loopStartNode, loopEndNode], edges: [...updatedEdges] }
});
document.dispatchEvent(event);
}, 0);
return updatedEdges;
});
}, [nodes, edges]);
// 节点拖拽处理
const onNodeDrag = useCallback((_: any, node: Node) => {
// 获取对齐线
getGuidelines(node, nodes);
}, [nodes, getGuidelines]);
// 节点拖拽结束处理
const onNodeDragStop = useCallback(() => {
// 清除对齐线
clearGuidelines();
}, [clearGuidelines]);
// endregion
// region 画布数据处理
// 初始化画布数据
const initializeCanvasData = useCallback(() => {
if (canvasDataMap[initialData?.appId]) {
const { edges, nodes } = canvasDataMap[initialData?.appId];
setNodes(nodes);
setEdges(edges);
}
else {
// 首次进入
if (useDefault) projectFlowHandle(initialData, useDefault, setNodes, setEdges, dispatch, canvasDataMap);
else appFLowHandle(initialData, useDefault, setNodes, setEdges, dispatch);
}
// 标记历史记录已初始化
setHistoryInitialized(true);
}, [initialData, canvasDataMap]);
// 实时更新 canvasDataMap
const updateCanvasDataMapEffect = useCallback(() => {
if (initialData?.appId) {
updateCanvasDataMapDebounced(dispatch, canvasDataMap, initialData.appId, nodes, edges);
}
// 清理函数,在组件卸载时取消防抖
return () => {
// 取消防抖函数
};
}, [nodes, edges, initialData?.appId, dispatch, canvasDataMap]);
// 关闭编辑弹窗
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) => {
// 将节点数据存储到localStorage中以便粘贴时使用
const nodeData = {
...node,
// 清除可能存在的运行时状态
selected: false,
dragging: false,
// 添加应用标识信息
appId: initialData?.appId
};
localStorage.setItem('copiedNode', JSON.stringify(nodeData));
}, [initialData?.appId]);
// 粘贴节点
const pasteNode = useCallback((position: { x: number; y: number }) => {
const copiedNodeStr = localStorage.getItem('copiedNode');
if (!copiedNodeStr) {
console.warn('没有找到复制的节点数据');
return;
}
try {
const copiedNode = JSON.parse(copiedNodeStr);
// 检查是否为同一应用,如果不是则不允许粘贴
if (copiedNode.appId && initialData?.appId && copiedNode.appId !== initialData.appId) {
console.warn('不能在不同应用之间粘贴节点');
// Message.warning('不能在不同应用之间粘贴节点');
return;
}
// 创建新节点更新ID和位置
const newNode = {
...copiedNode,
id: `${copiedNode.type}-${Date.now()}`, // 生成新的唯一ID
position, // 使用传入的位置
selected: false,
dragging: false,
// 移除应用标识信息,避免存储在节点数据中
appId: undefined
};
// 特殊处理循环节点
if (copiedNode.type === 'LOOP') {
// 对于循环节点,我们需要特殊处理
setNodes((nds: Node[]) => {
const newNodes = [...nds, newNode];
// 添加节点后记录历史
setTimeout(() => {
const event = new CustomEvent('takeSnapshot', {
detail: { nodes: [...newNodes], edges: [...edges] }
});
document.dispatchEvent(event);
}, 0);
return newNodes;
});
return;
}
// 将未定义的节点动态追加进nodeTypes
const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
if (!nodeMap.includes(newNode.type)) {
registerNodeType(newNode.type, getNodeComponent(newNode.type), newNode.data?.title || newNode.type);
}
setNodes((nds: Node[]) => {
const newNodes = [...nds, newNode];
// 添加节点后记录历史
setTimeout(() => {
const event = new CustomEvent('takeSnapshot', {
detail: { nodes: [...newNodes], edges: [...edges] }
});
document.dispatchEvent(event);
}, 0);
return newNodes;
});
} catch (error) {
console.error('粘贴节点时出错:', error);
}
}, [edges, initialData?.appId]);
// 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
);
const event = new CustomEvent('takeSnapshot', {
detail: {
nodes: [...updatedNodes],
edges: [...updatedEdges]
}
});
document.dispatchEvent(event);
}, 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(() => {
const event = new CustomEvent('takeSnapshot', {
detail: {
nodes: [...nodes.filter((n) => n.id !== node.id)],
edges: [...edges.filter((e) => e.source !== node.id && e.target !== node.id)]
}
});
document.dispatchEvent(event);
}, 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(() => {
const event = new CustomEvent('takeSnapshot', {
detail: {
nodes: [...nodes],
edges: [...edges.filter((e) => e.id !== edge.id)]
}
});
document.dispatchEvent(event);
}, 0);
}, [nodes, edges]);
// 在边上添加节点的具体实现
const addNodeOnEdge = useCallback((nodeType: string, node: any) => {
const { currentAppData, flowData } = store.getState().ideContainer;
if (!edgeForNodeAdd || !reactFlowInstance) return;
// 查找节点定义
const nodeDefinition = localNodeData.find(n => n.nodeType === nodeType) || node;
if (!nodeDefinition) return;
// 特殊处理循环节点,添加开始和结束节点
if (nodeType === 'LOOP') {
// 获取源节点和目标节点的位置
const sourceNode = nodes.find(n => n.id === edgeForNodeAdd.source);
const targetNode = nodes.find(n => n.id === edgeForNodeAdd.target);
if (!sourceNode || !targetNode) return;
// 计算中点位置
const position = {
x: (sourceNode.position.x + targetNode.position.x) / 2,
y: (sourceNode.position.y + targetNode.position.y) / 2
};
// 创建循环开始和结束节点
const loopStartNode = {
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 = {
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 })
};
// 创建连接边(连接循环开始和结束节点的顶部连接点)
const groupEdge = {
id: `${loopStartNode.id}-${loopEndNode.id}-group`,
source: loopStartNode.id,
target: loopEndNode.id,
sourceHandle: `${loopStartNode.id}-group`,
targetHandle: `${loopEndNode.id}-group`,
type: 'custom'
};
// 创建连接原有节点到循环开始节点,以及循环结束节点到目标节点的边
const connectionEdges = [
{
id: `e${edgeForNodeAdd.source}-${loopStartNode.id}`,
source: edgeForNodeAdd.source,
target: loopStartNode.id,
sourceHandle: edgeForNodeAdd.sourceHandle,
targetHandle: 'start', // 循环开始节点的输入句柄
type: 'custom',
lineType: 'api',
data: {
lineType: 'api'
}
},
{
id: `e${loopEndNode.id}-${edgeForNodeAdd.target}`,
source: loopEndNode.id,
target: edgeForNodeAdd.target,
sourceHandle: 'break', // 循环结束节点的输出句柄
targetHandle: edgeForNodeAdd.targetHandle,
type: 'custom',
lineType: 'api',
data: {
lineType: 'api'
}
}
];
// 将未定义的节点动态追加进nodeTypes
const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
if (!nodeMap.includes('LOOP')) {
registerNodeType('LOOP', LoopNode, '循环');
}
// 更新节点和边
setNodes((nds: Node[]) => {
const newNodes = [...nds, loopStartNode, loopEndNode];
// 添加节点后记录历史
setTimeout(() => {
const event = new CustomEvent('takeSnapshot', {
detail: { nodes: [...newNodes], edges: [...edges, groupEdge, ...connectionEdges] }
});
document.dispatchEvent(event);
}, 0);
return newNodes;
});
setEdges((eds: Edge[]) => {
// 删除原来的边,添加新的边
const updatedEdges = [...eds.filter(e => e.id !== edgeForNodeAdd.id), groupEdge, ...connectionEdges];
// 添加边后记录历史
setTimeout(() => {
const event = new CustomEvent('takeSnapshot', {
detail: {
nodes: [...nodes, loopStartNode, loopEndNode],
edges: [...updatedEdges]
}
});
document.dispatchEvent(event);
}, 0);
return updatedEdges;
});
// 关闭菜单
setEdgeForNodeAdd(null);
setPositionForNodeAdd(null);
return;
}
// 获取源节点和目标节点
const sourceNode = nodes.find(n => n.id === edgeForNodeAdd.source);
const targetNode = nodes.find(n => n.id === edgeForNodeAdd.target);
if (!sourceNode || !targetNode) return;
// 计算中点位置
const position = {
x: (sourceNode.position.x + targetNode.position.x) / 2,
y: (sourceNode.position.y + targetNode.position.y) / 2
};
// 创建新节点
const newNode = {
id: `${nodeType}-${Date.now()}`,
type: nodeType,
position,
data: {
...nodeDefinition.data,
title: nodeDefinition.nodeName,
type: nodeType
}
};
// 有组件ID的节点就追加组件id数据
if (nodeDefinition.id || nodeDefinition?.flowHousVO?.id) newNode.data.compId = nodeDefinition.id || nodeDefinition?.flowHousVO?.id;
if (nodeType === 'SWITCH') {
newNode.data.component = {
customDef: JSON.stringify({
apiOutIds: ['default'],
conditions: []
})
};
}
else if (nodeType === 'SUB') {
const flowSubMap = flowData[currentAppData.id]?.subMap || {};
const sameData: any = flowSubMap[newNode.data.compId];
if (sameData) {
newNode.data.component = {
type: nodeType,
compId: newNode.data.compId,
customDef: JSON.stringify({
dataIns: newNode.data.parameters.dataIns,
dataOuts: newNode.data.parameters.dataOuts,
subflowId: sameData,
name: newNode.data.title
})
};
}
else {
newNode.data.component = {
type: nodeType,
compId: newNode.data.compId
};
}
}
else if (nodeType === 'EVENTSEND' || nodeType === 'EVENTLISTENE') {
const { eventList } = store.getState().ideContainer;
const emptyEvent = eventList.find(item => item.topic.includes('**empty**'));
newNode.data.component = {
type: nodeType,
customDef: { eventId: emptyEvent.id, name: emptyEvent.name, topic: emptyEvent.topic }
};
}
else if (nodeType === 'BASIC') {
newNode.data.component = {
type: nodeType,
compId: nodeDefinition.id
};
}
// 将未定义的节点动态追加进nodeTypes
const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
if (!nodeMap.includes(nodeType)) {
registerNodeType(nodeType, getNodeComponent(nodeType), nodeDefinition.nodeName);
}
// 添加新节点
setNodes((nds: Node[]) => [...nds, newNode]);
// 删除旧边
setEdges((eds: Edge[]) => eds.filter(e => e.id !== edgeForNodeAdd.id));
// 确定新边的句柄
// 对于第一条边 (source -> new node): 使用原始边的 sourceHandle目标句柄根据节点类型确定
// 对于第二条边 (new node -> target): 源句柄根据节点类型确定,使用原始边的 targetHandle
// 获取新节点的默认句柄
let newNodeSourceHandle = 'done'; // 默认源句柄
let newNodeTargetHandle = 'start'; // 默认目标句柄
// 如果新节点有参数定义,尝试获取更准确的句柄信息
if (newNode.data?.parameters) {
const { apiOuts, apiIns } = newNode.data.parameters;
// 获取第一个api输出作为源句柄如果存在
if (apiOuts && apiOuts.length > 0) {
newNodeSourceHandle = apiOuts[0].name || apiOuts[0].id || newNodeSourceHandle;
}
// 获取第一个api输入作为目标句柄如果存在
if (apiIns && apiIns.length > 0) {
newNodeTargetHandle = apiIns[0].name || apiIns[0].id || newNodeTargetHandle;
}
}
// 创建新边: source -> new node, new node -> target
const newEdges = [
...edges.filter(e => e.id !== edgeForNodeAdd.id),
{
id: `e${edgeForNodeAdd.source}-${newNode.id}`,
source: edgeForNodeAdd.source,
target: newNode.id,
sourceHandle: edgeForNodeAdd.sourceHandle,
targetHandle: newNodeTargetHandle,
type: 'custom',
lineType: 'api',
data: {
lineType: 'api'
}
},
{
id: `e${newNode.id}-${edgeForNodeAdd.target}`,
source: newNode.id,
target: edgeForNodeAdd.target,
sourceHandle: newNodeSourceHandle,
targetHandle: edgeForNodeAdd.targetHandle,
type: 'custom',
lineType: 'api',
data: {
lineType: 'api'
}
}
];
setEdges(newEdges);
// 关闭菜单
setEdgeForNodeAdd(null);
setPositionForNodeAdd(null);
// 添加节点后记录历史
setTimeout(() => {
const event = new CustomEvent('takeSnapshot', {
detail: {
nodes: [...nodes, newNode],
edges: [...newEdges]
}
});
document.dispatchEvent(event);
}, 0);
}, [edgeForNodeAdd, nodes, reactFlowInstance, edges, addLoopNodeWithStartEnd]);
// 在画布上添加节点
const addNodeOnPane = useCallback((nodeType: string, position: { x: number; y: number }, node?: any) => {
const { currentAppData, flowData } = store.getState().ideContainer;
if (!reactFlowInstance) return;
// 查找节点定义
const nodeDefinition = localNodeData.find(n => n.nodeType === nodeType) || node;
if (!nodeDefinition) return;
// 特殊处理循环节点,添加开始和结束节点
if (nodeType === 'LOOP') {
addLoopNodeWithStartEnd(position, nodeDefinition);
return;
}
// 创建新节点
const newNode = {
id: `${nodeType}-${Date.now()}`,
type: nodeType,
position,
data: {
...nodeDefinition.data,
title: nodeDefinition.nodeName,
type: nodeType
}
};
if (nodeDefinition.id || nodeDefinition?.flowHousVO?.id) newNode.data.compId = nodeDefinition.id || nodeDefinition?.flowHousVO?.id;
if (nodeType === 'SWITCH') {
newNode.data.component = {
customDef: JSON.stringify({
apiOutIds: ['default'],
conditions: []
})
};
}
else if (nodeType === 'SUB') {
const flowSubMap = flowData[currentAppData.id]?.subMap || {};
const sameData: any = flowSubMap[newNode.data.compId];
if (sameData) {
newNode.data.component = {
type: nodeType,
compId: newNode.data.compId,
customDef: JSON.stringify({
dataIns: newNode.data.parameters.dataIns,
dataOuts: newNode.data.parameters.dataOuts,
subflowId: sameData,
name: newNode.data.title
})
};
}
else {
newNode.data.component = {
type: nodeType,
compId: newNode.data.compId
};
}
}
else if (nodeType === 'EVENTSEND' || nodeType === 'EVENTLISTENE') {
const { eventList } = store.getState().ideContainer;
const emptyEvent = eventList.find(item => item.topic.includes('**empty**'));
newNode.data.component = {
type: nodeType,
customDef: { eventId: emptyEvent?.eventId ?? null, name: emptyEvent.name, topic: emptyEvent.topic }
};
}
else if (nodeType === 'BASIC') {
newNode.data.component = {
type: nodeType,
compId: nodeDefinition.id
};
}
// 将未定义的节点动态追加进nodeTypes
const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
// 目前默认添加的都是系统组件/本地组件
if (!nodeMap.includes(nodeType)) {
registerNodeType(nodeType, getNodeComponent(nodeType), nodeDefinition.nodeName);
}
setNodes((nds: Node[]) => {
const newNodes = [...nds, newNode];
// 添加节点后记录历史
setTimeout(() => {
const event = new CustomEvent('takeSnapshot', {
detail: { nodes: [...newNodes], edges: [...edges] }
});
document.dispatchEvent(event);
}, 0);
return newNodes;
});
}, [reactFlowInstance, edges, addLoopNodeWithStartEnd]);
// 处理添加节点的统一方法
const handleAddNode = useCallback((nodeType: string, node: any) => {
// 如果是通过边添加节点
if (edgeForNodeAdd) {
addNodeOnEdge(nodeType, node);
}
// 如果是通过画布添加节点
else if (positionForNodeAdd) {
addNodeOnPane(nodeType, positionForNodeAdd, node);
}
// 清除状态
setEdgeForNodeAdd(null);
setPositionForNodeAdd(null);
}, [edgeForNodeAdd, positionForNodeAdd, addNodeOnEdge, addNodeOnPane]);
// 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));
// 更新缓存数据 - 与主流程保持一致
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);
}
}
// 更新主流程
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) {
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;
if (running) {
// 启动运行
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));
// 开始运行时动画
setEdges((eds) => eds.map(edge => ({
...edge,
data: {
...edge.data,
isRunning: true,
animationProgress: 0
}
})));
}
else {
Message.error(res.message);
}
}
else {
// 设置运行状态为false
dispatch(updateIsRunning(false));
await stopApp(appRuntimeData[currentAppData.id].runId);
// 更新运行ID
dispatch(updateRuntimeId(''));
// 停止运行
setEdges((eds) => eds.map(edge => ({
...edge,
data: {
...edge.data,
isRunning: false,
animationProgress: 0
}
})));
// 清空当前应用的运行日志
dispatch(clearRuntimeLogs({ appId: currentAppData.id }));
}
}, [initialData?.appId]);
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
};
};
export default useFlowCallbacks;