import { useTranslation } from "react-i18next"; import { Box, Typography, Stack, Chip, Button, alpha, useTheme, Select, MenuItem, FormControl, InputLabel, SelectChangeEvent, Tooltip, } from "@mui/material"; import { useEffect, useState } from "react"; import { RouterOutlined, SignalWifi4Bar as SignalStrong, SignalWifi3Bar as SignalGood, SignalWifi2Bar as SignalMedium, SignalWifi1Bar as SignalWeak, SignalWifi0Bar as SignalNone, WifiOff as SignalError, ChevronRight, } from "@mui/icons-material"; import { useNavigate } from "react-router-dom"; import { useCurrentProxy } from "@/hooks/use-current-proxy"; import { EnhancedCard } from "@/components/home/enhanced-card"; import { getProxies, updateProxy } from "@/services/api"; import delayManager from "@/services/delay"; // 本地存储的键名 const STORAGE_KEY_GROUP = "clash-verge-selected-proxy-group"; const STORAGE_KEY_PROXY = "clash-verge-selected-proxy"; // 代理节点信息接口 interface ProxyOption { name: string; } // 将delayManager返回的颜色格式转换为MUI Chip组件需要的格式 function convertDelayColor( delayValue: number, ): | "default" | "success" | "warning" | "error" | "primary" | "secondary" | "info" | undefined { const colorStr = delayManager.formatDelayColor(delayValue); if (!colorStr) return "default"; // 从"error.main"这样的格式转为"error" const mainColor = colorStr.split(".")[0]; switch (mainColor) { case "success": return "success"; case "warning": return "warning"; case "error": return "error"; case "primary": return "primary"; default: return "default"; } } // 根据延迟值获取合适的WiFi信号图标 function getSignalIcon(delay: number): { icon: JSX.Element; text: string; color: string; } { if (delay < 0) return { icon: , text: "未测试", color: "text.secondary", }; if (delay >= 10000) return { icon: , text: "超时", color: "error.main", }; if (delay >= 500) return { icon: , text: "延迟较高", color: "error.main", }; if (delay >= 300) return { icon: , text: "延迟中等", color: "warning.main", }; if (delay >= 200) return { icon: , text: "延迟良好", color: "info.main", }; return { icon: , text: "延迟极佳", color: "success.main", }; } export const CurrentProxyCard = () => { const { t } = useTranslation(); const { currentProxy, primaryGroupName, mode, refreshProxy } = useCurrentProxy(); const navigate = useNavigate(); const theme = useTheme(); // 判断模式 const isGlobalMode = mode === "global"; const isDirectMode = mode === "direct"; // 添加直连模式判断 // 从本地存储获取初始值,如果是特殊模式或没有存储值则使用默认值 const getSavedGroup = () => { // 全局模式使用 GLOBAL 组 if (isGlobalMode) { return "GLOBAL"; } // 直连模式使用 DIRECT if (isDirectMode) { return "DIRECT"; } const savedGroup = localStorage.getItem(STORAGE_KEY_GROUP); return savedGroup || primaryGroupName || "GLOBAL"; }; // 状态管理 const [groups, setGroups] = useState< { name: string; now: string; all: string[] }[] >([]); const [selectedGroup, setSelectedGroup] = useState(getSavedGroup()); const [proxyOptions, setProxyOptions] = useState([]); const [selectedProxy, setSelectedProxy] = useState(""); const [displayProxy, setDisplayProxy] = useState(null); const [records, setRecords] = useState>({}); const [globalProxy, setGlobalProxy] = useState(""); // 存储全局代理 const [directProxy, setDirectProxy] = useState(null); // 存储直连代理信息 // 保存选择的代理组到本地存储 useEffect(() => { // 只有在普通模式下才保存到本地存储 if (selectedGroup && !isGlobalMode && !isDirectMode) { localStorage.setItem(STORAGE_KEY_GROUP, selectedGroup); } }, [selectedGroup, isGlobalMode, isDirectMode]); // 保存选择的代理节点到本地存储 useEffect(() => { // 只有在普通模式下才保存到本地存储 if (selectedProxy && !isGlobalMode && !isDirectMode) { localStorage.setItem(STORAGE_KEY_PROXY, selectedProxy); } }, [selectedProxy, isGlobalMode, isDirectMode]); // 当模式变化时更新选择的组 useEffect(() => { if (isGlobalMode) { setSelectedGroup("GLOBAL"); } else if (isDirectMode) { setSelectedGroup("DIRECT"); } else if (primaryGroupName) { const savedGroup = localStorage.getItem(STORAGE_KEY_GROUP); setSelectedGroup(savedGroup || primaryGroupName); } }, [isGlobalMode, isDirectMode, primaryGroupName]); // 获取所有代理组和代理信息 useEffect(() => { const fetchProxies = async () => { try { const data = await getProxies(); // 保存所有节点记录信息,用于显示详细节点信息 setRecords(data.records); // 检查并存储全局代理信息 if (data.global) { setGlobalProxy(data.global.now || ""); } // 查找并存储直连代理信息 if (data.records && data.records["DIRECT"]) { setDirectProxy(data.records["DIRECT"]); } const filteredGroups = data.groups .filter((g) => g.name !== "DIRECT" && g.name !== "REJECT") .map((g) => ({ name: g.name, now: g.now || "", all: g.all.map((p) => p.name), })); setGroups(filteredGroups); // 直连模式处理 if (isDirectMode) { // 直连模式下使用 DIRECT 节点 setSelectedGroup("DIRECT"); setSelectedProxy("DIRECT"); if (data.records && data.records["DIRECT"]) { setDisplayProxy(data.records["DIRECT"]); } // 设置仅包含 DIRECT 节点的选项 setProxyOptions([{ name: "DIRECT" }]); return; } // 全局模式处理 if (isGlobalMode) { // 在全局模式下,使用 GLOBAL 组和 data.global.now 作为选中节点 if (data.global) { const globalNow = data.global.now || ""; setSelectedGroup("GLOBAL"); setSelectedProxy(globalNow); if (globalNow && data.records[globalNow]) { setDisplayProxy(data.records[globalNow]); } // 设置全局组的代理选项 const options = data.global.all.map((proxy) => ({ name: proxy.name, })); setProxyOptions(options); } return; } // 以下是普通模式的处理逻辑 let targetGroup = primaryGroupName; // 非特殊模式下,尝试从本地存储获取上次选择的代理组 const savedGroup = localStorage.getItem(STORAGE_KEY_GROUP); targetGroup = savedGroup || primaryGroupName; // 如果目标组在列表中,则选择它 if (targetGroup && filteredGroups.some((g) => g.name === targetGroup)) { setSelectedGroup(targetGroup); // 设置该组下的代理选项 const currentGroup = filteredGroups.find( (g) => g.name === targetGroup, ); if (currentGroup) { // 创建代理选项 const options = currentGroup.all.map((proxyName) => { return { name: proxyName }; }); setProxyOptions(options); let targetProxy = currentGroup.now; const savedProxy = localStorage.getItem(STORAGE_KEY_PROXY); // 如果有保存的代理节点且该节点在当前组中,则选择它 if (savedProxy && currentGroup.all.includes(savedProxy)) { targetProxy = savedProxy; } setSelectedProxy(targetProxy); if (targetProxy && data.records[targetProxy]) { setDisplayProxy(data.records[targetProxy]); } } } else if (filteredGroups.length > 0) { // 否则选择第一个组 setSelectedGroup(filteredGroups[0].name); // 创建代理选项 const options = filteredGroups[0].all.map((proxyName) => { return { name: proxyName }; }); setProxyOptions(options); setSelectedProxy(filteredGroups[0].now); // 更新显示的代理节点信息 if (filteredGroups[0].now && data.records[filteredGroups[0].now]) { setDisplayProxy(data.records[filteredGroups[0].now]); } } } catch (error) { console.error("获取代理信息失败", error); } }; fetchProxies(); }, [primaryGroupName, isGlobalMode, isDirectMode]); // 当选择的组发生变化时更新代理选项 useEffect(() => { // 如果是特殊模式,已在 fetchProxies 中处理 if (isGlobalMode || isDirectMode) return; const group = groups.find((g) => g.name === selectedGroup); if (group && records) { // 创建代理选项 const options = group.all.map((proxyName) => { return { name: proxyName }; }); setProxyOptions(options); let targetProxy = group.now; const savedProxy = localStorage.getItem(STORAGE_KEY_PROXY); // 如果保存的代理节点在当前组中,则选择它 if (savedProxy && group.all.includes(savedProxy)) { targetProxy = savedProxy; } setSelectedProxy(targetProxy); if (targetProxy && records[targetProxy]) { setDisplayProxy(records[targetProxy]); } } }, [selectedGroup, groups, records, isGlobalMode, isDirectMode]); // 刷新代理信息 const refreshProxyData = async () => { try { const data = await getProxies(); // 检查并更新全局代理信息 if (isGlobalMode && data.global) { const globalNow = data.global.now || ""; setSelectedProxy(globalNow); if (globalNow && data.records[globalNow]) { setDisplayProxy(data.records[globalNow]); } // 更新全局组的代理选项 const options = data.global.all.map((proxy) => ({ name: proxy.name, })); setProxyOptions(options); } // 更新直连代理信息 if (isDirectMode && data.records["DIRECT"]) { setDirectProxy(data.records["DIRECT"]); setDisplayProxy(data.records["DIRECT"]); } } catch (error) { console.error("刷新代理信息失败", error); } }; // 每隔一段时间刷新特殊模式下的代理信息 useEffect(() => { if (!isGlobalMode && !isDirectMode) return; const refreshInterval = setInterval(refreshProxyData, 3000); return () => clearInterval(refreshInterval); }, [isGlobalMode, isDirectMode]); // 处理代理组变更 const handleGroupChange = (event: SelectChangeEvent) => { // 特殊模式下不允许切换组 if (isGlobalMode || isDirectMode) return; const newGroup = event.target.value; setSelectedGroup(newGroup); }; // 处理代理节点变更 const handleProxyChange = async (event: SelectChangeEvent) => { // 直连模式下不允许切换节点 if (isDirectMode) return; const newProxy = event.target.value; setSelectedProxy(newProxy); // 更新显示的代理节点信息 if (records[newProxy]) { setDisplayProxy(records[newProxy]); } try { // 更新代理设置 await updateProxy(selectedGroup, newProxy); setTimeout(() => { refreshProxy(); if (isGlobalMode || isDirectMode) { refreshProxyData(); // 特殊模式下额外刷新数据 } }, 300); } catch (error) { console.error("更新代理失败", error); } }; // 导航到代理页面 const goToProxies = () => { // 修正路由路径,根据_routers.tsx配置,代理页面的路径是"/" navigate("/"); }; // 获取要显示的代理节点 const proxyToDisplay = displayProxy || currentProxy; // 获取当前节点的延迟 const currentDelay = proxyToDisplay ? delayManager.getDelayFix(proxyToDisplay, selectedGroup) : -1; // 获取信号图标 const signalInfo = getSignalIcon(currentDelay); // 自定义渲染选择框中的值 const renderProxyValue = (selected: string) => { if (!selected || !records[selected]) return selected; const delayValue = delayManager.getDelayFix( records[selected], selectedGroup, ); return ( {selected} ); }; return ( {proxyToDisplay ? signalInfo.icon : } } iconColor={proxyToDisplay ? "primary" : undefined} action={ } > {proxyToDisplay ? ( {/* 代理节点信息显示 */} {proxyToDisplay.name} {proxyToDisplay.type} {isGlobalMode && ( )} {isDirectMode && ( )} {/* 节点特性 */} {proxyToDisplay.udp && ( )} {proxyToDisplay.tfo && ( )} {proxyToDisplay.xudp && ( )} {proxyToDisplay.mptcp && ( )} {proxyToDisplay.smux && ( )} {/* 显示延迟 */} {proxyToDisplay && !isDirectMode && ( )} {/* 代理组选择器 */} {t("Group")} {/* 代理节点选择器 */} {t("Proxy")} ) : ( {t("No active proxy node")} )} ); };