|
|
|
|
@ -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>
|
|
|
|
|
|
|
|
|
|
|