diff --git a/src/components/FlowEditor/node/appNode/AppNode.tsx b/src/components/FlowEditor/node/appNode/AppNode.tsx
new file mode 100644
index 0000000..fcc67b4
--- /dev/null
+++ b/src/components/FlowEditor/node/appNode/AppNode.tsx
@@ -0,0 +1,33 @@
+import React, { useMemo } from 'react';
+import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
+import NodeContentApp from '@/pages/flowEditor/components/nodeContentApp';
+import { useStore } from '@xyflow/react';
+import { defaultNodeTypes } from '@/components/FlowEditor/node/types/defaultType';
+
+
+const AppNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => {
+ const title = data.title || '基础节点';
+
+ // 生成随机背景色,使用useMemo确保颜色只在节点首次创建时生成一次
+ const backgroundColor = useMemo(() => {
+ const colors = ['#e59428', '#4a90e2', '#7b68ee', '#50c878', '#ff6347', '#9370db', '#00bfff', '#ff8c00'];
+ return colors[Math.floor(Math.random() * colors.length)];
+ }, []);
+
+ // 获取节点选中状态 - 适配React Flow v12 API
+ const isSelected = useStore((state) =>
+ state.nodeLookup.get(id)?.selected || false
+ );
+
+ return (
+
+ );
+};
+
+export default AppNode;
\ No newline at end of file
diff --git a/src/pages/flowEditor/components/nodeContentApp.tsx b/src/pages/flowEditor/components/nodeContentApp.tsx
new file mode 100644
index 0000000..46d83a4
--- /dev/null
+++ b/src/pages/flowEditor/components/nodeContentApp.tsx
@@ -0,0 +1,293 @@
+import React from 'react';
+import styles from '@/components/FlowEditor/node/style/baseOther.module.less';
+import { Handle, Position, useStore } from '@xyflow/react';
+import { deserializeValue, 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.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) => {
+ 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, topic } = isJSON(data.customDef) ? JSON.parse(data.customDef) : data.customDef;
+ if (topic.includes('**empty**')) return '';
+ 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 = formatFooter(data.component) || false;
+ const footerData = (showFooter && data.component) || {};
+ // console.log(apiIns, apiOuts, dataIns, dataOuts);
+
+ // 判断节点类型
+ 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.topic.includes('**empty') ? input.name : ''}
+
+ ))}
+
+ )}
+
+ {apiOuts.length > 0 && (
+
+ {apiOuts.map((output, index) => (
+
+ {!output.topic.includes('**empty') ? output.name : ''}
+
+ ))}
+
+ )}
+
+
+ {(dataIns.length > 0 || dataOuts.length > 0) && (
+ <>
+ {/*分割*/}
+
+
+ {/*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