完成节点遍历

This commit is contained in:
fengfeng 2026-02-11 13:34:21 +08:00
parent cf66dfec5a
commit 0c58ae4d3d

View File

@ -1,16 +1,96 @@
import { ReactNode, useContext } from 'react';
import { Handle, Position, NodeProps } from '@xyflow/react';
import { AppNode, SettingsContext } from '../types/graph';
import { Handle, Position, NodeProps, useReactFlow} from '@xyflow/react';
import { AppNode, AppEdge, SettingsContext, NodeData } from '../types/graph';
import './CustomNode.css';
import toast from 'react-hot-toast';
class ConfigResult {
constructor(
public success: boolean,
public config?: string,
public error?: string
) {}
}
function generateConfig(
data : NodeData,
getEdges : () => AppEdge[],
getNode : (id: string) => AppNode | undefined) : ConfigResult {
const getNearEdges = (node: AppNode) : AppEdge[] => {
return getEdges().filter(edge => edge.source === node.id || edge.target === node.id);
};
const getNextNode = (edge: AppEdge, node: AppNode) : AppNode | undefined => {
const nextNodeId = edge.source === node.id ? edge.target : edge.source;
return getNode(nextNodeId);
};
const node = getNode(data.id);
if(!node) {
return new ConfigResult(false, undefined, "节点未找到");
}
const belongsToEdge : Record<string, string | undefined> = {[node.id]: node.id};
const queue : AppNode[] = [];
const nearEdges = getNearEdges(node);
nearEdges.forEach(edge => {
const nextNode = getNextNode(edge, node);
if(nextNode) {
belongsToEdge[nextNode.id] = edge.id;
queue.push(nextNode);
}
});
while(queue.length > 0) {
const currentNode = queue.shift()!;
const fromEdgeId = belongsToEdge[currentNode.id];
if(!fromEdgeId) continue;
getNearEdges(currentNode).forEach(edge => {
const nextNode = getNextNode(edge, currentNode);
if(nextNode && !belongsToEdge[nextNode.id]) {
belongsToEdge[nextNode.id] = fromEdgeId;
queue.push(nextNode);
}
});
}
const groupedByEdge: Record<string, string[] | undefined> = {};
for (const nodeId in belongsToEdge) {
const edgeId = belongsToEdge[nodeId];
if(edgeId === nodeId) continue; // 跳过起始节点
if(!edgeId) continue;
if (!groupedByEdge[edgeId]) {
groupedByEdge[edgeId] = [];
}
groupedByEdge[edgeId].push(nodeId);
}
}
export default function CustomNode({
data,
selected
}: NodeProps<AppNode>): ReactNode {
const handleGenerate = (e: React.MouseEvent) => {
e.stopPropagation();
toast.success('保存成功!');
const { getNode, getEdges } = useReactFlow<AppNode, AppEdge>();
const handleGenerate = (node : NodeData) => {
const result = generateConfig(node, getEdges, getNode);
if(result.success && result.config) {
navigator.clipboard.writeText(result.config).then(() => {
toast.success("配置已复制到剪贴板");
}).catch(() => {
toast.error("复制失败,请手动复制");
});
} else {
toast.error("配置生成失败:" + (result.error || "未知错误"));
}
};
const settings = useContext(SettingsContext);
@ -38,13 +118,16 @@ export default function CustomNode({
{(!settings.ipv4Subnet && !settings.ipv6Subnet) && (
<div className="info-item">
<span className="label"></span>
<span className="label"></span>
</div>
)}
</div>
<div className="node-actions">
<button className="gen-btn" onClick={handleGenerate} onDoubleClick={e => e.stopPropagation()}></button>
<button className="gen-btn" onClick={e => {
e.stopPropagation();
handleGenerate(data);
}} onDoubleClick={e => e.stopPropagation()}></button>
</div>
{[Position.Top, Position.Bottom, Position.Right, Position.Left].map((position) => (