feat(flow): 支持添加应用组件和复合组件

- 新增对项目组件数据的获取与处理逻辑- 在 AddNodeMenu 中按分组展示本地节点和项目组件
- 修改 onAddNode 接口以支持传递完整的节点信息- 实现根据节点类型动态注册 BasicNode 或 LocalNode
-优化节点句柄 ID 生成逻辑,兼容 name 和 id 字段- 调整组件标签 key 值生成规则,优先使用 id 避免重复
- 移除调试用 console 日志输出
- 更新分组名称映射,将 application 分组更名为基础组件
master
钟良源 4 months ago
parent fc7f4853a0
commit 3c57f650fb

@ -1,11 +1,12 @@
import React from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { Menu, Tabs } from '@arco-design/web-react';
import { localNodeData } from '@/pages/flowEditor/sideBar/config/localNodeData';
import { useSelector } from 'react-redux';
const TabPane = Tabs.TabPane;
interface AddNodeMenuProps {
onAddNode: (nodeType: string) => void;
onAddNode: (nodeType: string, node: any) => void;
position?: { x: number; y: number }; // 用于画布上下文菜单
edgeId?: string; // 用于边上下文菜单
}
@ -13,8 +14,14 @@ interface AddNodeMenuProps {
const AddNodeMenu: React.FC<AddNodeMenuProps> = ({
onAddNode
}) => {
const { projectComponentData, info } = useSelector((state: any) => state.ideContainer);
const [groupedNodes, setGroupedNodes] = useState<Record<string, any[]>>({});
const [activeTab, setActiveTab] = useState('common');
// 按分组组织节点数据
const groupedNodes = localNodeData.reduce((acc, node) => {
const formattedNodes = useCallback(() => {
// 先复制本地节点数据
const initialGroupedNodes = localNodeData.reduce((acc, node) => {
if (!acc[node.nodeGroup]) {
acc[node.nodeGroup] = [];
}
@ -22,15 +29,71 @@ const AddNodeMenu: React.FC<AddNodeMenuProps> = ({
return acc;
}, {} as Record<string, typeof localNodeData>);
const handleAddNode = (nodeType: string) => {
onAddNode(nodeType);
const projectComponent = projectComponentData[info.id];
if (projectComponent) {
const projectComp = projectComponent.projectCompDto;
const projectFlow = projectComponent.projectFlowDto;
const projectCompList = projectComp && Object.values(projectComp).flat(2) || [];
const projectFlowList = projectFlow && Object.values(projectFlow).flat(2) || [];
initialGroupedNodes['application'] = projectCompList.map((v: any) => {
return {
...v,
nodeName: v.name,
nodeType: 'BASIC',
nodeGroup: 'application',
data: {
parameters: {
apiIns: v.def?.apis || [],
apiOuts: Array.isArray(v.def?.apiOut) ? v.def?.apiOut : (v.def?.apiOut ? [v.def?.apiOut] : []),
dataIns: v.def?.dataIns || [],
dataOuts: v.def?.dataOuts || []
}
}
};
});
// groupedNodes['composite'] = projectFlowList.map((v: any) => {
// return {
// ...v,
// nodeName: v.name,
// nodeType: 'BASE',
// nodeGroup: 'application',
// data: {
// parameters: {
// apiIns: v.def.apis,
// apiOuts: v.def.apiOut,
// dataIns: v.def.dataIns,
// dataOuts: v.def.dataOuts
// }
// }
// };
// });
console.log(projectCompList, projectFlowList, initialGroupedNodes);
}
// 更新状态以触发重新渲染
setGroupedNodes(initialGroupedNodes);
}, [projectComponentData, info.id]);
const handleAddNode = (nodeType: string, node: any) => {
onAddNode(nodeType, node);
};
// 分组名称映射
const groupNames: Record<string, string> = {
'common': '系统组件',
'application': '应用组件',
'composite': '复合组件'
'application': '基础组件',
'composite': '复合组件',
'common': '系统组件'
};
useEffect(() => {
formattedNodes();
}, [formattedNodes]);
const handleTabChange = (key: string) => {
setActiveTab(key);
};
return (
@ -44,9 +107,11 @@ const AddNodeMenu: React.FC<AddNodeMenuProps> = ({
flexDirection: 'column'
}}
>
<Tabs defaultActiveTab="common" style={{ flex: '0 0 auto' }}>
<Tabs defaultActiveTab="common" style={{ flex: '0 0 auto' }} onChange={handleTabChange}>
{Object.entries(groupedNodes).map(([group, nodes]) => (
<TabPane key={group} title={groupNames[group] || group}>
{/* 只有在当前 tab 激活时才渲染内容 */}
{activeTab === group && (
<div style={{ maxHeight: '300px', overflowY: 'auto' }}>
<Menu
style={{
@ -58,10 +123,10 @@ const AddNodeMenu: React.FC<AddNodeMenuProps> = ({
mode="vertical"
hasCollapseButton={false}
>
{nodes.map((node) => (
{nodes && nodes.map((node) => (
<Menu.Item
key={node.nodeType}
onClick={() => handleAddNode(node.nodeType)}
key={node.nodeType + (node.id || '')}
onClick={() => handleAddNode(node.nodeType, node)}
style={{
padding: '0 16px',
height: 36,
@ -73,6 +138,7 @@ const AddNodeMenu: React.FC<AddNodeMenuProps> = ({
))}
</Menu>
</div>
)}
</TabPane>
))}
</Tabs>

@ -127,7 +127,7 @@ const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[]
key={`api-output-handle-${index}`}
type="source"
position={Position.Right}
id={apiOuts[index].name || `output-${index}`}
id={apiOuts[index].name || apiOuts[index].id || `output-${index}`}
style={{
...handleStyles.mainSource,
top: `${35 + index * 20}px`
@ -139,7 +139,7 @@ const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[]
key={`api-input-handle-${index}`}
type="target"
position={Position.Left}
id={apiIns[index].name || `input-${index}`}
id={apiIns[index].name || apiIns[index].id || `input-${index}`}
style={{
...handleStyles.mainTarget,
top: `${35 + index * 20}px`
@ -153,7 +153,7 @@ const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[]
key={`data-input-handle-${index}`}
type="target"
position={Position.Left}
id={dataIns[index].name || `input-${index}`}
id={dataIns[index].name || dataIns[index].id || `input-${index}`}
style={{
...handleStyles.data,
top: `${70 + (apiIns.length + index) * 20}px`
@ -167,7 +167,7 @@ const renderRegularNodeHandles = (dataIns: any[], dataOuts: any[], apiIns: any[]
key={`data-output-handle-${index}`}
type="source"
position={Position.Right}
id={dataOuts[index].name || `output-${index}`}
id={dataOuts[index].name || dataOuts[index].id || `output-${index}`}
style={{
...handleStyles.data,
top: `${70 + (apiOuts.length + index) * 20}px`
@ -209,6 +209,7 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
const dataOuts = data.parameters?.dataOuts || [];
const showFooter = data?.component?.customDef || false;
const footerData = (showFooter && data.component) || {};
// console.log(apiIns, apiOuts, dataIns, dataOuts);
// 判断节点类型
const isStartNode = data.type === 'start';
@ -223,7 +224,7 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
{apiIns.length > 0 && (
<div className={styles['node-inputs']}>
{apiIns.map((input, index) => (
<div key={`input-${index}`} className={styles['node-input-label']}>
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
{input.desc}
</div>
))}
@ -233,7 +234,7 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
{apiOuts.length > 0 && (
<div className={styles['node-outputs-api']}>
{apiOuts.map((output, index) => (
<div key={`output-${index}`} className={styles['node-input-label']}>
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
{output.desc}
</div>
))}
@ -256,7 +257,7 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
{dataIns.length > 0 && !isStartNode && (
<div className={styles['node-inputs']}>
{dataIns.map((input, index) => (
<div key={`input-${index}`} className={styles['node-input-label']}>
<div key={input.id || `input-${index}`} className={styles['node-input-label']}>
{input.id || `输入${index + 1}`}
</div>
))}
@ -266,7 +267,7 @@ const NodeContent = ({ data }: { data: NodeContentData }) => {
{dataOuts.length > 0 && !isEndNode && (
<div className={styles['node-outputs']}>
{dataOuts.map((output, index) => (
<div key={`output-${index}`} className={styles['node-input-label']}>
<div key={output.id || `output-${index}`} className={styles['node-input-label']}>
{output.id || `输出${index + 1}`}
</div>
))}

@ -5,7 +5,7 @@ import AddNodeMenu from './addNodeMenu';
const SubMenu = Menu.SubMenu;
interface PaneContextMenuProps {
onAddNode: (nodeType: string, position: { x: number; y: number }) => void;
onAddNode: (nodeType: string, position: { x: number; y: number }, node: any) => void;
position: { x: number; y: number };
}
@ -14,8 +14,8 @@ const PaneContextMenu: React.FC<PaneContextMenuProps> = ({
position
}) => {
// 包装onAddNode函数以适配AddNodeMenu组件的接口
const handleAddNode = (nodeType: string) => {
onAddNode(nodeType, position);
const handleAddNode = (nodeType: string, node: any) => {
onAddNode(nodeType, position, node);
};
return (
@ -35,7 +35,9 @@ const PaneContextMenu: React.FC<PaneContextMenuProps> = ({
popupStyle: { height: 'auto', maxHeight: 'none' }
}}
>
<AddNodeMenu onAddNode={handleAddNode} />
<AddNodeMenu onAddNode={(nodeType, node) => {
handleAddNode(nodeType, node);
}} />
</SubMenu>
</Menu>
);

@ -39,6 +39,7 @@ import { localNodeData } from '@/pages/flowEditor/sideBar/config/localNodeData';
import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines';
import { setMainFlow } from '@/api/appRes';
import { Message } from '@arco-design/web-react';
import BasicNode from '@/components/FlowEditor/node/basicNode/BasicNode';
const edgeTypes: EdgeTypes = {
custom: CustomEdge
@ -97,8 +98,8 @@ const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ ini
const apiOuts = nodeParams.apiOuts || [];
const apiIns = nodeParams.apiIns || [];
if (apiOuts.some((api: any) => api.name === handleId) ||
apiIns.some((api: any) => api.name === handleId)) {
if (apiOuts.some((api: any) => (api.name || api.id) === handleId) ||
apiIns.some((api: any) => (api.name || api.id) === handleId)) {
return 'api';
}
@ -106,8 +107,8 @@ const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ ini
const dataOuts = nodeParams.dataOuts || [];
const dataIns = nodeParams.dataIns || [];
if (dataOuts.some((data: any) => data.name === handleId) ||
dataIns.some((data: any) => data.name === handleId)) {
if (dataOuts.some((data: any) => (data.name || data.id) === handleId) ||
dataIns.some((data: any) => (data.name || data.id) === handleId)) {
return 'data';
}
@ -200,6 +201,7 @@ const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ ini
// 获取源节点和目标节点的参数信息
const sourceParams = sourceNode.data?.parameters || {};
const targetParams = targetNode.data?.parameters || {};
console.log(sourceParams, targetParams, params);
// 获取源handle和目标handle的类型 (api或data)
const sourceHandleType = getHandleType(params.sourceHandle, sourceParams);
@ -526,11 +528,11 @@ const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ ini
}, []);
// 在边上添加节点的具体实现
const addNodeOnEdge = useCallback((nodeType: string) => {
const addNodeOnEdge = useCallback((nodeType: string, node: any) => {
if (!edgeForNodeAdd || !reactFlowInstance) return;
// 查找节点定义
const nodeDefinition = localNodeData.find(n => n.nodeType === nodeType);
const nodeDefinition = localNodeData.find(n => n.nodeType === nodeType) || node;
if (!nodeDefinition) return;
// 获取源节点和目标节点
@ -559,7 +561,7 @@ const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ ini
// 将未定义的节点动态追加进nodeTypes
const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
if (!nodeMap.includes(nodeType)) registerNodeType(nodeType, LocalNode, nodeDefinition.nodeName);
if (!nodeMap.includes(nodeType)) registerNodeType(nodeType, nodeType === 'BASIC' ? BasicNode : LocalNode, nodeDefinition.nodeName);
// 添加新节点
setNodes((nds) => [...nds, newNode]);
@ -590,13 +592,13 @@ const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ ini
}, [edgeForNodeAdd, nodes, reactFlowInstance]);
// 在画布上添加节点
const addNodeOnPane = useCallback((nodeType: string, position: { x: number; y: number }) => {
const addNodeOnPane = useCallback((nodeType: string, position: { x: number; y: number }, node?: any) => {
setMenu(null);
if (!reactFlowInstance) return;
// 查找节点定义
const nodeDefinition = localNodeData.find(n => n.nodeType === nodeType);
const nodeDefinition = localNodeData.find(n => n.nodeType === nodeType) || node;
if (!nodeDefinition) return;
// 创建新节点
@ -614,20 +616,20 @@ const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ ini
// 将未定义的节点动态追加进nodeTypes
const nodeMap = Array.from(Object.values(nodeTypeMap).map(key => key));
// 目前默认添加的都是系统组件/本地组件
if (!nodeMap.includes(nodeType)) registerNodeType(nodeType, LocalNode, nodeDefinition.nodeName);
if (!nodeMap.includes(nodeType)) registerNodeType(nodeType, nodeType === 'BASIC' ? BasicNode : LocalNode, nodeDefinition.nodeName);
setNodes((nds) => [...nds, newNode]);
}, [reactFlowInstance]);
// 处理添加节点的统一方法
const handleAddNode = useCallback((nodeType: string) => {
const handleAddNode = useCallback((nodeType: string, node) => {
// 如果是通过边添加节点
if (edgeForNodeAdd) {
addNodeOnEdge(nodeType);
addNodeOnEdge(nodeType, node);
}
// 如果是通过画布添加节点
else if (positionForNodeAdd) {
addNodeOnPane(nodeType, positionForNodeAdd);
addNodeOnPane(nodeType, positionForNodeAdd, node);
}
// 清除状态
@ -767,8 +769,8 @@ const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ ini
>
<PaneContextMenu
position={menu.position!}
onAddNode={(nodeType: string, position: { x: number, y: number }) => {
addNodeOnPane(nodeType, position);
onAddNode={(nodeType: string, position: { x: number, y: number }, node) => {
addNodeOnPane(nodeType, position, node);
setMenu(null); // 关闭上下文菜单
}}
/>
@ -797,8 +799,8 @@ const FlowEditor: React.FC<{ initialData?: any, useDefault?: boolean }> = ({ ini
}}
>
<AddNodeMenu
onAddNode={(nodeType) => {
handleAddNode(nodeType);
onAddNode={(nodeType, node) => {
handleAddNode(nodeType, node);
// 关闭菜单
setEdgeForNodeAdd(null);
setPositionForNodeAdd(null);

@ -3,7 +3,6 @@ import styles from './style/compInfo.module.less';
import { Space, Divider, Table, TableColumnProps } from '@arco-design/web-react';
const CompInfo = ({ currentCompInfo }) => {
console.log('传入的组件信息:', currentCompInfo);
const columns: TableColumnProps[] = [
{

Loading…
Cancel
Save