feat(flow):优化循环节点配置与展示逻辑
- 调整 LOOP_START 节点的 apiIns 和 apiOuts 参数结构 - 新增 buildNodeId 工具函数统一管理节点句柄 ID 生成逻辑- 在 convertFlowData 中增强 nodeId 获取逻辑,兼容 id与 name 字段 - LoopNode 组件新增状态管理,支持动态解析条件表达式并生成 apiOuts - 引入 NodeContentLoop 组件专门负责循环节点的内容渲染和句柄绘制 - 更新 useFlowCallbacks 中 LOOP_START 节点默认参数配置 - 新增 nodeContentLoop 组件实现节点内容与句柄的模块化渲染master
parent
26fe3794fa
commit
bba0197215
@ -0,0 +1,211 @@
|
||||
import React from 'react';
|
||||
import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
|
||||
import { Handle, Position, useStore } from '@xyflow/react';
|
||||
import { deserializeValue } 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)'
|
||||
}
|
||||
};
|
||||
|
||||
// 渲染LOOP节点的句柄
|
||||
const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[], apiOuts: any[]) => {
|
||||
return (
|
||||
<>
|
||||
{apiOuts.map((_, index) => (
|
||||
<Handle
|
||||
key={`api-output-handle-${index}`}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id={apiOuts[index].name || apiOuts[index].id || `output-${index}`}
|
||||
style={{
|
||||
...handleStyles.mainSource,
|
||||
top: `${35 + index * 20}px`
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{apiIns.map((_, index) => (
|
||||
<Handle
|
||||
key={`api-input-handle-${index}`}
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
id={apiIns[index].name || apiIns[index].id || `input-${index}`}
|
||||
style={{
|
||||
...handleStyles.mainTarget,
|
||||
top: `${35 + index * 20}px`
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* 输入参数连接端点 */}
|
||||
{dataIns.map((_, index) => (
|
||||
<Handle
|
||||
key={`data-input-handle-${index}`}
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
id={dataIns[index].name || dataIns[index].id || `input-${index}`}
|
||||
style={{
|
||||
...handleStyles.data,
|
||||
top: `${65 + (apiOuts.length + index) * 20}px`
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* 输出参数连接端点 */}
|
||||
{dataOuts.map((_, index) => (
|
||||
<Handle
|
||||
key={`data-output-handle-${index}`}
|
||||
type="source"
|
||||
position={Position.Right}
|
||||
id={dataOuts[index].name || dataOuts[index].id || `output-${index}`}
|
||||
style={{
|
||||
...handleStyles.data,
|
||||
top: `${65 + (apiOuts.length + index) * 20}px`
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const formatFooter = (data: any) => {
|
||||
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 { name } = data.customDef;
|
||||
return `事件: ${name}`;
|
||||
default:
|
||||
return '这个类型还没开发';
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
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';
|
||||
|
||||
return (
|
||||
<>
|
||||
{/*content栏-api部分*/}
|
||||
<div className={styles['node-api-box']}>
|
||||
<div className={styles['node-content-api']}>
|
||||
{apiIns.length > 0 && (
|
||||
<div className={styles['node-inputs']}>
|
||||
{apiIns.map((input, index) => (
|
||||
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
|
||||
{data.type !== 'LOOP_START' ? input.desc || input.id : ''}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{apiOuts.length > 0 && (
|
||||
<div className={styles['node-outputs-api']}>
|
||||
{apiOuts.map((output, index) => (
|
||||
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
|
||||
{data.type !== 'LOOP_START' ? output.desc || output.id : ''}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{(dataIns.length > 0 || dataOuts.length > 0) && (
|
||||
<>
|
||||
{/*分割*/}
|
||||
<div className={styles['node-split-line']}></div>
|
||||
|
||||
{/*content栏-data部分*/}
|
||||
<div className={styles['node-data-box']}>
|
||||
<div className={styles['node-content']}>
|
||||
{dataIns.length > 0 && !isStartNode && (
|
||||
<div className={styles['node-inputs']}>
|
||||
{dataIns.map((input, index) => (
|
||||
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
|
||||
{input.id || `输入${index + 1}`}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{dataOuts.length > 0 && !isEndNode && (
|
||||
<div className={styles['node-outputs']}>
|
||||
{dataOuts.map((output, index) => (
|
||||
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
|
||||
{output.id || `输出${index + 1}`}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/*footer栏*/}
|
||||
{/*{showFooter && (*/}
|
||||
{/* <div className={styles['node-footer']}>*/}
|
||||
{/* {formatFooter(footerData)}*/}
|
||||
{/* </div>*/}
|
||||
{/*)}*/}
|
||||
|
||||
{renderRegularNodeHandles(dataIns, dataOuts, apiIns, apiOuts)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default NodeContent;
|
||||
Loading…
Reference in New Issue