diff --git a/src/pages/flowEditor/components/nodeContentOther.tsx b/src/pages/flowEditor/components/nodeContentOther.tsx new file mode 100644 index 0000000..1aea57f --- /dev/null +++ b/src/pages/flowEditor/components/nodeContentOther.tsx @@ -0,0 +1,288 @@ +import React from 'react'; +import styles from '@/pages/flowEditor/node/style/baseOther.module.less'; +import { Handle, Position, useStore } from '@xyflow/react'; +import { isJSON } from '@/utils/common'; +import cronstrue from 'cronstrue/i18n'; + +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.map((_, index) => ( + + ))} + + ); + }; + + const renderEndNodeHandles = () => { + if (!isEndNode) return null; + + return ( + <> + {apiIns.map((_, index) => ( + + ))} + {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) => { + console.log('formatFooter:', data); + switch (data.type) { + case 'WAIT': + const { duration } = 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 } = data.customDef; + return cronstrue.toString(intervalSeconds, { locale: 'zh_CN' }); + case 'EVENTSEND': + case 'EVENTLISTENE': + const { name } = data.customDef; + return `事件: ${name}`; + default: + return '这个类型还没开发'; + } +}; + +const NodeContent = ({ data }: { data: NodeContentData }) => { + const apiIns = data.parameters?.apiIns || []; + const apiOuts = data.parameters?.apiOuts || []; + const dataIns = data.parameters?.dataIns || []; + const dataOuts = data.parameters?.dataOuts || []; + const showFooter = data?.component?.customDef || 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) => ( +
+ {input.desc} +
+ ))} +
+ )} + + {apiOuts.length > 0 && ( +
+ {apiOuts.map((output, index) => ( +
+ {output.desc} +
+ ))} +
+ )} +
+
+ +
+ 中间分割 +
+ + {/*content栏-data部分*/} +
+
+ {dataIns.length > 0 && !isStartNode && ( +
+ {dataIns.map((input, index) => ( +
+ {input.id || `输入${index + 1}`} +
+ ))} +
+ )} + + {dataOuts.length > 0 && !isEndNode && ( +
+ {dataOuts.map((output, index) => ( +
+ {output.id || `输出${index + 1}`} +
+ ))} +
+ )} +
+
+ + {/*footer栏*/} + {showFooter && ( +
+ {formatFooter(footerData)} +
+ )} + + {/* 根据节点类型渲染不同的句柄 */} + {isSpecialNode + ? renderSpecialNodeHandles(isStartNode, isEndNode, dataIns, dataOuts, apiIns, apiOuts) + : renderRegularNodeHandles(dataIns, dataOuts, apiIns, apiOuts)} + + ); +}; + +export default NodeContent; \ No newline at end of file diff --git a/src/pages/flowEditor/node/basicNode/BasicNode.tsx b/src/pages/flowEditor/node/basicNode/BasicNode.tsx index c302fe0..8976c54 100644 --- a/src/pages/flowEditor/node/basicNode/BasicNode.tsx +++ b/src/pages/flowEditor/node/basicNode/BasicNode.tsx @@ -1,6 +1,8 @@ import React from 'react'; import styles from '@/pages/flowEditor/node/style/base.module.less'; +// import styles from '@/pages/flowEditor/node/style/baseOther.module.less'; import NodeContent from '@/pages/flowEditor/components/nodeContent'; +import NodeContentOther from '@/pages/flowEditor/components/nodeContentOther'; import { useStore } from '@xyflow/react'; import { defaultNodeTypes } from '@/pages/flowEditor/node/types/defaultType'; @@ -9,7 +11,7 @@ const BasicNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => { const title = data.title || '基础节点'; // 获取节点选中状态 - 适配React Flow v12 API - const isSelected = useStore((state) => + const isSelected = useStore((state) => state.nodeLookup.get(id)?.selected || false ); @@ -20,6 +22,7 @@ const BasicNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => { + {/**/} ); }; diff --git a/src/pages/flowEditor/node/style/baseOther.module.less b/src/pages/flowEditor/node/style/baseOther.module.less new file mode 100644 index 0000000..d5b0b04 --- /dev/null +++ b/src/pages/flowEditor/node/style/baseOther.module.less @@ -0,0 +1,93 @@ +.node-container { + color: white; + min-width: 150px; + font-size: 14px; + border: 2px solid transparent; + + &.selected { + border-color: #1890ff; + box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2); + } + + .node-header { + border-bottom: 1px solid rgba(255, 255, 255, 0.2); + color: #000000; + text-align: center; + } + + .node-api-box, + .node-data-box { + //box-shadow: 3px 3px 5px #cccccc; + } + + .node-data-box { + margin-top: -1px; + } + + .node-split-line { + z-index: 230; + position: relative; + width: 90%; + background-color: #ffffff; + margin: -1px auto; // -1是为了遮住上面盒子的部分边框 + color: #000000; + text-align: center; + height: 20px; + line-height: 20px; + font-size: 12px; + border: 1px solid #cccccc; + border-bottom-color: #ffffff; + border-top-color: #ffffff; + } + + .node-content, + .node-content-api { + display: flex; + background-color: #ffffff; + color: #000000; + padding: 0 5px; + border: 1px solid #cccccc; + border-radius: 3px; + + + .node-inputs, + .node-outputs, + .node-outputs-api { + flex: 1; + + .node-input-label { + font-size: 12px; + padding: 1px 0; + height: 20px; + line-height: 20px; + } + } + + .node-inputs { + margin-bottom: 5px; + margin-right: 30px; + } + + .node-outputs, + .node-outputs-api { + text-align: right; + } + } + + .node-content-api { + min-height: 30px; + } + + .node-content { + min-height: 10px; + } + + .node-footer { + background-color: #ffffff; + color: #000000; + padding: 5px 20px; + border-top: 1px solid rgba(204, 204, 204, 0.18); + min-height: 20px; + text-align: center; + } +} \ No newline at end of file