diff --git a/src/components/FlowEditor/node/localNode/LocalNode.tsx b/src/components/FlowEditor/node/localNode/LocalNode.tsx index 1356e72..f50b77d 100644 --- a/src/components/FlowEditor/node/localNode/LocalNode.tsx +++ b/src/components/FlowEditor/node/localNode/LocalNode.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useStore } from '@xyflow/react'; import styles from '@/components/FlowEditor/node/style/baseOther.module.less'; import DynamicIcon from '@/components/DynamicIcon'; -import NodeContentOther from '@/pages/flowEditor/components/nodeContentOther'; +import NodeContentLocal from '@/pages/flowEditor/components/nodeContentLocal'; import NodeStatusIndicator, { NodeStatus } from '@/components/FlowEditor/NodeStatusIndicator'; import { useStore as useFlowStore } from '@xyflow/react'; @@ -83,7 +83,7 @@ const LocalNode = ({ data, id }: { data: any; id: string }) => { {title} - + ); }; diff --git a/src/pages/flowEditor/components/nodeContentLocal.tsx b/src/pages/flowEditor/components/nodeContentLocal.tsx new file mode 100644 index 0000000..2495080 --- /dev/null +++ b/src/pages/flowEditor/components/nodeContentLocal.tsx @@ -0,0 +1,318 @@ +import React from 'react'; +import styles from '@/components/FlowEditor/node/style/baseOther.module.less'; +import { Handle, Position, useStore } from '@xyflow/react'; +import { deserializeValue, formatDataType, isJSON } from '@/utils/common'; +import cronstrue from 'cronstrue/i18n'; +import { useSelector } from 'react-redux'; + +interface NodeContentData { + parameters?: { + dataIns?: any[]; + dataOuts?: any[]; + apiIns?: any[]; + apiOuts?: any[]; + }; + showFooter?: boolean; + type?: string; + + [key: string]: any; +} + +// 定义通用的句柄样式 +const handleStyles = { + mainSource: { + background: '#2290f6', + width: '8px', + height: '8px', + border: '2px solid #fff', + boxShadow: '0 0 4px rgba(0,0,0,0.2)' + }, + mainTarget: { + background: '#2290f6', + width: '8px', + height: '8px', + border: '2px solid #fff', + boxShadow: '0 0 4px rgba(0,0,0,0.2)' + }, + data: { + background: '#555', + width: '6px', + height: '6px', + border: '1px solid #fff', + boxShadow: '0 0 2px rgba(0,0,0,0.2)' + } +}; + +// 渲染特殊节点(开始/结束节点)的句柄 +const renderSpecialNodeHandles = (isStartNode: boolean, isEndNode: boolean, dataIns: any[], dataOuts: any[], apiIns: any[], apiOuts: any[]) => { + const renderStartNodeHandles = () => { + if (!isStartNode) return null; + + return ( + <> + {apiOuts.map((_, index) => ( + + ))} + {dataOuts.length > 0 && dataOuts.map((_, index) => ( + + ))} + + ); + }; + + const renderEndNodeHandles = () => { + if (!isEndNode) return null; + + return ( + <> + {apiIns.map((_, index) => ( + + ))} + {dataIns.length > 0 && dataIns.map((_, index) => ( + + ))} + + ); + }; + + return ( + <> + {renderStartNodeHandles()} + {renderEndNodeHandles()} + + ); +}; + +// 渲染普通节点的句柄 +const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[], apiOuts: any[]) => { + return ( + <> + {apiOuts.map((_, index) => ( + + ))} + {apiIns.map((_, index) => ( + + ))} + + {/* 输入参数连接端点 */} + {dataIns.map((_, index) => ( + + ))} + + {/* 输出参数连接端点 */} + {dataOuts.map((_, index) => ( + + ))} + + ); +}; + +const formatFooter = (data: any, eventListOld = []) => { + try { + switch (data?.type) { + case 'WAIT': + const { duration } = deserializeValue(data.customDef); + const hours = Math.floor(duration / 3600); + const minutes = Math.floor((duration % 3600) / 60); + const seconds = Math.floor(duration % 60); + return `${hours}小时${minutes}分钟${seconds}秒`; + case 'CYCLE': + const { intervalSeconds } = deserializeValue(data.customDef); + return cronstrue.toString(intervalSeconds, { locale: 'zh_CN' }); + case 'EVENTSEND': + case 'EVENTLISTENE': + const parsedData = isJSON(data.customDef) ? JSON.parse(data.customDef) : null; + // 数据是JSON字符串,标识是接口回来的,数据是普通对象标识是当前操作 + if (parsedData) { + const { eventId, topic, name } = parsedData; + if (topic.includes('**empty**')) return ''; + const currentEvent = eventListOld.length > 0 ? eventListOld.find(item => item.eventId === eventId) : { name: '无' }; + return `事件: ${currentEvent.name}`; + } + else { + const { name, topic } = data.customDef; + if (topic.includes('**empty**')) return ''; + return `事件: ${name}`; + } + case 'BASIC': + return data.compIdentifier ? `当前实例:${data.compIdentifier}` : ''; + default: + return ''; + } + } catch (e) { + console.log(e); + } +}; + +const formatTitle = (text) => { + return text === 'start' || text === 'end' ? '' : text; +}; + +const NodeContentLocal = ({ data }: { data: NodeContentData }) => { + const { eventListOld } = useSelector((state) => state.ideContainer); + const apiIns = data.parameters?.apiIns || []; + const apiOuts = data.parameters?.apiOuts || []; + const dataIns = data.parameters?.dataIns || []; + const dataOuts = data.parameters?.dataOuts || []; + const showFooter = formatFooter(data.component) || false; + const footerData = (showFooter && data.component) || {}; + + // 判断节点类型 + const isStartNode = data.type === 'start'; + const isEndNode = data.type === 'end'; + const isSpecialNode = isStartNode || isEndNode; + + return ( + <> + {/*content栏-api部分*/} +
+
+ {apiIns.length > 0 && ( +
+ {apiIns.map((input, index) => ( +
+ {formatTitle(input.desc || input.id || input.name)} +
+ ))} +
+ )} + + {apiOuts.length > 0 && ( +
+ {apiOuts.map((output, index) => ( +
+ {output.desc} +
+ ))} +
+ )} +
+
+ {(dataIns.length > 0 || dataOuts.length > 0) && ( + <> + {/*分割*/} +
+ +
+ + {/*content栏-data部分*/} +
+
+
+ {dataIns.map((input, index) => ( +
+ {input.id || `输入${index + 1}`} {formatDataType(input.dataType)} + +
+ ))} +
+ + {dataOuts.length > 0 && !isEndNode && ( +
+ {dataOuts.map((output, index) => ( +
+ {formatDataType(output.dataType)} {output.id || `输出${index + 1}`} + +
+ ))} +
+ )} +
+
+ + )} + + {/*footer栏*/} + {showFooter && ( +
+ {formatFooter(footerData, eventListOld)} +
+ )} + + {/* 根据节点类型渲染不同的句柄 */} + {isSpecialNode + ? renderSpecialNodeHandles(isStartNode, isEndNode, dataIns, dataOuts, apiIns, apiOuts) + : renderRegularNodeHandles(dataIns, dataOuts, apiIns, apiOuts)} + + ); +}; + +export default NodeContentLocal; \ No newline at end of file