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={
}
>
{t("Label-Proxies")}
}
>
{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")}
)}
);
};