|
|
|
@ -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) => (
|
|
|
|
|
|
|
|
<Handle
|
|
|
|
|
|
|
|
key={`start-output-handle-${index}`}
|
|
|
|
|
|
|
|
type="source"
|
|
|
|
|
|
|
|
position={Position.Right}
|
|
|
|
|
|
|
|
id={apiOuts[index].name || `start-output-${index}`}
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
...handleStyles.mainSource,
|
|
|
|
|
|
|
|
top: `${35 + index * 20}px`
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
{dataOuts.length > 0 && dataOuts.map((_, index) => (
|
|
|
|
|
|
|
|
<Handle
|
|
|
|
|
|
|
|
key={`output-handle-${index}`}
|
|
|
|
|
|
|
|
type="source"
|
|
|
|
|
|
|
|
position={Position.Right}
|
|
|
|
|
|
|
|
id={dataOuts[index].name || `output-${index}`}
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
...handleStyles.data,
|
|
|
|
|
|
|
|
top: `${70 + apiIns.length * 20 + index * 20}px`
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
</>
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const renderEndNodeHandles = () => {
|
|
|
|
|
|
|
|
if (!isEndNode) return null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
|
|
<>
|
|
|
|
|
|
|
|
{apiIns.map((_, index) => (
|
|
|
|
|
|
|
|
<Handle
|
|
|
|
|
|
|
|
key={`end-input-handle-${index}`}
|
|
|
|
|
|
|
|
type="target"
|
|
|
|
|
|
|
|
position={Position.Left}
|
|
|
|
|
|
|
|
id={apiIns[index].name || `end-input-${index}`}
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
...handleStyles.mainTarget,
|
|
|
|
|
|
|
|
top: `${35 + index * 20}px`
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
{dataIns.length > 0 && dataIns.map((_, index) => (
|
|
|
|
|
|
|
|
<Handle
|
|
|
|
|
|
|
|
key={`input-handle-${index}`}
|
|
|
|
|
|
|
|
type="target"
|
|
|
|
|
|
|
|
position={Position.Left}
|
|
|
|
|
|
|
|
id={dataIns[index].name || `input-${index}`}
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
...handleStyles.data,
|
|
|
|
|
|
|
|
top: `${70 + apiIns.length * 20 + index * 20}px`
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
</>
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
|
|
<>
|
|
|
|
|
|
|
|
{renderStartNodeHandles()}
|
|
|
|
|
|
|
|
{renderEndNodeHandles()}
|
|
|
|
|
|
|
|
</>
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染普通节点的句柄
|
|
|
|
|
|
|
|
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: `${37 + index * 22}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: `${37 + index * 22}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 + (apiIns.length + index) * 22}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 + (apiIns.length + index) * 22}px`
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
</>
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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部分*/}
|
|
|
|
|
|
|
|
<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']}>
|
|
|
|
|
|
|
|
{formatTitle(input.desc || input.id || input.name)}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{apiOuts.length > 0 && (
|
|
|
|
|
|
|
|
<div className={styles['node-outputs']}>
|
|
|
|
|
|
|
|
{apiOuts.map((output, index) => (
|
|
|
|
|
|
|
|
<div key={output.id || `output-${index}`} className={styles['node-output-label']}>
|
|
|
|
|
|
|
|
{output.desc}
|
|
|
|
|
|
|
|
</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']}>
|
|
|
|
|
|
|
|
<div className={styles['node-inputs']}>
|
|
|
|
|
|
|
|
{dataIns.map((input, index) => (
|
|
|
|
|
|
|
|
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
className={styles['node-data-type']}
|
|
|
|
|
|
|
|
>{input.id || `输入${index + 1}`} {formatDataType(input.dataType)}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{dataOuts.length > 0 && !isEndNode && (
|
|
|
|
|
|
|
|
<div className={styles['node-outputs']}>
|
|
|
|
|
|
|
|
{dataOuts.map((output, index) => (
|
|
|
|
|
|
|
|
<div key={output.id || `output-${index}`} className={styles['node-output-label']}>
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
className={styles['node-data-type']}
|
|
|
|
|
|
|
|
>{formatDataType(output.dataType)} {output.id || `输出${index + 1}`}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/*footer栏*/}
|
|
|
|
|
|
|
|
{showFooter && (
|
|
|
|
|
|
|
|
<div className={styles['node-footer']}>
|
|
|
|
|
|
|
|
{formatFooter(footerData, eventListOld)}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 根据节点类型渲染不同的句柄 */}
|
|
|
|
|
|
|
|
{isSpecialNode
|
|
|
|
|
|
|
|
? renderSpecialNodeHandles(isStartNode, isEndNode, dataIns, dataOuts, apiIns, apiOuts)
|
|
|
|
|
|
|
|
: renderRegularNodeHandles(dataIns, dataOuts, apiIns, apiOuts)}
|
|
|
|
|
|
|
|
</>
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export default NodeContentLocal;
|