feat(flowEditor): 增强节点内容显示与事件分组关联

- 引入 useMemo 优化事件分组计算性能- 新增事件分组逻辑,支持按事件 ID 分组并分配颜色
- 添加事件与数据/API 项的关联匹配功能
- 实现节点输入输出标签的颜色标识,提升可视化效果
- 增加对空值(**empty**)的校验处理,避免无效数据显示- 重构原有 footer 格式化逻辑,改为动态事件分组展示方式
master
钟良源 3 months ago
parent 4b6e4b8556
commit d080efff5b

@ -1,4 +1,4 @@
import React from 'react'; 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';
@ -13,6 +13,7 @@ interface NodeContentData {
}; };
showFooter?: boolean; showFooter?: boolean;
type?: string; type?: string;
component?: any;
[key: string]: any; [key: string]: any;
} }
@ -178,29 +179,112 @@ const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[]
); );
}; };
const formatFooter = (data: any) => { // 检查是否为有效的API非empty
try { const isValidApi = (api: any) => {
switch (data?.type) { return api && !api.topic?.includes('**empty**') && !api.name?.includes('**empty**');
case 'WAIT': };
const { duration } = deserializeValue(data.customDef);
const hours = Math.floor(duration / 3600); // 检查是否为有效的数据非empty
const minutes = Math.floor((duration % 3600) / 60); const isValidData = (data: any) => {
const seconds = Math.floor(duration % 60); return data && !data.topic?.includes('**empty**') && !data.name?.includes('**empty**');
return `${hours}小时${minutes}分钟${seconds}`; };
case 'CYCLE':
const { intervalSeconds } = deserializeValue(data.customDef); // 为每组关联的数据定义颜色
return cronstrue.toString(intervalSeconds, { locale: 'zh_CN' }); const getGroupColor = (groupId: number) => {
case 'EVENTSEND': const colors = [
case 'EVENTLISTENE': '#1890ff', // 蓝色
const { name, topic } = isJSON(data.customDef) ? JSON.parse(data.customDef) : data.customDef; '#52c41a', // 绿色
if (topic.includes('**empty**')) return ''; '#faad14', // 黄色
return `事件: ${name}`; '#f5222d', // 红色
default: '#722ed1', // 紫色
return ''; '#13c2c2' // 青色
];
return colors[groupId % colors.length];
};
// 从customDef中提取事件分组信息
const useEventGroups = (component: any) => {
return useMemo(() => {
if (!component?.customDef) return {};
try {
const customDef = isJSON(component.customDef)
? JSON.parse(component.customDef)
: component.customDef;
const groups: Record<string, { event: any; color: string; index: number }> = {};
let groupIndex = 0;
// 处理eventListenes对应apiOuts
if (Array.isArray(customDef.eventListenes)) {
customDef.eventListenes
.filter(event => event && !event.topic?.includes('**empty**'))
.forEach(event => {
groups[`eventListenes-${event.eventId}`] = {
event,
color: getGroupColor(groupIndex++),
index: groupIndex - 1
};
});
}
// 处理eventSends对应apiIns
if (Array.isArray(customDef.eventSends)) {
customDef.eventSends
.filter(event => event && !event.topic?.includes('**empty**'))
.forEach(event => {
groups[`eventSends-${event.eventId}`] = {
event,
color: getGroupColor(groupIndex++),
index: groupIndex - 1
};
});
}
console.log('groups:', groups);
return groups;
} catch (e) {
console.error('解析customDef时出错:', e);
return {};
}
}, [component?.customDef]);
};
// 根据数据ID查找关联的事件分组
const findRelatedEventGroup = (dataItem: any, eventGroups: Record<string, any>, eventType: string) => {
if (!dataItem || !eventGroups) return null;
// 查找与数据项关联的事件
for (const key in eventGroups) {
const group = eventGroups[key];
const eventDataIns = group.event?.dataIns || [];
const eventDataOuts = group.event?.dataOuts || [];
// 检查数据项是否属于当前事件的数据输入或输出
if (eventType === 'eventListenes' && eventDataIns.some((input: any) => input.id === dataItem.id)) {
return group;
}
if (eventType === 'eventSends' && eventDataOuts.some((output: any) => output.id === dataItem.id)) {
return group;
} }
} catch (e) {
console.log(e);
} }
return null;
};
// 根据topic查找API关联的事件分组
const findApiGroupByTopic = (apiItem: any, eventGroups: Record<string, any>) => {
if (!apiItem || !apiItem.topic || !eventGroups) return null;
// 查找与API项topic关联的事件
for (const key in eventGroups) {
const group = eventGroups[key];
if (group.event.topic === apiItem.topic) {
return group;
}
}
return null;
}; };
const NodeContent = ({ data }: { data: NodeContentData }) => { const NodeContent = ({ data }: { data: NodeContentData }) => {
@ -208,9 +292,8 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
const apiOuts = data.parameters?.apiOuts || []; const apiOuts = data.parameters?.apiOuts || [];
const dataIns = data.parameters?.dataIns || []; const dataIns = data.parameters?.dataIns || [];
const dataOuts = data.parameters?.dataOuts || []; const dataOuts = data.parameters?.dataOuts || [];
const showFooter = formatFooter(data.component) || false; // 获取事件分组信息
const footerData = (showFooter && data.component) || {}; const eventGroups = useEventGroups(data.component);
// console.log(apiIns, apiOuts, dataIns, dataOuts);
// 判断节点类型 // 判断节点类型
const isStartNode = data.type === 'start'; const isStartNode = data.type === 'start';
@ -224,21 +307,41 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
<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) => {
<div key={input.id || `input-${index}`} className={styles['node-input-label']}> // 查找关联的事件分组
{!input.topic.includes('**empty') ? input.name : ''} const group = findApiGroupByTopic(input, eventGroups);
</div> return (
))} <div
key={input.id || `input-${index}`}
className={styles['node-input-label']}
style={{
color: group ? group.color : '#000'
}}
>
{isValidApi(input) ? input.name : ''}
</div>
);
})}
</div> </div>
)} )}
{apiOuts.length > 0 && ( {apiOuts.length > 0 && (
<div className={styles['node-outputs-api']}> <div className={styles['node-outputs-api']}>
{apiOuts.map((output, index) => ( {apiOuts.map((output, index) => {
<div key={output.id || `output-${index}`} className={styles['node-input-label']}> // 查找关联的事件分组
{!output.topic.includes('**empty') ? output.name : ''} const group = findApiGroupByTopic(output, eventGroups);
</div> return (
))} <div
key={output.id || `output-${index}`}
className={styles['node-input-label']}
style={{
color: group ? group.color : '#000'
}}
>
{isValidApi(output) ? output.name : ''}
</div>
);
})}
</div> </div>
)} )}
</div> </div>
@ -253,21 +356,42 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
<div className={styles['node-content']}> <div className={styles['node-content']}>
{dataIns.length > 0 && !isStartNode && ( {dataIns.length > 0 && !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']}> // 查找关联的事件分组
{input.id || `输入${index + 1}`} const group = findRelatedEventGroup(input, eventGroups, 'eventSends');
</div> return (
))} <div
key={input.id || `input-${index}`}
className={styles['node-input-label']}
style={{
color: group ? group.color : '#000'
}}
>
{isValidData(input) ? (input.name || input.id || `输入${index + 1}`) : ''}
</div>
);
})}
</div> </div>
)} )}
{dataOuts.length > 0 && !isEndNode && ( {dataOuts.length > 0 && !isEndNode && (
<div className={styles['node-outputs']}> <div className={styles['node-outputs']}>
{dataOuts.map((output, index) => ( {dataOuts.map((output, index) => {
<div key={output.id || `output-${index}`} className={styles['node-input-label']}> // 查找关联的事件分组
{output.id || `输出${index + 1}`} const group = findRelatedEventGroup(output, eventGroups, 'eventListenes');
</div>
))} return (
<div
key={output.id || `output-${index}`}
className={styles['node-input-label']}
style={{
color: group ? group.color : '#000'
}}
>
{isValidData(output) ? (output.name || output.id || `输出${index + 1}`) : ''}
</div>
);
})}
</div> </div>
)} )}
</div> </div>

Loading…
Cancel
Save