diff --git a/src/api/interface/index.ts b/src/api/interface/index.ts index e3455d1..5c5ba29 100644 --- a/src/api/interface/index.ts +++ b/src/api/interface/index.ts @@ -205,7 +205,7 @@ export interface DeleteEventParams { export interface DeleteEventForAppParams { appId: string, - eventIds: string[] + topics: string[] } // runtime diff --git a/src/components/FlowEditor/node/style/baseOther.module.less b/src/components/FlowEditor/node/style/baseOther.module.less index e3f2ca2..e0a4444 100644 --- a/src/components/FlowEditor/node/style/baseOther.module.less +++ b/src/components/FlowEditor/node/style/baseOther.module.less @@ -49,6 +49,7 @@ padding: 0 5px; border: 1px solid #cccccc; border-radius: 3px; + justify-content: space-between; .node-inputs { padding-right: 10px; @@ -58,8 +59,9 @@ padding-left: 10px; } - .node-inputs, - .node-outputs, + + //.node-inputs, + //.node-outputs, .node-inputs-api, .node-outputs-api { flex: 1; diff --git a/src/hooks/useFlowCallbacks.ts b/src/hooks/useFlowCallbacks.ts index 90b0121..8d8b294 100644 --- a/src/hooks/useFlowCallbacks.ts +++ b/src/hooks/useFlowCallbacks.ts @@ -7,7 +7,7 @@ import { Node, Edge } 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 { nodeTypeMap, registerNodeType } from '@/components/FlowEditor/node'; import { convertFlowData, reverseConvertFlowData, revertFlowData } from '@/utils/convertFlowData'; @@ -22,7 +22,7 @@ import { updateEventListOld, addRuntimeLog, clearRuntimeLogs, - updateRuntimeId + updateRuntimeId, updateFlowData } from '@/store/ideContainer'; import { validateAllNodes, @@ -36,13 +36,14 @@ import { } 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 } from '@/api/event'; +import { queryEventItemBySceneIdOld, deleteEventSub, deleteEventPub } from '@/api/event'; export const useFlowCallbacks = ( nodes: Node[], @@ -810,7 +811,11 @@ export const useFlowCallbacks = ( target: loopStartNode.id, sourceHandle: edgeForNodeAdd.sourceHandle, targetHandle: 'start', // 循环开始节点的输入句柄 - type: 'custom' + type: 'custom', + lineType: 'api', + data: { + lineType: 'api' + } }, { id: `e${loopEndNode.id}-${edgeForNodeAdd.target}`, @@ -818,7 +823,11 @@ export const useFlowCallbacks = ( target: edgeForNodeAdd.target, sourceHandle: 'break', // 循环结束节点的输出句柄 targetHandle: edgeForNodeAdd.targetHandle, - type: 'custom' + type: 'custom', + lineType: 'api', + data: { + lineType: 'api' + } } ]; @@ -903,18 +912,24 @@ export const useFlowCallbacks = ( }; } else if (nodeType === 'SUB') { - const flowMainData = flowData[currentAppData.id]?.main?.components; - const sameData: any = Object.values(flowMainData).filter((item: any) => { - if (item?.component?.compId === newNode.data.compId) return item; - }); - if (sameData.length) { + const flowSubMap = flowData[currentAppData.id]?.subMap || {}; + const sameData: any = flowSubMap[newNode.data.compId]; + if (sameData) { 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 { newNode.data.component = { - type: nodeType + type: nodeType, + compId: newNode.data.compId }; } } @@ -977,7 +992,11 @@ export const useFlowCallbacks = ( target: newNode.id, sourceHandle: edgeForNodeAdd.sourceHandle, targetHandle: newNodeTargetHandle, - type: 'custom' + type: 'custom', + lineType: 'api', + data: { + lineType: 'api' + } }, { id: `e${newNode.id}-${edgeForNodeAdd.target}`, @@ -985,7 +1004,11 @@ export const useFlowCallbacks = ( target: edgeForNodeAdd.target, sourceHandle: newNodeSourceHandle, targetHandle: edgeForNodeAdd.targetHandle, - type: 'custom' + type: 'custom', + lineType: 'api', + data: { + lineType: 'api' + } } ]; @@ -1044,18 +1067,24 @@ export const useFlowCallbacks = ( }; } else if (nodeType === 'SUB') { - const flowMainData = flowData[currentAppData.id]?.main?.components; - const sameData: any = Object.values(flowMainData).filter((item: any) => { - if (item?.component?.compId === newNode.data.compId) return item; - }); - if (sameData.length) { + const flowSubMap = flowData[currentAppData.id]?.subMap || {}; + const sameData: any = flowSubMap[newNode.data.compId]; + if (sameData) { 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 { newNode.data.component = { - type: nodeType + type: nodeType, + compId: newNode.data.compId }; } } @@ -1111,168 +1140,7 @@ export const useFlowCallbacks = ( }, [edgeForNodeAdd, positionForNodeAdd, addNodeOnEdge, addNodeOnPane]); // 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 () => { if (useDefault) { try { @@ -1295,8 +1163,7 @@ export const useFlowCallbacks = ( const newRevertedData = reverseConvertFlowData(nodes, edges, upDatePublishCB); const { flowData, currentAppData, info } = store.getState().ideContainer; const { deleteEventSendNodeList, deleteEventlisteneList } = handelEventNodeList(newRevertedData); - // console.log(deleteEventSendNodeList, deleteEventlisteneList); - // return; + let params = {}; // 更新复合组件/子流程 if (currentAppData.key.includes('sub')) { @@ -1313,6 +1180,25 @@ export const useFlowCallbacks = ( // 更新事件枚举表 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); @@ -1339,23 +1225,45 @@ export const useFlowCallbacks = ( ...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 params = { - nodes, - edges - }; const appFlowParams = { - appEventList: {} + appEventList: {}, + eventEdges: [] }; nodes.forEach(node => { @@ -1367,6 +1275,20 @@ export const useFlowCallbacks = ( 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; diff --git a/src/pages/flowEditor/FlowEditorMain.tsx b/src/pages/flowEditor/FlowEditorMain.tsx index fb35c24..2769ae1 100644 --- a/src/pages/flowEditor/FlowEditorMain.tsx +++ b/src/pages/flowEditor/FlowEditorMain.tsx @@ -139,6 +139,34 @@ const FlowEditorMain: React.FC = (props) => { const { undo, redo, canUndo, canRedo } = useHistory(); const reactFlowId = useMemo(() => new Date().getTime().toString(), []); + // 用于存储隐藏的节点ID + const [hiddenNodes, setHiddenNodes] = React.useState>(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中获取当前应用的运行状态 const { appRuntimeData, currentAppData } = useSelector((state: any) => state.ideContainer); const currentAppIsRunning = currentAppData?.id && appRuntimeData[currentAppData.id] @@ -148,6 +176,31 @@ const FlowEditorMain: React.FC = (props) => { // 在应用编排模式下(useDefault为false)禁用删除功能 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(() => { const handleKeyDown = (e: KeyboardEvent) => { @@ -188,8 +241,41 @@ const FlowEditorMain: React.FC = (props) => { onContextMenu={(e) => e.preventDefault()}> ({ ...node, draggable: !currentAppIsRunning }))} // 运行时禁用节点拖拽 - edges={edges} + nodes={nodes.map(node => { + // 检查节点是否应该被隐藏 + 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} edgeTypes={edgeTypes} snapToGrid={true} @@ -279,8 +365,14 @@ const FlowEditorMain: React.FC = (props) => { index === self.findIndex(n => n.id === node.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 { // 普通节点删除 diff --git a/src/pages/flowEditor/components/customEdge.tsx b/src/pages/flowEditor/components/customEdge.tsx index df19e51..c7705c2 100644 --- a/src/pages/flowEditor/components/customEdge.tsx +++ b/src/pages/flowEditor/components/customEdge.tsx @@ -135,7 +135,8 @@ const DataDisplayEdge: React.FC = ({ position: 'absolute', transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`, fontSize: 12, - pointerEvents: 'all' + pointerEvents: 'all', + opacity: style?.opacity || 1 // 应用透明度样式到标签容器 }} className="nodrag nopan" > @@ -148,7 +149,8 @@ const DataDisplayEdge: React.FC = ({ border: isOpen ? '1px solid #1890ff' : '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#fff', - position: 'relative' + position: 'relative', + opacity: style?.opacity || 1 // 应用透明度样式到下拉框 }} >
= ({ backgroundColor: '#fff', zIndex: 1000, maxHeight: 200, - overflowY: 'auto' + overflowY: 'auto', + opacity: style?.opacity || 1 // 应用透明度样式到下拉列表 }} > {eventTopicList.map((option: { value: string; label: string }) => ( @@ -204,6 +207,7 @@ const DataDisplayEdge: React.FC = ({ {hovered && Object.keys(displayData).length === 0 && lineType !== 'data' && ( handleEdgeAddNode(e)} + style={{ opacity: style?.opacity || 1 }} // 应用透明度样式到添加节点按钮 /> )}
diff --git a/src/pages/flowEditor/components/nodeContentApp.tsx b/src/pages/flowEditor/components/nodeContentApp.tsx index d43d04b..c5d9d06 100644 --- a/src/pages/flowEditor/components/nodeContentApp.tsx +++ b/src/pages/flowEditor/components/nodeContentApp.tsx @@ -2,7 +2,6 @@ import React, { useMemo } from 'react'; import styles from '@/components/FlowEditor/node/style/baseOther.module.less'; import { Handle, Position, useStore } from '@xyflow/react'; import { deserializeValue, isJSON } from '@/utils/common'; -import cronstrue from 'cronstrue/i18n'; interface NodeContentData { parameters?: { @@ -121,7 +120,16 @@ const getGroupColor = (groupId: number) => { '#faad14', // 黄色 '#f5222d', // 红色 '#722ed1', // 紫色 - '#13c2c2' // 青色 + '#13c2c2', // 青色 + '#eb2f96', // 粉色 + '#a0d911', // 青绿色 + '#ff7a45', // 橙色 + '#9254de', // 深紫色 + '#40a9ff', // 浅蓝色 + '#ff4d4f', // 亮红色 + '#36cfc9', // 深青色 + '#fadb14', // 亮黄色 + '#c41d7f' // 深粉色 ]; return colors[groupId % colors.length]; }; @@ -224,25 +232,25 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
- {apiIns.length > 0 && ( -
- {apiIns.map((input, index) => { - // 查找关联的事件分组 - const group = findApiGroupByTopic(input, eventGroups); - return ( -
- {isValidApi(input) ? input.name : ''} -
- ); - })} -
- )} + {/*{apiIns.length > 0 && (*/} +
+ {apiIns.map((input, index) => { + // 查找关联的事件分组 + const group = findApiGroupByTopic(input, eventGroups); + return ( +
+ {isValidApi(input) ? input.name : ''} +
+ ); + })} +
+ {/*)}*/} {apiOuts.length > 0 && (
@@ -273,25 +281,25 @@ const NodeContent = ({ data }: { data: NodeContentData }) => { {/*content栏-data部分*/}
- {dataIns.length > 0 && ( -
- {dataIns.map((input, index) => { - // 查找关联的事件分组 - const group = findRelatedEventGroup(input, eventGroups, 'eventSends'); - return ( -
- {isValidData(input) ? `${input.name || input.id} ${input?.dataType}` : ''} -
- ); - })} -
- )} + {/*{dataIns.length > 0 && (*/} +
+ {dataIns.map((input, index) => { + // 查找关联的事件分组 + const group = findRelatedEventGroup(input, eventGroups, 'eventSends'); + return ( +
+ {isValidData(input) ? `${input.name || input.id} ${input?.dataType}` : ''} +
+ ); + })} +
+ {/*)}*/} {dataOuts.length > 0 && (
diff --git a/src/pages/flowEditor/components/nodeContentOther.tsx b/src/pages/flowEditor/components/nodeContentOther.tsx index fd66f09..b78785a 100644 --- a/src/pages/flowEditor/components/nodeContentOther.tsx +++ b/src/pages/flowEditor/components/nodeContentOther.tsx @@ -202,7 +202,8 @@ const formatFooter = (data: any, eventListOld = []) => { return `事件: ${currentEvent.name}`; } else { - const { name } = data.customDef; + const { name, topic } = data.customDef; + if (topic.includes('**empty**')) return ''; return `事件: ${name}`; } 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 { eventListOld } = useSelector((state) => state.ideContainer); const apiIns = data.parameters?.apiIns || []; @@ -238,17 +243,17 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
{apiIns.map((input, index) => (
- {input.desc} + {formatTitle(input.desc || input.id || input.name)}
))}
)} {apiOuts.length > 0 && ( -
+
{apiOuts.map((output, index) => (
- {output.dataType} {output.desc} + {output.desc}
))}
@@ -267,7 +272,7 @@ const NodeContent = ({ data }: { data: NodeContentData }) => { {/*content栏-data部分*/}
- {dataIns.length > 0 && !isStartNode && ( + {!isStartNode && (
{dataIns.map((input, index) => (
diff --git a/src/pages/flowEditor/utils/common.ts b/src/pages/flowEditor/utils/common.ts new file mode 100644 index 0000000..e01145a --- /dev/null +++ b/src/pages/flowEditor/utils/common.ts @@ -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 {}; +}; \ No newline at end of file diff --git a/src/pages/ideContainer/logBar.tsx b/src/pages/ideContainer/logBar.tsx index e233d01..a36aeef 100644 --- a/src/pages/ideContainer/logBar.tsx +++ b/src/pages/ideContainer/logBar.tsx @@ -3,6 +3,7 @@ import { ResizeBox, Tabs } from '@arco-design/web-react'; import styles from './style/logBar.module.less'; import { updateLogBarStatus } from '@/store/ideContainer'; import { useSelector, useDispatch } from 'react-redux'; +import { getNodeData } from '@/api/appIns'; // 添加导入 const TabPane = Tabs.TabPane; @@ -13,6 +14,11 @@ interface LogMessage { timestamp: string; } +// 添加运行数据接口 +interface RuntimeData { + [appId: string]: any; +} + interface LogBarProps { a?: string; } @@ -50,6 +56,9 @@ const LogBar: React.FC = () => { const [runtimeLogs, setRuntimeLogs] = useState([]); // 添加运行时日志状态 const [logContainerHeight, setLogContainerHeight] = useState('250px'); // 添加日志容器高度状态 const { currentAppData } = useSelector((state: any) => state.ideContainer); + // 添加运行数据状态 + const [runtimeData, setRuntimeData] = useState({}); + const [loading, setLoading] = useState(false); // 处理 Tab 点击事件 const handleTabClick = (key: string) => { @@ -121,13 +130,13 @@ const LogBar: React.FC = () => { // 自动切换到运行日志tab并展开logBar setActiveTab('1'); dispatch(updateLogBarStatus(true)); - + // 同时将日志添加到当前应用的运行日志中 const appId = currentAppData?.id; if (appId) { dispatch({ type: 'ideContainer/addRuntimeLog', - payload: { + payload: { log: newLog, appId: appId } @@ -145,6 +154,42 @@ const LogBar: React.FC = () => { }; }, [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 = () => { return ( @@ -170,10 +215,10 @@ const LogBar: React.FC = () => { // 渲染运行时日志内容 const renderRuntimeLogs = () => { // 获取当前应用的运行日志 - const currentAppLogs = currentAppData?.id && appRuntimeData[currentAppData.id] + const currentAppLogs = currentAppData?.id && appRuntimeData[currentAppData.id] ? appRuntimeData[currentAppData.id].logs || [] : []; - + return (
{currentAppLogs.length === 0 ? ( @@ -194,6 +239,28 @@ const LogBar: React.FC = () => { ); }; + // 渲染运行数据内容 + const renderRuntimeData = () => { + const currentAppDataContent = currentAppData?.id ? runtimeData[currentAppData.id] : null; + + return ( +
+ {loading ? ( +

加载中...

+ ) : !currentAppData?.id ? ( +

请选择应用

+ ) : !currentAppDataContent ? ( +

暂无运行数据

+ ) : ( +
+

应用: {currentAppData.name}

+
{JSON.stringify(currentAppDataContent, null, 2)}
+
+ )} +
+ ); + }; + return ( <> = () => { {x.key === '1' ? renderRuntimeLogs() : x.key === '2' ? renderValidationLogs() : - x.content} + x.key === '3' ? renderRuntimeData() : // 添加运行数据渲染 + x.content} ))} diff --git a/src/pages/ideContainer/sideBar.tsx b/src/pages/ideContainer/sideBar.tsx index 6b14f5b..f53bbc0 100644 --- a/src/pages/ideContainer/sideBar.tsx +++ b/src/pages/ideContainer/sideBar.tsx @@ -12,9 +12,17 @@ import { Menu, Popconfirm } 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 { IconSearch, IconPlus } from '@arco-design/web-react/icon'; import { Selected } from '@/pages/ideContainer/types'; import { useDispatch, useSelector } from 'react-redux'; import { @@ -22,7 +30,8 @@ import { updateFlowData, updateCanvasDataMap, updateCurrentAppData, - updateEventListOld + updateEventListOld, + updateEventNodeList } from '@/store/ideContainer'; import { addApp, getProjectEnv, editApp, deleteApp } from '@/api/apps'; import _ from 'lodash'; @@ -197,6 +206,8 @@ const SideBar: React.FC = ({ y: 0, nodeData: null }); // 添加右键菜单状态 + // 用于存储隐藏的节点ID + const [hiddenNodes, setHiddenNodes] = useState>(new Set()); const resizeBoxRef = useRef(null); // 引用第一个 ResizeBox 容器 const contextMenuRef = useRef(null); // 右键菜单引用 const { menuData, info, canvasDataMap } = useSelector(state => state.ideContainer); @@ -430,6 +441,10 @@ const SideBar: React.FC = ({ dispatch(updateFlowData({ [data.id]: res.data })); // 更新 currentAppData 中的数据 dispatch(updateCurrentAppData({ ...findMenuItem(menuData[identity], children.key) })); + dispatch(updateEventNodeList({ + eventSendNodeList: [], + eventlisteneList: [] + })); // 同步更新到 canvasDataMap if (res.data.main?.components) { @@ -475,6 +490,7 @@ const SideBar: React.FC = ({ ); } else { + if (item.title.includes('隐藏事件')) return null; return ( = ({ }; }, []); + // 监听自定义事件以更新隐藏节点状态 + 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) => { // 只有当 node.dataRef.id 存在时才渲染操作按钮 - if (!node.dataRef?.id) { - return null; - } + if (!node.dataRef?.id) return null; const dropList = ( @@ -621,6 +661,44 @@ const SideBar: React.FC = ({ ); }; + 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 ( +
+ {isNodeHidden ? : } +
+ ); + }; + return (
= ({ } }} style={{ background: 'transparent' }} // 移除背景色 - renderExtra={selected?.parentKey === 'appList' ? renderNodeExtra : null} + renderExtra={selected?.parentKey === 'appList' ? renderNodeExtra : renderNodeExtraEye} > {renderMenuItems(filteredMenu[activeKey]?.children)} diff --git a/src/store/ideContainer.ts b/src/store/ideContainer.ts index 2981138..5c9e9e4 100644 --- a/src/store/ideContainer.ts +++ b/src/store/ideContainer.ts @@ -21,8 +21,8 @@ interface IDEContainerState { isRunning: boolean; logs: any[]; runId: string; - eventSendNodeList: string[], - eventlisteneList: string[] + eventSendNodeList: any[], // [{nodeID:topic}] + eventlisteneList: any[] // [{nodeID:topic}] }>; } diff --git a/src/utils/convertFlowData.ts b/src/utils/convertFlowData.ts index 25deb98..1ee4ab4 100644 --- a/src/utils/convertFlowData.ts +++ b/src/utils/convertFlowData.ts @@ -75,13 +75,35 @@ export const convertFlowData = (flowData: any, useDefault = true) => { const nodeConfig: any = entry[1]; // 更新应用中的事件节点列表 - if (nodeId.includes('EVENTLISTENE')) eventlisteneList.push(nodeId); - else if (nodeId.includes('EVENTSEND')) eventSendNodeList.push(nodeId); + if (nodeId.includes('EVENTLISTENE')) { + 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({ - eventSendNodeList, - eventlisteneList + eventSendNodeList: [...eventSendNodeList], + eventlisteneList: [...eventlisteneList] })); + else { + store.dispatch(updateEventNodeList({ + eventSendNodeList: [], + eventlisteneList: [] + })); + } // 确定节点类型 let nodeType = 'BASIC'; @@ -304,7 +326,11 @@ export const convertFlowData = (flowData: any, useDefault = true) => { target: target, sourceHandle: finalSourceHandle, targetHandle: finalTargetHandle, - type: 'custom' + type: 'custom', + lineType: 'api', + data: { + lineType: 'api' + } }); } } @@ -340,7 +366,11 @@ export const convertFlowData = (flowData: any, useDefault = true) => { target: targetNodeId, sourceHandle: sourceHandle, 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 信息 - if (node.type === 'SUB' && !node.customDef) { + if (node.type === 'SUB' && !node.data.component.customDef) { nodeConfig.component = { type: 'SUB', compId: node.data.compId,