diff --git a/UPDATELOG.md b/UPDATELOG.md index 613690b0..c91aa917 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -33,6 +33,7 @@ - 订阅卡片点击时间可切换下次自动更新时间,自动更新触发后页面有明确的成功与否提示 - 添加网络管理器以优化网络请求处理,防止资源竞争导致的启动时 UI 卡死 - 更新依赖 + - 首页当前节点增加排序功能 #### 优化了: - 系统代理 Bypass 设置 diff --git a/src/components/home/current-proxy-card.tsx b/src/components/home/current-proxy-card.tsx index 891d77d3..0af6b9a4 100644 --- a/src/components/home/current-proxy-card.tsx +++ b/src/components/home/current-proxy-card.tsx @@ -12,6 +12,7 @@ import { InputLabel, SelectChangeEvent, Tooltip, + IconButton, } from "@mui/material"; import { useEffect, useState, useMemo, useCallback } from "react"; import { @@ -22,6 +23,9 @@ import { 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"; @@ -33,18 +37,20 @@ import { useAppData } from "@/providers/app-data-provider"; // 本地存储的键名 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; } -// 将delayManager返回的颜色格式转换为MUI Chip组件需要的格式 +// 排序类型: 默认 | 按延迟 | 按字母 +export type ProxySortType = 0 | 1 | 2; + function convertDelayColor(delayValue: number) { const colorStr = delayManager.formatDelayColor(delayValue); if (!colorStr) return "default"; - // 从"error.main"这样的格式转为"error" const mainColor = colorStr.split(".")[0]; switch (mainColor) { @@ -61,7 +67,6 @@ function convertDelayColor(delayValue: number) { } } -// 根据延迟值获取合适的WiFi信号图标 function getSignalIcon(delay: number) { if (delay < 0) return { icon: , text: "未测试", color: "text.secondary" }; @@ -97,6 +102,12 @@ export const CurrentProxyCard = () => { 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: { @@ -112,7 +123,6 @@ export const CurrentProxyCard = () => { displayProxy: any; }; - // 合并状态,减少状态更新次数 const [state, setState] = useState({ proxyData: { groups: [], @@ -130,12 +140,10 @@ export const CurrentProxyCard = () => { // 初始化选择的组 useEffect(() => { if (!proxies) return; - - // 提取primaryGroupName + const getPrimaryGroupName = () => { if (!proxies?.groups?.length) return ""; - - // 查找主要的代理组(优先级:包含关键词 > 第一个非GLOBAL组) + const primaryKeywords = [ "auto", "select", @@ -187,10 +195,8 @@ export const CurrentProxyCard = () => { // 监听代理数据变化,更新状态 useEffect(() => { if (!proxies) return; - - // 使用函数式更新确保状态更新的原子性 + setState((prev) => { - // 过滤和格式化组 const filteredGroups = proxies.groups .filter((g: { name: string }) => g.name !== "DIRECT" && g.name !== "REJECT") .map((g: { name: string; now: string; all: Array<{ name: string }> }) => ({ @@ -213,7 +219,6 @@ export const CurrentProxyCard = () => { newProxy = proxies.global.now || ""; newDisplayProxy = proxies.records?.[newProxy] || null; } else { - // 普通模式 - 检查当前选择的组是否存在 const currentGroup = filteredGroups.find( (g: { name: string }) => g.name === prev.selection.group, ); @@ -225,7 +230,6 @@ export const CurrentProxyCard = () => { newProxy = firstGroup.now; newDisplayProxy = proxies.records?.[newProxy] || null; - // 保存到本地存储 if (!isGlobalMode && !isDirectMode) { localStorage.setItem(STORAGE_KEY_GROUP, newGroup); if (newProxy) { @@ -233,7 +237,6 @@ export const CurrentProxyCard = () => { } } } else if (currentGroup) { - // 使用当前组的代理 newProxy = currentGroup.now; newDisplayProxy = proxies.records?.[newProxy] || null; } @@ -256,7 +259,7 @@ export const CurrentProxyCard = () => { }); }, [proxies, isGlobalMode, isDirectMode]); - // 使用防抖包装状态更新,避免快速连续更新,增加防抖时间 + // 使用防抖包装状态更新 const debouncedSetState = useCallback( debounce((updateFn: (prev: ProxyState) => ProxyState) => { setState(updateFn); @@ -271,10 +274,8 @@ export const CurrentProxyCard = () => { 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) { @@ -308,7 +309,6 @@ export const CurrentProxyCard = () => { const currentGroup = state.selection.group; const previousProxy = state.selection.proxy; - // 立即更新UI,优化体验 debouncedSetState((prev: ProxyState) => ({ ...prev, selection: { @@ -318,13 +318,11 @@ export const CurrentProxyCard = () => { displayProxy: prev.proxyData.records[newProxy] || null, })); - // 非特殊模式下保存到本地存储 if (!isGlobalMode && !isDirectMode) { localStorage.setItem(STORAGE_KEY_PROXY, newProxy); } try { - // 更新代理设置 await updateProxy(currentGroup, newProxy); // 自动关闭连接设置 @@ -363,7 +361,6 @@ export const CurrentProxyCard = () => { // 获取要显示的代理节点 const currentProxy = useMemo(() => { - // 从state中获取当前代理信息 return state.displayProxy; }, [state.displayProxy]); @@ -372,7 +369,6 @@ export const CurrentProxyCard = () => { ? delayManager.getDelayFix(currentProxy, state.selection.group) : -1; - // 获取信号图标 const signalInfo = getSignalIcon(currentDelay); // 自定义渲染选择框中的值 @@ -399,27 +395,99 @@ export const CurrentProxyCard = () => { [state.proxyData.records, state.selection.group], ); - // 计算要显示的代理选项 - 使用 useMemo 优化 + // 排序类型变更 + 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; + + const list = [...proxies]; + + if (sortType === 1) { + list.sort((a, b) => { + const ad = delayManager.getDelayFix( + state.proxyData.records[a.name], + state.selection.group + ); + const bd = delayManager.getDelayFix( + state.proxyData.records[b.name], + 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 && state.proxyData.records) { - // 全局模式下的选项 - return Object.keys(state.proxyData.records) - .filter((name) => name !== "DIRECT" && name !== "REJECT") - .map((name) => ({ name })); + 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.proxyData.groups.find( (g: { name: string }) => g.name === state.selection.group, ); if (group) { - return group.all.map((name) => ({ name })); + const options = group.all.map((name) => ({ name })); + return sortProxies(options); } return []; - }, [isDirectMode, isGlobalMode, state.proxyData, state.selection.group]); + }, [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 ( { } iconColor={currentProxy ? "primary" : undefined} action={ - + + + + {getSortIcon()} + + + + } > {currentProxy ? (