feat(flowEditor): 优化节点连接逻辑并添加数据类型验证

- 新增 getHandleType 函数,用于获取 handle 类型 (api 或 data)
- 新增 validateDataType函数,用于验证数据类型是否匹配
- 修改 onConnect 和 onReconnect 回调,增加类型验证逻辑
- 优化节点双击编辑逻辑,增加不可编辑节点类型
- 调整面板位置和样式,提升用户体验
- 新增 defaultNodeTypes 接口,统一节点数据结构
master
钟良源 5 months ago
parent 0d579c69e7
commit ff3090522f

@ -17,6 +17,7 @@ import {
Panel
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { Button } from '@arco-design/web-react';
import { nodeTypeMap, nodeTypes, registerNodeType } from './node';
import SideBar from './sideBar/sideBar';
import { convertFlowData } from '@/utils/convertFlowData';
@ -26,6 +27,7 @@ import CustomEdge from './components/customEdge';
import NodeContextMenu from './components/nodeContextMenu';
import EdgeContextMenu from './components/edgeContextMenu';
import NodeEditModal from './components/nodeEditModal';
import { defaultNodeTypes } from '@/pages/flowEditor/node/types/defaultType';
const edgeTypes: EdgeTypes = {
custom: CustomEdge
@ -59,11 +61,82 @@ const FlowEditor: React.FC = () => {
const [editingNode, setEditingNode] = useState<Node | null>(null);
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
// 获取handle类型 (api或data)
const getHandleType = (handleId: string, nodeParams: any) => {
// 检查是否为api类型的handle
const apiOuts = nodeParams.apiOuts || [];
const apiIns = nodeParams.apiIns || [];
if (apiOuts.some((api: any) => api.name === handleId) ||
apiIns.some((api: any) => api.name === handleId)) {
return 'api';
}
// 检查是否为data类型的handle
const dataOuts = nodeParams.dataOuts || [];
const dataIns = nodeParams.dataIns || [];
if (dataOuts.some((data: any) => data.name === handleId) ||
dataIns.some((data: any) => data.name === handleId)) {
return 'data';
}
// 默认为data类型
return 'data';
};
// 验证数据类型是否匹配
const validateDataType = (sourceNode: defaultNodeTypes, targetNode: defaultNodeTypes, sourceHandleId: string, targetHandleId: string) => {
const sourceParams = sourceNode.data?.parameters || {};
const targetParams = targetNode.data?.parameters || {};
// 获取源节点的输出参数
let sourceDataType = '';
const sourceApiOuts = sourceParams.apiOuts || [];
const sourceDataOuts = sourceParams.dataOuts || [];
// 查找源handle的数据类型
const sourceApi = sourceApiOuts.find((api: any) => api.name === sourceHandleId);
const sourceData = sourceDataOuts.find((data: any) => data.name === sourceHandleId);
if (sourceApi) {
sourceDataType = sourceApi.dataType || '';
}
else if (sourceData) {
sourceDataType = sourceData.dataType || '';
}
// 获取目标节点的输入参数
let targetDataType = '';
const targetApiIns = targetParams.apiIns || [];
const targetDataIns = targetParams.dataIns || [];
// 查找目标handle的数据类型
const targetApi = targetApiIns.find((api: any) => api.name === targetHandleId);
const targetData = targetDataIns.find((data: any) => data.name === targetHandleId);
if (targetApi) {
targetDataType = targetApi.dataType || '';
}
else if (targetData) {
targetDataType = targetData.dataType || '';
}
// 如果任一数据类型为空,则允许连接
if (!sourceDataType || !targetDataType) {
return true;
}
// 比较数据类型是否匹配
return sourceDataType === targetDataType;
};
const onNodesChange = useCallback(
(changes: any) => {
const newNodes = applyNodeChanges(changes, nodes);
setNodes(newNodes);
// 如果需要在节点变化时执行某些操作,可以在这里添加
onPaneClick();
},
[nodes]
);
@ -73,20 +146,84 @@ const FlowEditor: React.FC = () => {
const newEdges = applyEdgeChanges(changes, edges);
setEdges(newEdges);
// 如果需要在边变化时执行某些操作,可以在这里添加
onPaneClick();
},
[edges]
);
const onConnect = useCallback(
(params: any) => setEdges((edgesSnapshot) => addEdge({ ...params, type: 'custom' }, edgesSnapshot)),
[]
(params: any) => {
// 获取源节点和目标节点
const sourceNode = nodes.find(node => node.id === params.source);
const targetNode = nodes.find(node => node.id === params.target);
// 如果找不到节点,不创建连接
if (!sourceNode || !targetNode) {
return;
}
// 获取源节点和目标节点的参数信息
const sourceParams = sourceNode.data?.parameters || {};
const targetParams = targetNode.data?.parameters || {};
// 获取源handle和目标handle的类型 (api或data)
const sourceHandleType = getHandleType(params.sourceHandle, sourceParams);
const targetHandleType = getHandleType(params.targetHandle, targetParams);
// 验证连接类型是否匹配 (api只能连api, data只能连data)
if (sourceHandleType !== targetHandleType) {
console.warn('连接类型不匹配: ', sourceHandleType, targetHandleType);
return;
}
// 验证数据类型是否匹配
if (!validateDataType(sourceNode, targetNode, params.sourceHandle, params.targetHandle)) {
console.warn('数据类型不匹配');
return;
}
// 如果验证通过,创建连接
setEdges((edgesSnapshot) => addEdge({ ...params, type: 'custom' }, edgesSnapshot));
},
[nodes]
);
// 边重新连接处理
const onReconnect = useCallback(
(oldEdge: Edge, newConnection: any) =>
setEdges((els) => reconnectEdge(oldEdge, newConnection, els)),
[]
(oldEdge: Edge, newConnection: any) => {
// 获取源节点和目标节点
const sourceNode = nodes.find(node => node.id === newConnection.source);
const targetNode = nodes.find(node => node.id === newConnection.target);
// 如果找不到节点,不创建连接
if (!sourceNode || !targetNode) {
return;
}
// 获取源节点和目标节点的参数信息
const sourceParams = sourceNode.data?.parameters || {};
const targetParams = targetNode.data?.parameters || {};
// 获取源handle和目标handle的类型 (api或data)
const sourceHandleType = getHandleType(newConnection.sourceHandle, sourceParams);
const targetHandleType = getHandleType(newConnection.targetHandle, targetParams);
// 验证连接类型是否匹配 (api只能连api, data只能连data)
if (sourceHandleType !== targetHandleType) {
console.warn('连接类型不匹配: ', sourceHandleType, targetHandleType);
return;
}
// 验证数据类型是否匹配
if (!validateDataType(sourceNode, targetNode, newConnection.sourceHandle, newConnection.targetHandle)) {
console.warn('数据类型不匹配');
return;
}
// 如果验证通过,重新连接
setEdges((els) => reconnectEdge(oldEdge, newConnection, els));
},
[nodes]
);
const onDragOver = useCallback((event: React.DragEvent) => {
@ -161,8 +298,8 @@ const FlowEditor: React.FC = () => {
// 节点双击处理
const onNodeDoubleClick = useCallback(
(event: React.MouseEvent, node: Node) => {
// 与门和或门不可编辑
if (['AND', 'OR'].includes(node.type)) return;
// 不可编辑的类型
if (['AND', 'OR', 'JSON2STR', 'STR2JSON'].includes(node.type)) return;
setEditingNode(node);
setIsEditModalOpen(true);
},
@ -312,9 +449,9 @@ const FlowEditor: React.FC = () => {
>
<Background />
<Controls />
<Panel position="top-right">
<Panel position="top-left">
<div></div>
<button onClick={saveFlowDataToServer}></button>
<Button onClick={saveFlowDataToServer} type="primary"></Button>
</Panel>
</ReactFlow>

@ -2,21 +2,10 @@ import React from 'react';
import styles from '@/pages/flowEditor/node/style/base.module.less';
import NodeContent from '@/pages/flowEditor/components/nodeContent';
import { useStore } from '@xyflow/react';
import { defaultNodeTypes } from '@/pages/flowEditor/node/types/defaultType';
interface BasicNodeData {
title?: string;
parameters?: {
dataIns?: any[];
dataOuts?: any[];
apiIns?: any[];
apiOuts?: any[];
};
showFooter?: boolean;
[key: string]: any;
}
const BasicNode = ({ data, id }: { data: BasicNodeData; id: string }) => {
const BasicNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => {
const title = data.title || '基础节点';
// 获取节点选中状态 - 适配React Flow v12 API

@ -2,21 +2,10 @@ import React from 'react';
import styles from '@/pages/flowEditor/node/style/base.module.less';
import NodeContent from '@/pages/flowEditor/components/nodeContent';
import { useStore } from '@xyflow/react';
import { defaultNodeTypes } from '@/pages/flowEditor/node/types/defaultType';
interface EndNodeData {
title?: string;
parameters?: {
dataIns?: any[];
dataOuts?: any[];
apiIns?: any[];
apiOuts?: any[];
};
showFooter?: boolean;
[key: string]: any;
}
const EndNode = ({ data, id }: { data: EndNodeData; id: string }) => {
const EndNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => {
const title = data.title || '结束';
// 获取节点选中状态 - 适配React Flow v12 API

@ -2,21 +2,10 @@ import React from 'react';
import styles from '@/pages/flowEditor/node/style/base.module.less';
import NodeContent from '@/pages/flowEditor/components/nodeContent';
import { useStore } from '@xyflow/react';
import { defaultNodeTypes } from '@/pages/flowEditor/node/types/defaultType';
interface StartNodeData {
title?: string;
parameters?: {
dataIns?: any[];
dataOuts?: any[];
apiIns?: any[];
apiOuts?: any[];
};
showFooter?: boolean;
[key: string]: any;
}
const StartNode = ({ data, id }: { data: StartNodeData; id: string }) => {
const StartNode = ({ data, id }: { data: defaultNodeTypes; id: string }) => {
const title = data.title || '开始';
// 获取节点选中状态 - 适配React Flow v12 API

@ -0,0 +1,12 @@
export interface defaultNodeTypes {
title?: string;
parameters?: {
dataIns?: any[];
dataOuts?: any[];
apiIns?: any[];
apiOuts?: any[];
};
showFooter?: boolean;
[key: string]: any;
}
Loading…
Cancel
Save