Merge branch 'refs/heads/master' into production

master
钟良源 3 months ago
commit d381a6091d

@ -205,7 +205,7 @@ export interface DeleteEventParams {
export interface DeleteEventForAppParams { export interface DeleteEventForAppParams {
appId: string, appId: string,
eventIds: string[] topics: string[]
} }
// runtime // runtime

@ -49,6 +49,7 @@
padding: 0 5px; padding: 0 5px;
border: 1px solid #cccccc; border: 1px solid #cccccc;
border-radius: 3px; border-radius: 3px;
justify-content: space-between;
.node-inputs { .node-inputs {
padding-right: 10px; padding-right: 10px;
@ -58,8 +59,9 @@
padding-left: 10px; padding-left: 10px;
} }
.node-inputs,
.node-outputs, //.node-inputs,
//.node-outputs,
.node-inputs-api, .node-inputs-api,
.node-outputs-api { .node-outputs-api {
flex: 1; flex: 1;

@ -7,7 +7,7 @@ import {
Node, Node,
Edge Edge
} from '@xyflow/react'; } from '@xyflow/react';
import { refPublish, setMainFlow, setMainFlowNew, setSubFlowNew } from '@/api/appRes'; import { getAppInfoNew, setMainFlowNew, setSubFlowNew } from '@/api/appRes';
import { Message } from '@arco-design/web-react'; import { Message } from '@arco-design/web-react';
import { nodeTypeMap, registerNodeType } from '@/components/FlowEditor/node'; import { nodeTypeMap, registerNodeType } from '@/components/FlowEditor/node';
import { convertFlowData, reverseConvertFlowData, revertFlowData } from '@/utils/convertFlowData'; import { convertFlowData, reverseConvertFlowData, revertFlowData } from '@/utils/convertFlowData';
@ -22,7 +22,7 @@ import {
updateEventListOld, updateEventListOld,
addRuntimeLog, addRuntimeLog,
clearRuntimeLogs, clearRuntimeLogs,
updateRuntimeId updateRuntimeId, updateFlowData
} from '@/store/ideContainer'; } from '@/store/ideContainer';
import { import {
validateAllNodes, validateAllNodes,
@ -36,13 +36,14 @@ import {
} from '@/utils/flowCommon'; } from '@/utils/flowCommon';
import { projectFlowHandle } from '@/pages/flowEditor/utils/projectFlowHandle'; import { projectFlowHandle } from '@/pages/flowEditor/utils/projectFlowHandle';
import { appFLowHandle } from '@/pages/flowEditor/utils/appFlowhandle'; import { appFLowHandle } from '@/pages/flowEditor/utils/appFlowhandle';
import { handelEventNodeList, updateEvent, upDatePublish } from '@/pages/flowEditor/utils/common';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import { runMainFlow, stopApp } from '@/api/apps'; import { runMainFlow, stopApp } from '@/api/apps';
import store from '@/store'; import store from '@/store';
import { updateAppEvent, updateAppEventChannel, updateAppFlowData } from '@/api/appEvent'; import { updateAppEvent, updateAppEventChannel, updateAppFlowData } from '@/api/appEvent';
import { sleep } from '@/utils/common'; import { sleep } from '@/utils/common';
import { queryEventItemBySceneIdOld } from '@/api/event'; import { queryEventItemBySceneIdOld, deleteEventSub, deleteEventPub } from '@/api/event';
export const useFlowCallbacks = ( export const useFlowCallbacks = (
nodes: Node[], nodes: Node[],
@ -810,7 +811,11 @@ export const useFlowCallbacks = (
target: loopStartNode.id, target: loopStartNode.id,
sourceHandle: edgeForNodeAdd.sourceHandle, sourceHandle: edgeForNodeAdd.sourceHandle,
targetHandle: 'start', // 循环开始节点的输入句柄 targetHandle: 'start', // 循环开始节点的输入句柄
type: 'custom' type: 'custom',
lineType: 'api',
data: {
lineType: 'api'
}
}, },
{ {
id: `e${loopEndNode.id}-${edgeForNodeAdd.target}`, id: `e${loopEndNode.id}-${edgeForNodeAdd.target}`,
@ -818,7 +823,11 @@ export const useFlowCallbacks = (
target: edgeForNodeAdd.target, target: edgeForNodeAdd.target,
sourceHandle: 'break', // 循环结束节点的输出句柄 sourceHandle: 'break', // 循环结束节点的输出句柄
targetHandle: edgeForNodeAdd.targetHandle, targetHandle: edgeForNodeAdd.targetHandle,
type: 'custom' type: 'custom',
lineType: 'api',
data: {
lineType: 'api'
}
} }
]; ];
@ -903,18 +912,24 @@ export const useFlowCallbacks = (
}; };
} }
else if (nodeType === 'SUB') { else if (nodeType === 'SUB') {
const flowMainData = flowData[currentAppData.id]?.main?.components; const flowSubMap = flowData[currentAppData.id]?.subMap || {};
const sameData: any = Object.values(flowMainData).filter((item: any) => { const sameData: any = flowSubMap[newNode.data.compId];
if (item?.component?.compId === newNode.data.compId) return item; if (sameData) {
});
if (sameData.length) {
newNode.data.component = { newNode.data.component = {
...sameData[0].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 { else {
newNode.data.component = { newNode.data.component = {
type: nodeType type: nodeType,
compId: newNode.data.compId
}; };
} }
} }
@ -977,7 +992,11 @@ export const useFlowCallbacks = (
target: newNode.id, target: newNode.id,
sourceHandle: edgeForNodeAdd.sourceHandle, sourceHandle: edgeForNodeAdd.sourceHandle,
targetHandle: newNodeTargetHandle, targetHandle: newNodeTargetHandle,
type: 'custom' type: 'custom',
lineType: 'api',
data: {
lineType: 'api'
}
}, },
{ {
id: `e${newNode.id}-${edgeForNodeAdd.target}`, id: `e${newNode.id}-${edgeForNodeAdd.target}`,
@ -985,7 +1004,11 @@ export const useFlowCallbacks = (
target: edgeForNodeAdd.target, target: edgeForNodeAdd.target,
sourceHandle: newNodeSourceHandle, sourceHandle: newNodeSourceHandle,
targetHandle: edgeForNodeAdd.targetHandle, targetHandle: edgeForNodeAdd.targetHandle,
type: 'custom' type: 'custom',
lineType: 'api',
data: {
lineType: 'api'
}
} }
]; ];
@ -1044,18 +1067,24 @@ export const useFlowCallbacks = (
}; };
} }
else if (nodeType === 'SUB') { else if (nodeType === 'SUB') {
const flowMainData = flowData[currentAppData.id]?.main?.components; const flowSubMap = flowData[currentAppData.id]?.subMap || {};
const sameData: any = Object.values(flowMainData).filter((item: any) => { const sameData: any = flowSubMap[newNode.data.compId];
if (item?.component?.compId === newNode.data.compId) return item; if (sameData) {
});
if (sameData.length) {
newNode.data.component = { newNode.data.component = {
...sameData[0].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 { else {
newNode.data.component = { newNode.data.component = {
type: nodeType type: nodeType,
compId: newNode.data.compId
}; };
} }
} }
@ -1111,168 +1140,7 @@ export const useFlowCallbacks = (
}, [edgeForNodeAdd, positionForNodeAdd, addNodeOnEdge, addNodeOnPane]); }, [edgeForNodeAdd, positionForNodeAdd, addNodeOnEdge, addNodeOnPane]);
// endregion // endregion
// 保存所有节点和边数据到服务器,更新事件相关数据
const updateEvent = (revertedData, appid) => {
// 初始化参数对象
const params: any = {
eventListenes: [],
eventSends: []
};
// 遍历所有节点,查找事件相关节点
Object.entries(revertedData).forEach(([nodeId, nodeConfig]: [string, any]) => {
// 检查节点是否有component属性和type定义
if (nodeConfig.component?.type) {
const nodeType = nodeConfig.component.type;
const customDef = nodeConfig.component.customDef;
// 解析customDef可能是字符串也可能是对象
let eventConfig;
if (typeof customDef === 'string') {
try {
eventConfig = JSON.parse(customDef);
} catch (e) {
console.error('Failed to parse customDef:', e);
return;
}
}
else {
eventConfig = customDef;
}
// 根据节点类型处理事件节点
if (nodeType === 'EVENTLISTENE') {
// 事件接收节点
params.eventListenes.push({
nodeName: nodeConfig.nodeName || '',
eventId: eventConfig?.eventId || null,
topic: eventConfig?.topic || '',
eventName: eventConfig?.name || '',
dataOuts: nodeConfig.dataOuts || [],
nodeId: nodeConfig.nodeId
});
}
else if (nodeType === 'EVENTSEND') {
// 事件发送节点
params.eventSends.push({
nodeName: nodeConfig.nodeName || '',
eventId: eventConfig?.eventId || null,
topic: eventConfig?.topic || '',
eventName: eventConfig?.name || '',
dataIns: nodeConfig.dataIns || [],
nodeId: nodeConfig.nodeId
});
}
}
});
// 调用更新事件的API
if (params.eventListenes.length > 0 || params.eventSends.length > 0) {
return params;
}
else return null;
};
const upDatePublish = async (revertedData) => {
const { currentAppData } = store.getState().ideContainer;
const params = {
appId: currentAppData.id,
publishAppId: []
};
revertedData.forEach(item => {
if (item?.component && item.component.type === 'SUB' && !item.component.customDef) params.publishAppId.push(item.component.compId); // 复合组件的这个compId实际上就是flowHousVO里面的id
});
if (params.publishAppId.length > 0) {
const res: any = await refPublish(params);
if (res.code === 200) return res.data;
else return {};
}
else return {};
};
const handelEventNodeList = (newRevertedData) => {
const { appRuntimeData, currentAppData } = store.getState().ideContainer;
const current = appRuntimeData[currentAppData.id];
const deleteEventSendNodeList = [];
const deleteEventlisteneList = [];
// 处理流程接口后的数据
const currentEventSendNodeList = current.eventSendNodeList;
const currentEventlisteneList = current.eventlisteneList;
// 画布数据
const nodeEntries: [string, any][] = Object.entries(newRevertedData);
// 分类事件节点
const eventSendNodes = [];
const eventListenerNodes = [];
// 从nodeEntries中提取事件节点
nodeEntries.forEach(([nodeId, nodeConfig]) => {
if (nodeConfig.component?.type === 'EVENTSEND') {
eventSendNodes.push({ nodeId, ...nodeConfig });
}
else if (nodeConfig.component?.type === 'EVENTLISTENE') {
eventListenerNodes.push({ nodeId, ...nodeConfig });
}
});
// 处理EVENTSEND节点
// 如果currentEventSendNodeList中有而nodeEntries中没有则加入删除列表
currentEventSendNodeList.forEach((eventNode) => {
const nodeId = eventNode;
const nodeInCanvas = eventSendNodes.find(node => node.nodeId === nodeId);
if (!nodeInCanvas) {
// 画布数据中没有该节点,加入删除列表
deleteEventSendNodeList.push(eventNode);
}
else {
// 节点存在于画布中,比较时间戳
// nodeId格式为"节点类型-时间戳"
const canvasNode = nodeInCanvas.nodeId.split('-');
const canvasTimestamp = canvasNode[canvasNode.length - 1];
const interfaceNode = nodeId.split('-');
const interfaceTimestamp = interfaceNode[interfaceNode.length - 1];
// 如果画布数据的时间戳是最新的那就是新增的事件节点不需要添加到deleteList中
// 反之需要进一步判断
if (canvasTimestamp < interfaceTimestamp) {
// 画布节点时间戳较旧,需要进一步判断
// 这里留空,等待后续实现
}
}
});
// 处理EVENTLISTENE节点
// 如果currentEventlisteneList中有而nodeEntries中没有则加入删除列表
currentEventlisteneList.forEach((eventNode) => {
const nodeId = eventNode;
const nodeInCanvas = eventListenerNodes.find(node => node.nodeId === nodeId);
if (!nodeInCanvas) {
// 画布数据中没有该节点,加入删除列表
deleteEventlisteneList.push(eventNode);
}
else {
// 节点存在于画布中,比较时间戳
// nodeId格式为"节点类型-时间戳"
const canvasNode = nodeInCanvas.nodeId.split('-');
const canvasTimestamp = canvasNode[canvasNode.length - 1];
const interfaceNode = nodeId.split('-');
const interfaceTimestamp = interfaceNode[interfaceNode.length - 1];
// 如果画布数据的时间戳是最新的那就是新增的事件节点不需要添加到deleteList中
// 反之需要进一步判断
if (canvasTimestamp < interfaceTimestamp) {
// 画布节点时间戳较旧,需要进一步判断
// 这里留空,等待后续实现
}
}
});
return {
deleteEventSendNodeList,
deleteEventlisteneList
};
};
const saveFlowDataToServer = useCallback(async () => { const saveFlowDataToServer = useCallback(async () => {
if (useDefault) { if (useDefault) {
try { try {
@ -1295,8 +1163,7 @@ export const useFlowCallbacks = (
const newRevertedData = reverseConvertFlowData(nodes, edges, upDatePublishCB); const newRevertedData = reverseConvertFlowData(nodes, edges, upDatePublishCB);
const { flowData, currentAppData, info } = store.getState().ideContainer; const { flowData, currentAppData, info } = store.getState().ideContainer;
const { deleteEventSendNodeList, deleteEventlisteneList } = handelEventNodeList(newRevertedData); const { deleteEventSendNodeList, deleteEventlisteneList } = handelEventNodeList(newRevertedData);
// console.log(deleteEventSendNodeList, deleteEventlisteneList);
// return;
let params = {}; let params = {};
// 更新复合组件/子流程 // 更新复合组件/子流程
if (currentAppData.key.includes('sub')) { if (currentAppData.key.includes('sub')) {
@ -1313,6 +1180,25 @@ export const useFlowCallbacks = (
// 更新事件枚举表 // 更新事件枚举表
const res1: any = await queryEventItemBySceneIdOld(info.id); const res1: any = await queryEventItemBySceneIdOld(info.id);
if (res1.code === 200) dispatch(updateEventListOld(res1.data)); 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 { else {
Message.error(res.message); Message.error(res.message);
@ -1339,23 +1225,45 @@ export const useFlowCallbacks = (
...canvasDataMap, ...canvasDataMap,
[currentAppData.id]: { nodes, edges } [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 { else {
Message.error(res.message); 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) { } catch (error) {
console.error('Error saving flow data:', error); console.error('Error saving flow data:', error);
Message.error('保存失败'); Message.error('保存失败');
} }
} }
else { else {
const params = {
nodes,
edges
};
const appFlowParams = { const appFlowParams = {
appEventList: {} appEventList: {},
eventEdges: []
}; };
nodes.forEach(node => { nodes.forEach(node => {
@ -1367,6 +1275,20 @@ export const useFlowCallbacks = (
const eventMap = new Map(); const eventMap = new Map();
edges.forEach((edge: any) => { 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 // 应用组件的桩点id就是事件id
const sourceId = edge.sourceHandle; const sourceId = edge.sourceHandle;
const targetId = edge.targetHandle; const targetId = edge.targetHandle;

@ -139,6 +139,34 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
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(), []);
// 用于存储隐藏的节点ID
const [hiddenNodes, setHiddenNodes] = React.useState<Set<string>>(new Set());
// 监听自定义事件以隐藏/显示节点
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);
};
}, []);
// 从Redux store中获取当前应用的运行状态 // 从Redux store中获取当前应用的运行状态
const { appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer); const { appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer);
const currentAppIsRunning = currentAppData?.id && appRuntimeData[currentAppData.id] const currentAppIsRunning = currentAppData?.id && appRuntimeData[currentAppData.id]
@ -148,6 +176,31 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
// 在应用编排模式下useDefault为false禁用删除功能 // 在应用编排模式下useDefault为false禁用删除功能
const isDeleteDisabled = currentAppIsRunning; const isDeleteDisabled = currentAppIsRunning;
// 监听自定义事件以隐藏/显示节点
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) => {
@ -188,8 +241,41 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
onContextMenu={(e) => e.preventDefault()}> onContextMenu={(e) => e.preventDefault()}>
<ReactFlow <ReactFlow
id={reactFlowId} id={reactFlowId}
nodes={nodes.map(node => ({ ...node, draggable: !currentAppIsRunning }))} // 运行时禁用节点拖拽 nodes={nodes.map(node => {
edges={edges} // 检查节点是否应该被隐藏
const isHidden = hiddenNodes.has(node.id);
// 应用透明度样式
const style = isHidden ? { opacity: 0.3 } : {};
return {
...node,
draggable: !currentAppIsRunning,
style: {
...node.style,
...style
}
};
})}
edges={edges.map(edge => {
// 检查边连接的节点是否被隐藏
const isSourceHidden = hiddenNodes.has(edge.source);
const isTargetHidden = hiddenNodes.has(edge.target);
// 如果源节点或目标节点被隐藏,则边也应用透明度
const style = (isSourceHidden || isTargetHidden) ? { opacity: 0.3 } : {};
// 更新边的数据,确保选择框也应用透明度
return {
...edge,
style: {
...edge.style,
...style
},
data: {
...edge.data,
...(isSourceHidden || isTargetHidden ? { hidden: true, opacity: 0.3 } : { opacity: 1 })
}
};
})}
nodeTypes={nodeTypes} nodeTypes={nodeTypes}
edgeTypes={edgeTypes} edgeTypes={edgeTypes}
snapToGrid={true} snapToGrid={true}
@ -279,8 +365,14 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
index === self.findIndex(n => n.id === node.id) index === self.findIndex(n => n.id === node.id)
); );
// 删除所有相关节点 // 删除所有相关节点和边
setNodes((nds) => nds.filter((n) => !nodesToRemove.find((d) => d.id === n.id))); setNodes((nds) => nds.filter((n) => !nodesToRemove.find((d) => d.id === n.id)));
// 删除与这些节点相关的所有边
const nodeIdsToRemove = nodesToRemove.map(node => node.id);
setEdges((eds) => eds.filter((e) =>
!nodeIdsToRemove.includes(e.source) && !nodeIdsToRemove.includes(e.target)
));
} }
else { else {
// 普通节点删除 // 普通节点删除

@ -135,7 +135,8 @@ const DataDisplayEdge: React.FC<EdgeProps> = ({
position: 'absolute', position: 'absolute',
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`, transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
fontSize: 12, fontSize: 12,
pointerEvents: 'all' pointerEvents: 'all',
opacity: style?.opacity || 1 // 应用透明度样式到标签容器
}} }}
className="nodrag nopan" className="nodrag nopan"
> >
@ -148,7 +149,8 @@ const DataDisplayEdge: React.FC<EdgeProps> = ({
border: isOpen ? '1px solid #1890ff' : '1px solid #d9d9d9', border: isOpen ? '1px solid #1890ff' : '1px solid #d9d9d9',
borderRadius: 4, borderRadius: 4,
backgroundColor: '#fff', backgroundColor: '#fff',
position: 'relative' position: 'relative',
opacity: style?.opacity || 1 // 应用透明度样式到下拉框
}} }}
> >
<div <div
@ -178,7 +180,8 @@ const DataDisplayEdge: React.FC<EdgeProps> = ({
backgroundColor: '#fff', backgroundColor: '#fff',
zIndex: 1000, zIndex: 1000,
maxHeight: 200, maxHeight: 200,
overflowY: 'auto' overflowY: 'auto',
opacity: style?.opacity || 1 // 应用透明度样式到下拉列表
}} }}
> >
{eventTopicList.map((option: { value: string; label: string }) => ( {eventTopicList.map((option: { value: string; label: string }) => (
@ -204,6 +207,7 @@ const DataDisplayEdge: React.FC<EdgeProps> = ({
{hovered && Object.keys(displayData).length === 0 && lineType !== 'data' && ( {hovered && Object.keys(displayData).length === 0 && lineType !== 'data' && (
<EdgeAddNodeButton <EdgeAddNodeButton
onClick={(e) => handleEdgeAddNode(e)} onClick={(e) => handleEdgeAddNode(e)}
style={{ opacity: style?.opacity || 1 }} // 应用透明度样式到添加节点按钮
/> />
)} )}
</div> </div>

@ -2,7 +2,6 @@ import React, { useMemo } from 'react';
import styles from '@/components/FlowEditor/node/style/baseOther.module.less'; import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
import { Handle, Position, useStore } from '@xyflow/react'; import { Handle, Position, useStore } from '@xyflow/react';
import { deserializeValue, isJSON } from '@/utils/common'; import { deserializeValue, isJSON } from '@/utils/common';
import cronstrue from 'cronstrue/i18n';
interface NodeContentData { interface NodeContentData {
parameters?: { parameters?: {
@ -121,7 +120,16 @@ const getGroupColor = (groupId: number) => {
'#faad14', // 黄色 '#faad14', // 黄色
'#f5222d', // 红色 '#f5222d', // 红色
'#722ed1', // 紫色 '#722ed1', // 紫色
'#13c2c2' // 青色 '#13c2c2', // 青色
'#eb2f96', // 粉色
'#a0d911', // 青绿色
'#ff7a45', // 橙色
'#9254de', // 深紫色
'#40a9ff', // 浅蓝色
'#ff4d4f', // 亮红色
'#36cfc9', // 深青色
'#fadb14', // 亮黄色
'#c41d7f' // 深粉色
]; ];
return colors[groupId % colors.length]; return colors[groupId % colors.length];
}; };
@ -224,25 +232,25 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
<div className={styles['node-api-box']}> <div className={styles['node-api-box']}>
<div className={styles['node-content-api']}> <div className={styles['node-content-api']}>
{apiIns.length > 0 && ( {/*{apiIns.length > 0 && (*/}
<div className={styles['node-inputs']}> <div className={styles['node-inputs']}>
{apiIns.map((input, index) => { {apiIns.map((input, index) => {
// 查找关联的事件分组 // 查找关联的事件分组
const group = findApiGroupByTopic(input, eventGroups); const group = findApiGroupByTopic(input, eventGroups);
return ( return (
<div <div
key={input.eventId || `input-${index}`} key={input.eventId || `input-${index}`}
className={styles['node-input-label']} className={styles['node-input-label']}
style={{ style={{
color: group ? group.color : '#000' color: group ? group.color : '#000'
}} }}
> >
{isValidApi(input) ? input.name : ''} {isValidApi(input) ? input.name : ''}
</div> </div>
); );
})} })}
</div> </div>
)} {/*)}*/}
{apiOuts.length > 0 && ( {apiOuts.length > 0 && (
<div className={styles['node-outputs']}> <div className={styles['node-outputs']}>
@ -273,25 +281,25 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
{/*content栏-data部分*/} {/*content栏-data部分*/}
<div className={styles['node-data-box']}> <div className={styles['node-data-box']}>
<div className={styles['node-content']}> <div className={styles['node-content']}>
{dataIns.length > 0 && ( {/*{dataIns.length > 0 && (*/}
<div className={styles['node-inputs']}> <div className={styles['node-inputs']}>
{dataIns.map((input, index) => { {dataIns.map((input, index) => {
// 查找关联的事件分组 // 查找关联的事件分组
const group = findRelatedEventGroup(input, eventGroups, 'eventSends'); const group = findRelatedEventGroup(input, eventGroups, 'eventSends');
return ( return (
<div <div
key={input.id || `input-${index}`} key={input.id || `input-${index}`}
className={styles['node-input-label']} className={styles['node-input-label']}
style={{ style={{
color: group ? group.color : '#000' color: group ? group.color : '#000'
}} }}
> >
{isValidData(input) ? `${input.name || input.id} ${input?.dataType}` : ''} {isValidData(input) ? `${input.name || input.id} ${input?.dataType}` : ''}
</div> </div>
); );
})} })}
</div> </div>
)} {/*)}*/}
{dataOuts.length > 0 && ( {dataOuts.length > 0 && (
<div className={styles['node-outputs']}> <div className={styles['node-outputs']}>

@ -202,7 +202,8 @@ const formatFooter = (data: any, eventListOld = []) => {
return `事件: ${currentEvent.name}`; return `事件: ${currentEvent.name}`;
} }
else { else {
const { name } = data.customDef; const { name, topic } = data.customDef;
if (topic.includes('**empty**')) return '';
return `事件: ${name}`; return `事件: ${name}`;
} }
case 'BASIC': case 'BASIC':
@ -215,6 +216,10 @@ const formatFooter = (data: any, eventListOld = []) => {
} }
}; };
const formatTitle = (text) => {
return text === 'start' || text === 'end' ? '' : text;
};
const NodeContent = ({ data }: { data: NodeContentData }) => { const NodeContent = ({ data }: { data: NodeContentData }) => {
const { eventListOld } = useSelector((state) => state.ideContainer); const { eventListOld } = useSelector((state) => state.ideContainer);
const apiIns = data.parameters?.apiIns || []; const apiIns = data.parameters?.apiIns || [];
@ -238,17 +243,17 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
<div className={styles['node-inputs']}> <div className={styles['node-inputs']}>
{apiIns.map((input, index) => ( {apiIns.map((input, index) => (
<div key={input.id || `input-${index}`} className={styles['node-input-label']}> <div key={input.id || `input-${index}`} className={styles['node-input-label']}>
{input.desc} {formatTitle(input.desc || input.id || input.name)}
</div> </div>
))} ))}
</div> </div>
)} )}
{apiOuts.length > 0 && ( {apiOuts.length > 0 && (
<div className={styles['node-outputs-api']}> <div className={styles['node-outputs']}>
{apiOuts.map((output, index) => ( {apiOuts.map((output, index) => (
<div key={output.id || `output-${index}`} className={styles['node-input-label']}> <div key={output.id || `output-${index}`} className={styles['node-input-label']}>
{output.dataType} {output.desc} {output.desc}
</div> </div>
))} ))}
</div> </div>
@ -267,7 +272,7 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
{/*content栏-data部分*/} {/*content栏-data部分*/}
<div className={styles['node-data-box']}> <div className={styles['node-data-box']}>
<div className={styles['node-content']}> <div className={styles['node-content']}>
{dataIns.length > 0 && !isStartNode && ( {!isStartNode && (
<div className={styles['node-inputs']}> <div className={styles['node-inputs']}>
{dataIns.map((input, index) => ( {dataIns.map((input, index) => (
<div key={input.id || `input-${index}`} className={styles['node-input-label']}> <div key={input.id || `input-${index}`} className={styles['node-input-label']}>

@ -0,0 +1,255 @@
import store from '@/store';
import { refPublish } from '@/api/appRes';
export const handelEventNodeList = (newRevertedData) => {
const { appRuntimeData, currentAppData } = store.getState().ideContainer;
const current = appRuntimeData[currentAppData.id];
const deleteEventSendNodeList = [];
const deleteEventlisteneList = [];
// 处理流程接口后的数据,这里是原始数据的依据
const currentEventSendNodeList = current.eventSendNodeList;
const currentEventlisteneList = current.eventlisteneList;
// 画布数据
const nodeEntries: [string, any][] = Object.entries(newRevertedData);
// 分类事件节点 [{nodeId:topic}]
const eventSendNodes = [];
const eventListenerNodes = [];
// 从nodeEntries中提取事件节点
nodeEntries.forEach(([nodeId, nodeConfig]) => {
if (nodeConfig.component?.type === 'EVENTSEND') {
try {
const customDef = JSON.parse(nodeConfig.component.customDef);
eventSendNodes.push({ nodeId, topic: customDef.topic });
} catch (e) {
eventSendNodes.push({ nodeId, topic: nodeConfig.component.customDef.topic });
}
}
else if (nodeConfig.component?.type === 'EVENTLISTENE') {
try {
const customDef = JSON.parse(nodeConfig.component.customDef);
eventListenerNodes.push({ nodeId, topic: customDef.topic });
} catch (e) {
eventListenerNodes.push({ nodeId, topic: nodeConfig.component.customDef.topic });
}
}
});
// 处理EVENTSEND节点
// 1. 原始数据中有但新数据中没有的节点,直接加入删除列表(节点被删除)
currentEventSendNodeList.forEach((eventNode) => {
const nodeId = Object.keys(eventNode)[0];
const nodeInCanvas = eventSendNodes.find(node => node.nodeId === nodeId);
if (!nodeInCanvas) {
// 画布数据中没有该节点,说明节点被删除了,直接加入删除列表
deleteEventSendNodeList.push(eventNode[nodeId]);
}
});
// 2. 遍历新数据中的每个事件节点
eventSendNodes.forEach((newNode) => {
const newNodeId = newNode.nodeId;
// nodeId格式为"节点类型-时间戳"
const newNodeParts = newNodeId.split('-');
const newNodeTimestamp = parseInt(newNodeParts[newNodeParts.length - 1]) || 0;
// 在原始数据中查找相同类型的节点
const sameTypeNodes = currentEventSendNodeList.filter(node => {
const nodeIdKey = Object.keys(node)[0];
const nodeIdParts = nodeIdKey.split('-');
// 比较节点类型部分(除了时间戳)
return nodeIdParts.slice(0, -1).join('-') === newNodeParts.slice(0, -1).join('-');
});
if (sameTypeNodes.length > 0) {
// 获取这些同类型节点中的最大时间戳
const maxTimestamp = Math.max(...sameTypeNodes.map(node => {
const nodeIdKey = Object.keys(node)[0];
return parseInt(nodeIdKey.split('-')[nodeIdKey.split('-').length - 1]) || 0;
}));
// 查找新节点在原始数据中的对应项
const correspondingNode = currentEventSendNodeList.find(node => {
const nodeIdKey = Object.keys(node)[0];
return nodeIdKey === newNodeId;
});
// 如果找到了对应的节点
if (correspondingNode) {
const correspondingNodeId = Object.keys(correspondingNode)[0];
const correspondingTopic = correspondingNode[correspondingNodeId];
// 比较topic是否一致
if (newNode.topic !== correspondingTopic) {
// topic不一致将原始数据的topic加入删除列表
deleteEventSendNodeList.push(correspondingTopic);
}
}
// 如果新节点的时间戳小于最大时间戳,则说明它是一个旧版本,应该被删除
else if (newNodeTimestamp < maxTimestamp) {
// 查找该节点在currentEventSendNodeList中的完整对象
const nodeToDelete = currentEventSendNodeList.find(node => {
const nodeIdKey = Object.keys(node)[0];
return nodeIdKey === newNodeId;
});
if (nodeToDelete) {
const nodeIdKey = Object.keys(nodeToDelete)[0];
deleteEventSendNodeList.push(nodeToDelete[nodeIdKey]);
}
}
}
// 如果sameTypeNodes为空说明这是一个全新的节点类型不需要处理
});
// 处理EVENTLISTENE节点
// 1. 原始数据中有但新数据中没有的节点,直接加入删除列表(节点被删除)
currentEventlisteneList.forEach((eventNode) => {
const nodeId = Object.keys(eventNode)[0];
const nodeInCanvas = eventListenerNodes.find(node => node.nodeId === nodeId);
if (!nodeInCanvas) {
// 画布数据中没有该节点,说明节点被删除了,直接加入删除列表
deleteEventlisteneList.push(eventNode[nodeId]);
}
});
// 2. 遍历新数据中的每个事件节点
eventListenerNodes.forEach((newNode) => {
const newNodeId = newNode.nodeId;
// nodeId格式为"节点类型-时间戳"
const newNodeParts = newNodeId.split('-');
const newNodeTimestamp = parseInt(newNodeParts[newNodeParts.length - 1]) || 0;
// 在原始数据中查找相同类型的节点
const sameTypeNodes = currentEventlisteneList.filter(node => {
const nodeIdKey = Object.keys(node)[0];
const nodeIdParts = nodeIdKey.split('-');
// 比较节点类型部分(除了时间戳)
return nodeIdParts.slice(0, -1).join('-') === newNodeParts.slice(0, -1).join('-');
});
if (sameTypeNodes.length > 0) {
// 获取这些同类型节点中的最大时间戳
const maxTimestamp = Math.max(...sameTypeNodes.map(node => {
const nodeIdKey = Object.keys(node)[0];
return parseInt(nodeIdKey.split('-')[nodeIdKey.split('-').length - 1]) || 0;
}));
// 查找新节点在原始数据中的对应项
const correspondingNode = currentEventlisteneList.find(node => {
const nodeIdKey = Object.keys(node)[0];
return nodeIdKey === newNodeId;
});
// 如果找到了对应的节点
if (correspondingNode) {
const correspondingNodeId = Object.keys(correspondingNode)[0];
const correspondingTopic = correspondingNode[correspondingNodeId];
// 比较topic是否一致
if (newNode.topic !== correspondingTopic) {
// topic不一致将原始数据的topic加入删除列表
deleteEventlisteneList.push(correspondingTopic);
}
}
// 如果新节点的时间戳小于最大时间戳,则说明它是一个旧版本,应该被删除
else if (newNodeTimestamp < maxTimestamp) {
// 查找该节点在currentEventlisteneList中的完整对象
const nodeToDelete = currentEventlisteneList.find(node => {
const nodeIdKey = Object.keys(node)[0];
return nodeIdKey === newNodeId;
});
if (nodeToDelete) {
const nodeIdKey = Object.keys(nodeToDelete)[0];
deleteEventlisteneList.push(nodeToDelete[nodeIdKey]);
}
}
}
// 如果sameTypeNodes为空说明这是一个全新的节点类型不需要处理
});
return {
deleteEventSendNodeList,
deleteEventlisteneList
};
};
export const updateEvent = (revertedData, appid) => {
// 初始化参数对象
const params: any = {
eventListenes: [],
eventSends: []
};
// 遍历所有节点,查找事件相关节点
Object.entries(revertedData).forEach(([nodeId, nodeConfig]: [string, any]) => {
// 检查节点是否有component属性和type定义
if (nodeConfig.component?.type) {
const nodeType = nodeConfig.component.type;
const customDef = nodeConfig.component.customDef;
// 解析customDef可能是字符串也可能是对象
let eventConfig;
if (typeof customDef === 'string') {
try {
eventConfig = JSON.parse(customDef);
} catch (e) {
console.error('Failed to parse customDef:', e);
return;
}
}
else {
eventConfig = customDef;
}
// 根据节点类型处理事件节点
if (nodeType === 'EVENTLISTENE') {
// 事件接收节点
params.eventListenes.push({
nodeName: nodeConfig.nodeName || '',
eventId: eventConfig?.eventId || null,
topic: eventConfig?.topic || '',
eventName: eventConfig?.name || '',
dataOuts: nodeConfig.dataOuts || [],
nodeId: nodeConfig.nodeId
});
}
else if (nodeType === 'EVENTSEND') {
// 事件发送节点
params.eventSends.push({
nodeName: nodeConfig.nodeName || '',
eventId: eventConfig?.eventId || null,
topic: eventConfig?.topic || '',
eventName: eventConfig?.name || '',
dataIns: nodeConfig.dataIns || [],
nodeId: nodeConfig.nodeId
});
}
}
});
// 调用更新事件的API
if (params.eventListenes.length > 0 || params.eventSends.length > 0) {
return params;
}
else return null;
};
export const upDatePublish = async (revertedData) => {
const { currentAppData } = store.getState().ideContainer;
const params = {
appId: currentAppData.id,
publishAppId: []
};
revertedData.forEach(item => {
if (item?.component && item.component.type === 'SUB' && !item.component.customDef) params.publishAppId.push(item.component.compId); // 复合组件的这个compId实际上就是flowHousVO里面的id
});
if (params.publishAppId.length > 0) {
const res: any = await refPublish(params);
if (res.code === 200) return res.data;
else return {};
}
else return {};
};

@ -3,6 +3,7 @@ import { ResizeBox, Tabs } from '@arco-design/web-react';
import styles from './style/logBar.module.less'; import styles from './style/logBar.module.less';
import { updateLogBarStatus } from '@/store/ideContainer'; import { updateLogBarStatus } from '@/store/ideContainer';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { getNodeData } from '@/api/appIns'; // 添加导入
const TabPane = Tabs.TabPane; const TabPane = Tabs.TabPane;
@ -13,6 +14,11 @@ interface LogMessage {
timestamp: string; timestamp: string;
} }
// 添加运行数据接口
interface RuntimeData {
[appId: string]: any;
}
interface LogBarProps { interface LogBarProps {
a?: string; a?: string;
} }
@ -50,6 +56,9 @@ const LogBar: React.FC<LogBarProps> = () => {
const [runtimeLogs, setRuntimeLogs] = useState<LogMessage[]>([]); // 添加运行时日志状态 const [runtimeLogs, setRuntimeLogs] = useState<LogMessage[]>([]); // 添加运行时日志状态
const [logContainerHeight, setLogContainerHeight] = useState('250px'); // 添加日志容器高度状态 const [logContainerHeight, setLogContainerHeight] = useState('250px'); // 添加日志容器高度状态
const { currentAppData } = useSelector((state: any) => state.ideContainer); const { currentAppData } = useSelector((state: any) => state.ideContainer);
// 添加运行数据状态
const [runtimeData, setRuntimeData] = useState<RuntimeData>({});
const [loading, setLoading] = useState(false);
// 处理 Tab 点击事件 // 处理 Tab 点击事件
const handleTabClick = (key: string) => { const handleTabClick = (key: string) => {
@ -121,13 +130,13 @@ const LogBar: React.FC<LogBarProps> = () => {
// 自动切换到运行日志tab并展开logBar // 自动切换到运行日志tab并展开logBar
setActiveTab('1'); setActiveTab('1');
dispatch(updateLogBarStatus(true)); dispatch(updateLogBarStatus(true));
// 同时将日志添加到当前应用的运行日志中 // 同时将日志添加到当前应用的运行日志中
const appId = currentAppData?.id; const appId = currentAppData?.id;
if (appId) { if (appId) {
dispatch({ dispatch({
type: 'ideContainer/addRuntimeLog', type: 'ideContainer/addRuntimeLog',
payload: { payload: {
log: newLog, log: newLog,
appId: appId appId: appId
} }
@ -145,6 +154,42 @@ const LogBar: React.FC<LogBarProps> = () => {
}; };
}, [dispatch, currentAppData?.id]); }, [dispatch, currentAppData?.id]);
// 实现轮询获取运行数据
useEffect(() => {
let intervalId: NodeJS.Timeout | null = null;
// 只有在当前tab是运行数据且有当前应用时才开始轮询
if (activeTab === '3' && currentAppData?.id && logBarStatus) {
const fetchRuntimeData = async () => {
try {
setLoading(true);
const response = await getNodeData(currentAppData.id);
setRuntimeData(prev => ({
...prev,
[currentAppData.id]: response.data
}));
} catch (error) {
console.error('获取运行数据失败:', error);
} finally {
setLoading(false);
}
};
// 立即获取一次数据
fetchRuntimeData();
// 设置轮询每5秒获取一次数据
intervalId = setInterval(fetchRuntimeData, 3000);
}
// 清理函数,组件卸载或条件不满足时清除定时器
return () => {
if (intervalId) {
clearInterval(intervalId);
}
};
}, [activeTab, currentAppData?.id, logBarStatus]);
// 渲染校验日志内容 // 渲染校验日志内容
const renderValidationLogs = () => { const renderValidationLogs = () => {
return ( return (
@ -170,10 +215,10 @@ const LogBar: React.FC<LogBarProps> = () => {
// 渲染运行时日志内容 // 渲染运行时日志内容
const renderRuntimeLogs = () => { const renderRuntimeLogs = () => {
// 获取当前应用的运行日志 // 获取当前应用的运行日志
const currentAppLogs = currentAppData?.id && appRuntimeData[currentAppData.id] const currentAppLogs = currentAppData?.id && appRuntimeData[currentAppData.id]
? appRuntimeData[currentAppData.id].logs || [] ? appRuntimeData[currentAppData.id].logs || []
: []; : [];
return ( return (
<div style={{ padding: '10px', height: 'calc(100% - 40px)', overflowY: 'auto' }}> <div style={{ padding: '10px', height: 'calc(100% - 40px)', overflowY: 'auto' }}>
{currentAppLogs.length === 0 ? ( {currentAppLogs.length === 0 ? (
@ -194,6 +239,28 @@ const LogBar: React.FC<LogBarProps> = () => {
); );
}; };
// 渲染运行数据内容
const renderRuntimeData = () => {
const currentAppDataContent = currentAppData?.id ? runtimeData[currentAppData.id] : null;
return (
<div style={{ padding: '10px', height: 'calc(100% - 40px)', overflowY: 'auto' }}>
{loading ? (
<p>...</p>
) : !currentAppData?.id ? (
<p></p>
) : !currentAppDataContent ? (
<p></p>
) : (
<div>
<h3>: {currentAppData.name}</h3>
<pre>{JSON.stringify(currentAppDataContent, null, 2)}</pre>
</div>
)}
</div>
);
};
return ( return (
<> <>
<ResizeBox <ResizeBox
@ -215,7 +282,8 @@ const LogBar: React.FC<LogBarProps> = () => {
<TabPane destroyOnHide key={x.key} title={x.title}> <TabPane destroyOnHide key={x.key} title={x.title}>
{x.key === '1' ? renderRuntimeLogs() : {x.key === '1' ? renderRuntimeLogs() :
x.key === '2' ? renderValidationLogs() : x.key === '2' ? renderValidationLogs() :
x.content} x.key === '3' ? renderRuntimeData() : // 添加运行数据渲染
x.content}
</TabPane> </TabPane>
))} ))}
</Tabs> </Tabs>

@ -12,9 +12,17 @@ import {
Menu, Menu,
Popconfirm Popconfirm
} from '@arco-design/web-react'; } from '@arco-design/web-react';
import { IconApps, IconMore, IconDelete, IconEdit } from '@arco-design/web-react/icon'; import {
IconApps,
IconMore,
IconDelete,
IconEdit,
IconEye,
IconSearch,
IconPlus,
IconEyeInvisible
} from '@arco-design/web-react/icon';
import { menuData1, menuData2 } from './config/menuData'; import { menuData1, menuData2 } from './config/menuData';
import { IconSearch, IconPlus } from '@arco-design/web-react/icon';
import { Selected } from '@/pages/ideContainer/types'; import { Selected } from '@/pages/ideContainer/types';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { import {
@ -22,7 +30,8 @@ import {
updateFlowData, updateFlowData,
updateCanvasDataMap, updateCanvasDataMap,
updateCurrentAppData, updateCurrentAppData,
updateEventListOld updateEventListOld,
updateEventNodeList
} from '@/store/ideContainer'; } from '@/store/ideContainer';
import { addApp, getProjectEnv, editApp, deleteApp } from '@/api/apps'; import { addApp, getProjectEnv, editApp, deleteApp } from '@/api/apps';
import _ from 'lodash'; import _ from 'lodash';
@ -197,6 +206,8 @@ const SideBar: React.FC<SideBarProps> = ({
y: 0, y: 0,
nodeData: null nodeData: null
}); // 添加右键菜单状态 }); // 添加右键菜单状态
// 用于存储隐藏的节点ID
const [hiddenNodes, setHiddenNodes] = useState<Set<string>>(new Set());
const resizeBoxRef = useRef<HTMLDivElement>(null); // 引用第一个 ResizeBox 容器 const resizeBoxRef = useRef<HTMLDivElement>(null); // 引用第一个 ResizeBox 容器
const contextMenuRef = useRef<HTMLDivElement>(null); // 右键菜单引用 const contextMenuRef = useRef<HTMLDivElement>(null); // 右键菜单引用
const { menuData, info, canvasDataMap } = useSelector(state => state.ideContainer); const { menuData, info, canvasDataMap } = useSelector(state => state.ideContainer);
@ -430,6 +441,10 @@ const SideBar: React.FC<SideBarProps> = ({
dispatch(updateFlowData({ [data.id]: res.data })); dispatch(updateFlowData({ [data.id]: res.data }));
// 更新 currentAppData 中的数据 // 更新 currentAppData 中的数据
dispatch(updateCurrentAppData({ ...findMenuItem(menuData[identity], children.key) })); dispatch(updateCurrentAppData({ ...findMenuItem(menuData[identity], children.key) }));
dispatch(updateEventNodeList({
eventSendNodeList: [],
eventlisteneList: []
}));
// 同步更新到 canvasDataMap // 同步更新到 canvasDataMap
if (res.data.main?.components) { if (res.data.main?.components) {
@ -475,6 +490,7 @@ const SideBar: React.FC<SideBarProps> = ({
); );
} }
else { else {
if (item.title.includes('隐藏事件')) return null;
return (<TreeNode return (<TreeNode
{...treeNodeProps} {...treeNodeProps}
title={item.title} title={item.title}
@ -550,12 +566,36 @@ const SideBar: React.FC<SideBarProps> = ({
}; };
}, []); }, []);
// 监听自定义事件以更新隐藏节点状态
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);
};
}, []);
// 渲染节点的额外操作按钮 // 渲染节点的额外操作按钮
const renderNodeExtra = (node) => { const renderNodeExtra = (node) => {
// 只有当 node.dataRef.id 存在时才渲染操作按钮 // 只有当 node.dataRef.id 存在时才渲染操作按钮
if (!node.dataRef?.id) { if (!node.dataRef?.id) return null;
return null;
}
const dropList = ( const dropList = (
<Menu> <Menu>
@ -621,6 +661,44 @@ const SideBar: React.FC<SideBarProps> = ({
); );
}; };
const renderNodeExtraEye = (node) => {
// console.log('node:', node);
// 只有当 node.dataRef.id 存在时才渲染操作按钮
if (!node.dataRef?.id) return null;
// 检查节点当前是否被隐藏
const isNodeHidden = hiddenNodes?.has(node.dataRef.id);
function handleEyeClick(e) {
e.stopPropagation();
// 发送自定义事件,通知流程图组件隐藏/显示节点
const event = new CustomEvent('toggleNodeVisibility', {
detail: {
appId: node.dataRef.id,
isVisible: isNodeHidden // 如果当前是隐藏的,点击后应该显示
}
});
document.dispatchEvent(event);
}
return (
<div
style={{
position: 'absolute',
right: 8,
fontSize: 20,
fontWeight: 700,
top: 10,
color: '#000000',
cursor: 'pointer'
}}
onClick={handleEyeClick}
>
{isNodeHidden ? <IconEye /> : <IconEyeInvisible />}
</div>
);
};
return ( return (
<div <div
className={styles['sider']} className={styles['sider']}
@ -698,7 +776,7 @@ const SideBar: React.FC<SideBarProps> = ({
} }
}} }}
style={{ background: 'transparent' }} // 移除背景色 style={{ background: 'transparent' }} // 移除背景色
renderExtra={selected?.parentKey === 'appList' ? renderNodeExtra : null} renderExtra={selected?.parentKey === 'appList' ? renderNodeExtra : renderNodeExtraEye}
> >
{renderMenuItems(filteredMenu[activeKey]?.children)} {renderMenuItems(filteredMenu[activeKey]?.children)}
</Tree> </Tree>

@ -21,8 +21,8 @@ interface IDEContainerState {
isRunning: boolean; isRunning: boolean;
logs: any[]; logs: any[];
runId: string; runId: string;
eventSendNodeList: string[], eventSendNodeList: any[], // [{nodeID:topic}]
eventlisteneList: string[] eventlisteneList: any[] // [{nodeID:topic}]
}>; }>;
} }

@ -75,13 +75,35 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
const nodeConfig: any = entry[1]; const nodeConfig: any = entry[1];
// 更新应用中的事件节点列表 // 更新应用中的事件节点列表
if (nodeId.includes('EVENTLISTENE')) eventlisteneList.push(nodeId); if (nodeId.includes('EVENTLISTENE')) {
else if (nodeId.includes('EVENTSEND')) eventSendNodeList.push(nodeId); try {
const customDef = JSON.parse(nodeConfig.component.customDef);
// 使用展开运算符创建新数组,避免修改冻结对象
eventlisteneList.splice(eventlisteneList.length, 0, { [nodeId]: customDef.topic });
} catch (error) {
console.log(error);
}
}
else if (nodeId.includes('EVENTSEND')) {
try {
const customDef = JSON.parse(nodeConfig.component.customDef);
// 使用展开运算符创建新数组,避免修改冻结对象
eventSendNodeList.splice(eventSendNodeList.length, 0, { [nodeId]: customDef.topic });
} catch (error) {
console.log(error);
}
}
if (eventlisteneList.length > 0 || eventSendNodeList.length > 0) store.dispatch(updateEventNodeList({ if (eventlisteneList.length > 0 || eventSendNodeList.length > 0) store.dispatch(updateEventNodeList({
eventSendNodeList, eventSendNodeList: [...eventSendNodeList],
eventlisteneList eventlisteneList: [...eventlisteneList]
})); }));
else {
store.dispatch(updateEventNodeList({
eventSendNodeList: [],
eventlisteneList: []
}));
}
// 确定节点类型 // 确定节点类型
let nodeType = 'BASIC'; let nodeType = 'BASIC';
@ -304,7 +326,11 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
target: target, target: target,
sourceHandle: finalSourceHandle, sourceHandle: finalSourceHandle,
targetHandle: finalTargetHandle, targetHandle: finalTargetHandle,
type: 'custom' type: 'custom',
lineType: 'api',
data: {
lineType: 'api'
}
}); });
} }
} }
@ -340,7 +366,11 @@ export const convertFlowData = (flowData: any, useDefault = true) => {
target: targetNodeId, target: targetNodeId,
sourceHandle: sourceHandle, sourceHandle: sourceHandle,
targetHandle: targetHandle, targetHandle: targetHandle,
type: 'custom' type: 'custom',
lineType: 'data',
data: {
lineType: 'data'
}
}); });
} }
} }
@ -506,7 +536,7 @@ export const reverseConvertFlowData = (nodes: any[], edges: any[], complexKV: an
}; };
// 处理 component 信息 // 处理 component 信息
if (node.type === 'SUB' && !node.customDef) { if (node.type === 'SUB' && !node.data.component.customDef) {
nodeConfig.component = { nodeConfig.component = {
type: 'SUB', type: 'SUB',
compId: node.data.compId, compId: node.data.compId,

Loading…
Cancel
Save