import { useTranslation } from "react-i18next";
import {
Box,
Typography,
Chip,
Button,
alpha,
useTheme,
Select,
MenuItem,
FormControl,
InputLabel,
SelectChangeEvent,
Tooltip,
IconButton,
} from "@mui/material";
import { useEffect, useState, useMemo, useCallback } from "react";
import {
SignalWifi4Bar as SignalStrong,
SignalWifi3Bar as SignalGood,
SignalWifi2Bar as SignalMedium,
SignalWifi1Bar as SignalWeak,
SignalWifi0Bar as SignalNone,
WifiOff as SignalError,
ChevronRight,
SortRounded,
AccessTimeRounded,
SortByAlphaRounded,
} from "@mui/icons-material";
import { useNavigate } from "react-router-dom";
import { EnhancedCard } from "@/components/home/enhanced-card";
import delayManager from "@/services/delay";
import { useAppData } from "@/providers/app-data-provider";
import { useProxySelection } from "@/hooks/use-proxy-selection";
// 本地存储的键名
const STORAGE_KEY_GROUP = "clash-verge-selected-proxy-group";
const STORAGE_KEY_PROXY = "clash-verge-selected-proxy";
const STORAGE_KEY_SORT_TYPE = "clash-verge-proxy-sort-type";
// 代理节点信息接口
interface ProxyOption {
name: string;
}
// 排序类型: 默认 | 按延迟 | 按字母
type ProxySortType = 0 | 1 | 2;
function convertDelayColor(delayValue: number) {
const colorStr = delayManager.formatDelayColor(delayValue);
if (!colorStr) return "default";
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";
}
}
function getSignalIcon(delay: number) {
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" };
}
// 简单的防抖函数
function debounce(fn: Function, ms = 100) {
let timeoutId: ReturnType;
return function (this: any, ...args: any[]) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), ms);
};
}
export const CurrentProxyCard = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const theme = useTheme();
const { proxies, clashConfig, refreshProxy } = useAppData();
// 统一代理选择器
const { handleSelectChange } = useProxySelection({
onSuccess: () => {
refreshProxy();
},
onError: (error) => {
console.error("代理切换失败", error);
refreshProxy();
},
});
// 判断模式
const mode = clashConfig?.mode?.toLowerCase() || "rule";
const isGlobalMode = mode === "global";
const isDirectMode = mode === "direct";
// 添加排序类型状态
const [sortType, setSortType] = useState(() => {
const savedSortType = localStorage.getItem(STORAGE_KEY_SORT_TYPE);
return savedSortType ? (Number(savedSortType) as ProxySortType) : 0;
});
// 定义状态类型
type ProxyState = {
proxyData: {
groups: { name: string; now: string; all: string[] }[];
records: Record;
};
selection: {
group: string;
proxy: string;
};
displayProxy: any;
};
const [state, setState] = useState({
proxyData: {
groups: [],
records: {},
},
selection: {
group: "",
proxy: "",
},
displayProxy: null,
});
// 初始化选择的组
useEffect(() => {
if (!proxies) return;
const getPrimaryGroupName = () => {
if (!proxies?.groups?.length) return "";
const primaryKeywords = [
"auto",
"select",
"proxy",
"节点选择",
"自动选择",
];
const primaryGroup =
proxies.groups.find((group: { name: string }) =>
primaryKeywords.some((keyword) =>
group.name.toLowerCase().includes(keyword.toLowerCase()),
),
) ||
proxies.groups.filter((g: { name: string }) => g.name !== "GLOBAL")[0];
return primaryGroup?.name || "";
};
const primaryGroupName = getPrimaryGroupName();
// 根据模式确定初始组
if (isGlobalMode) {
setState((prev) => ({
...prev,
selection: {
...prev.selection,
group: "GLOBAL",
},
}));
} else if (isDirectMode) {
setState((prev) => ({
...prev,
selection: {
...prev.selection,
group: "DIRECT",
},
}));
} else {
const savedGroup = localStorage.getItem(STORAGE_KEY_GROUP);
setState((prev) => ({
...prev,
selection: {
...prev.selection,
group: savedGroup || primaryGroupName || "",
},
}));
}
}, [isGlobalMode, isDirectMode, proxies]);
// 监听代理数据变化,更新状态
useEffect(() => {
if (!proxies) return;
setState((prev) => {
// 只保留 Selector 类型的组用于选择
const filteredGroups = proxies.groups
.filter((g: { name: string; type?: string }) => g.type === "Selector")
.map(
(g: { name: string; now: string; all: Array<{ name: string }> }) => ({
name: g.name,
now: g.now || "",
all: g.all.map((p: { name: string }) => p.name),
}),
);
let newProxy = "";
let newDisplayProxy = null;
let newGroup = prev.selection.group;
// 根据模式确定新代理
if (isDirectMode) {
newGroup = "DIRECT";
newProxy = "DIRECT";
newDisplayProxy = proxies.records?.DIRECT || { name: "DIRECT" }; // 确保非空
} else if (isGlobalMode && proxies.global) {
newGroup = "GLOBAL";
newProxy = proxies.global.now || "";
newDisplayProxy = proxies.records?.[newProxy] || null;
} else {
const currentGroup = filteredGroups.find(
(g: { name: string }) => g.name === prev.selection.group,
);
// 如果当前组不存在或为空,自动选择第一个 selector 类型的组
if (!currentGroup && filteredGroups.length > 0) {
const selectorGroup = filteredGroups[0];
if (selectorGroup) {
newGroup = selectorGroup.name;
newProxy = selectorGroup.now || selectorGroup.all[0] || "";
newDisplayProxy = proxies.records?.[newProxy] || null;
if (!isGlobalMode && !isDirectMode) {
localStorage.setItem(STORAGE_KEY_GROUP, newGroup);
if (newProxy) {
localStorage.setItem(STORAGE_KEY_PROXY, newProxy);
}
}
}
} else if (currentGroup) {
newProxy = currentGroup.now || currentGroup.all[0] || "";
newDisplayProxy = proxies.records?.[newProxy] || null;
}
}
// 返回新状态
return {
proxyData: {
groups: filteredGroups,
records: proxies.records || {},
},
selection: {
group: newGroup,
proxy: newProxy,
},
displayProxy: newDisplayProxy,
};
});
}, [proxies, isGlobalMode, isDirectMode]);
// 使用防抖包装状态更新
const debouncedSetState = useCallback(
debounce((updateFn: (prev: ProxyState) => ProxyState) => {
setState(updateFn);
}, 300),
[],
);
// 处理代理组变更
const handleGroupChange = useCallback(
(event: SelectChangeEvent) => {
if (isGlobalMode || isDirectMode) return;
const newGroup = event.target.value;
localStorage.setItem(STORAGE_KEY_GROUP, newGroup);
setState((prev) => {
const group = prev.proxyData.groups.find(
(g: { name: string }) => g.name === newGroup,
);
if (group) {
return {
...prev,
selection: {
group: newGroup,
proxy: group.now,
},
displayProxy: prev.proxyData.records[group.now] || null,
};
}
return {
...prev,
selection: {
...prev.selection,
group: newGroup,
},
};
});
},
[isGlobalMode, isDirectMode],
);
// 处理代理节点变更
const handleProxyChange = useCallback(
(event: SelectChangeEvent) => {
if (isDirectMode) return;
const newProxy = event.target.value;
const currentGroup = state.selection.group;
const previousProxy = state.selection.proxy;
debouncedSetState((prev: ProxyState) => ({
...prev,
selection: {
...prev.selection,
proxy: newProxy,
},
displayProxy: prev.proxyData.records[newProxy] || null,
}));
if (!isGlobalMode && !isDirectMode) {
localStorage.setItem(STORAGE_KEY_PROXY, newProxy);
}
const skipConfigSave = isGlobalMode || isDirectMode;
handleSelectChange(currentGroup, previousProxy, skipConfigSave)(event);
},
[
isDirectMode,
isGlobalMode,
state.selection,
debouncedSetState,
handleSelectChange,
],
);
// 导航到代理页面
const goToProxies = useCallback(() => {
navigate("/");
}, [navigate]);
// 获取要显示的代理节点
const currentProxy = useMemo(() => {
return state.displayProxy;
}, [state.displayProxy]);
// 获取当前节点的延迟(增加非空校验)
const currentDelay =
currentProxy && state.selection.group
? delayManager.getDelayFix(currentProxy, state.selection.group)
: -1;
// 信号图标(增加非空校验)
const signalInfo =
currentProxy && state.selection.group
? getSignalIcon(currentDelay)
: { icon: , text: "未初始化", color: "text.secondary" };
// 自定义渲染选择框中的值
const renderProxyValue = useCallback(
(selected: string) => {
if (!selected || !state.proxyData.records[selected]) return selected;
const delayValue = delayManager.getDelayFix(
state.proxyData.records[selected],
state.selection.group,
);
return (
{selected}
);
},
[state.proxyData.records, state.selection.group],
);
// 排序类型变更
const handleSortTypeChange = useCallback(() => {
const newSortType = ((sortType + 1) % 3) as ProxySortType;
setSortType(newSortType);
localStorage.setItem(STORAGE_KEY_SORT_TYPE, newSortType.toString());
}, [sortType]);
// 排序代理函数(增加非空校验)
const sortProxies = useCallback(
(proxies: ProxyOption[]) => {
if (!proxies || sortType === 0) return proxies;
// 确保数据存在
if (!state.proxyData.records || !state.selection.group) return proxies;
const list = [...proxies];
if (sortType === 1) {
list.sort((a, b) => {
const recordA = state.proxyData.records[a.name];
const recordB = state.proxyData.records[b.name];
// 处理 record 不存在的情况
if (!recordA) return 1;
if (!recordB) return -1;
const ad = delayManager.getDelayFix(recordA, state.selection.group);
const bd = delayManager.getDelayFix(recordB, state.selection.group);
if (ad === -1 || ad === -2) return 1;
if (bd === -1 || bd === -2) return -1;
return ad - bd;
});
} else {
list.sort((a, b) => a.name.localeCompare(b.name));
}
return list;
},
[sortType, state.proxyData.records, state.selection.group],
);
// 计算要显示的代理选项(增加非空校验)
const proxyOptions = useMemo(() => {
if (isDirectMode) {
return [{ name: "DIRECT" }];
}
if (isGlobalMode && proxies?.global) {
const options = proxies.global.all
.filter((p: any) => {
const name = typeof p === "string" ? p : p.name;
return name !== "DIRECT" && name !== "REJECT";
})
.map((p: any) => ({
name: typeof p === "string" ? p : p.name,
}));
return sortProxies(options);
}
// 规则模式
const group = state.selection.group
? state.proxyData.groups.find((g) => g.name === state.selection.group)
: null;
if (group) {
const options = group.all.map((name) => ({ name }));
return sortProxies(options);
}
return [];
}, [
isDirectMode,
isGlobalMode,
proxies,
state.proxyData,
state.selection.group,
sortProxies,
]);
// 获取排序图标
const getSortIcon = () => {
switch (sortType) {
case 1:
return ;
case 2:
return ;
default:
return ;
}
};
// 获取排序提示文本
const getSortTooltip = () => {
switch (sortType) {
case 0:
return t("Sort by default");
case 1:
return t("Sort by delay");
case 2:
return t("Sort by name");
default:
return "";
}
};
return (
{currentProxy ? signalInfo.icon : }
}
iconColor={currentProxy ? "primary" : undefined}
action={
{getSortIcon()}
}
>
{t("Label-Proxies")}
}
>
{currentProxy ? (
{/* 代理节点信息显示 */}
{currentProxy.name}
{currentProxy.type}
{isGlobalMode && (
)}
{isDirectMode && (
)}
{/* 节点特性 */}
{currentProxy.udp && (
)}
{currentProxy.tfo && (
)}
{currentProxy.xudp && (
)}
{currentProxy.mptcp && (
)}
{currentProxy.smux && (
)}
{/* 显示延迟 */}
{currentProxy && !isDirectMode && (
)}
{/* 代理组选择器 */}
{t("Group")}
{/* 代理节点选择器 */}
{t("Proxy")}
) : (
{t("No active proxy node")}
)}
);
};