feat: add sorting functionality to current node list on homepage

This commit is contained in:
wonfen 2025-05-04 16:27:44 +08:00
parent ff5a2c6ca4
commit cfe8328f9e
2 changed files with 121 additions and 40 deletions

View File

@ -33,6 +33,7 @@
- 订阅卡片点击时间可切换下次自动更新时间,自动更新触发后页面有明确的成功与否提示 - 订阅卡片点击时间可切换下次自动更新时间,自动更新触发后页面有明确的成功与否提示
- 添加网络管理器以优化网络请求处理,防止资源竞争导致的启动时 UI 卡死 - 添加网络管理器以优化网络请求处理,防止资源竞争导致的启动时 UI 卡死
- 更新依赖 - 更新依赖
- 首页当前节点增加排序功能
#### 优化了: #### 优化了:
- 系统代理 Bypass 设置 - 系统代理 Bypass 设置

View File

@ -12,6 +12,7 @@ import {
InputLabel, InputLabel,
SelectChangeEvent, SelectChangeEvent,
Tooltip, Tooltip,
IconButton,
} from "@mui/material"; } from "@mui/material";
import { useEffect, useState, useMemo, useCallback } from "react"; import { useEffect, useState, useMemo, useCallback } from "react";
import { import {
@ -22,6 +23,9 @@ import {
SignalWifi0Bar as SignalNone, SignalWifi0Bar as SignalNone,
WifiOff as SignalError, WifiOff as SignalError,
ChevronRight, ChevronRight,
SortRounded,
AccessTimeRounded,
SortByAlphaRounded,
} from "@mui/icons-material"; } from "@mui/icons-material";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { EnhancedCard } from "@/components/home/enhanced-card"; 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_GROUP = "clash-verge-selected-proxy-group";
const STORAGE_KEY_PROXY = "clash-verge-selected-proxy"; const STORAGE_KEY_PROXY = "clash-verge-selected-proxy";
const STORAGE_KEY_SORT_TYPE = "clash-verge-proxy-sort-type";
// 代理节点信息接口 // 代理节点信息接口
interface ProxyOption { interface ProxyOption {
name: string; name: string;
} }
// 将delayManager返回的颜色格式转换为MUI Chip组件需要的格式 // 排序类型: 默认 | 按延迟 | 按字母
export type ProxySortType = 0 | 1 | 2;
function convertDelayColor(delayValue: number) { function convertDelayColor(delayValue: number) {
const colorStr = delayManager.formatDelayColor(delayValue); const colorStr = delayManager.formatDelayColor(delayValue);
if (!colorStr) return "default"; if (!colorStr) return "default";
// 从"error.main"这样的格式转为"error"
const mainColor = colorStr.split(".")[0]; const mainColor = colorStr.split(".")[0];
switch (mainColor) { switch (mainColor) {
@ -61,7 +67,6 @@ function convertDelayColor(delayValue: number) {
} }
} }
// 根据延迟值获取合适的WiFi信号图标
function getSignalIcon(delay: number) { function getSignalIcon(delay: number) {
if (delay < 0) if (delay < 0)
return { icon: <SignalNone />, text: "未测试", color: "text.secondary" }; return { icon: <SignalNone />, text: "未测试", color: "text.secondary" };
@ -97,6 +102,12 @@ export const CurrentProxyCard = () => {
const isGlobalMode = mode === "global"; const isGlobalMode = mode === "global";
const isDirectMode = mode === "direct"; const isDirectMode = mode === "direct";
// 添加排序类型状态
const [sortType, setSortType] = useState<ProxySortType>(() => {
const savedSortType = localStorage.getItem(STORAGE_KEY_SORT_TYPE);
return savedSortType ? Number(savedSortType) as ProxySortType : 0;
});
// 定义状态类型 // 定义状态类型
type ProxyState = { type ProxyState = {
proxyData: { proxyData: {
@ -112,7 +123,6 @@ export const CurrentProxyCard = () => {
displayProxy: any; displayProxy: any;
}; };
// 合并状态,减少状态更新次数
const [state, setState] = useState<ProxyState>({ const [state, setState] = useState<ProxyState>({
proxyData: { proxyData: {
groups: [], groups: [],
@ -131,11 +141,9 @@ export const CurrentProxyCard = () => {
useEffect(() => { useEffect(() => {
if (!proxies) return; if (!proxies) return;
// 提取primaryGroupName
const getPrimaryGroupName = () => { const getPrimaryGroupName = () => {
if (!proxies?.groups?.length) return ""; if (!proxies?.groups?.length) return "";
// 查找主要的代理组(优先级:包含关键词 > 第一个非GLOBAL组
const primaryKeywords = [ const primaryKeywords = [
"auto", "auto",
"select", "select",
@ -188,9 +196,7 @@ export const CurrentProxyCard = () => {
useEffect(() => { useEffect(() => {
if (!proxies) return; if (!proxies) return;
// 使用函数式更新确保状态更新的原子性
setState((prev) => { setState((prev) => {
// 过滤和格式化组
const filteredGroups = proxies.groups const filteredGroups = proxies.groups
.filter((g: { name: string }) => g.name !== "DIRECT" && g.name !== "REJECT") .filter((g: { name: string }) => g.name !== "DIRECT" && g.name !== "REJECT")
.map((g: { name: string; now: string; all: Array<{ name: string }> }) => ({ .map((g: { name: string; now: string; all: Array<{ name: string }> }) => ({
@ -213,7 +219,6 @@ export const CurrentProxyCard = () => {
newProxy = proxies.global.now || ""; newProxy = proxies.global.now || "";
newDisplayProxy = proxies.records?.[newProxy] || null; newDisplayProxy = proxies.records?.[newProxy] || null;
} else { } else {
// 普通模式 - 检查当前选择的组是否存在
const currentGroup = filteredGroups.find( const currentGroup = filteredGroups.find(
(g: { name: string }) => g.name === prev.selection.group, (g: { name: string }) => g.name === prev.selection.group,
); );
@ -225,7 +230,6 @@ export const CurrentProxyCard = () => {
newProxy = firstGroup.now; newProxy = firstGroup.now;
newDisplayProxy = proxies.records?.[newProxy] || null; newDisplayProxy = proxies.records?.[newProxy] || null;
// 保存到本地存储
if (!isGlobalMode && !isDirectMode) { if (!isGlobalMode && !isDirectMode) {
localStorage.setItem(STORAGE_KEY_GROUP, newGroup); localStorage.setItem(STORAGE_KEY_GROUP, newGroup);
if (newProxy) { if (newProxy) {
@ -233,7 +237,6 @@ export const CurrentProxyCard = () => {
} }
} }
} else if (currentGroup) { } else if (currentGroup) {
// 使用当前组的代理
newProxy = currentGroup.now; newProxy = currentGroup.now;
newDisplayProxy = proxies.records?.[newProxy] || null; newDisplayProxy = proxies.records?.[newProxy] || null;
} }
@ -256,7 +259,7 @@ export const CurrentProxyCard = () => {
}); });
}, [proxies, isGlobalMode, isDirectMode]); }, [proxies, isGlobalMode, isDirectMode]);
// 使用防抖包装状态更新,避免快速连续更新,增加防抖时间 // 使用防抖包装状态更新
const debouncedSetState = useCallback( const debouncedSetState = useCallback(
debounce((updateFn: (prev: ProxyState) => ProxyState) => { debounce((updateFn: (prev: ProxyState) => ProxyState) => {
setState(updateFn); setState(updateFn);
@ -271,10 +274,8 @@ export const CurrentProxyCard = () => {
const newGroup = event.target.value; const newGroup = event.target.value;
// 保存到本地存储
localStorage.setItem(STORAGE_KEY_GROUP, newGroup); localStorage.setItem(STORAGE_KEY_GROUP, newGroup);
// 获取该组当前选中的代理
setState((prev) => { setState((prev) => {
const group = prev.proxyData.groups.find((g: { name: string }) => g.name === newGroup); const group = prev.proxyData.groups.find((g: { name: string }) => g.name === newGroup);
if (group) { if (group) {
@ -308,7 +309,6 @@ export const CurrentProxyCard = () => {
const currentGroup = state.selection.group; const currentGroup = state.selection.group;
const previousProxy = state.selection.proxy; const previousProxy = state.selection.proxy;
// 立即更新UI优化体验
debouncedSetState((prev: ProxyState) => ({ debouncedSetState((prev: ProxyState) => ({
...prev, ...prev,
selection: { selection: {
@ -318,13 +318,11 @@ export const CurrentProxyCard = () => {
displayProxy: prev.proxyData.records[newProxy] || null, displayProxy: prev.proxyData.records[newProxy] || null,
})); }));
// 非特殊模式下保存到本地存储
if (!isGlobalMode && !isDirectMode) { if (!isGlobalMode && !isDirectMode) {
localStorage.setItem(STORAGE_KEY_PROXY, newProxy); localStorage.setItem(STORAGE_KEY_PROXY, newProxy);
} }
try { try {
// 更新代理设置
await updateProxy(currentGroup, newProxy); await updateProxy(currentGroup, newProxy);
// 自动关闭连接设置 // 自动关闭连接设置
@ -363,7 +361,6 @@ export const CurrentProxyCard = () => {
// 获取要显示的代理节点 // 获取要显示的代理节点
const currentProxy = useMemo(() => { const currentProxy = useMemo(() => {
// 从state中获取当前代理信息
return state.displayProxy; return state.displayProxy;
}, [state.displayProxy]); }, [state.displayProxy]);
@ -372,7 +369,6 @@ export const CurrentProxyCard = () => {
? delayManager.getDelayFix(currentProxy, state.selection.group) ? delayManager.getDelayFix(currentProxy, state.selection.group)
: -1; : -1;
// 获取信号图标
const signalInfo = getSignalIcon(currentDelay); const signalInfo = getSignalIcon(currentDelay);
// 自定义渲染选择框中的值 // 自定义渲染选择框中的值
@ -399,27 +395,99 @@ export const CurrentProxyCard = () => {
[state.proxyData.records, state.selection.group], [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(() => { const proxyOptions = useMemo(() => {
if (isDirectMode) { if (isDirectMode) {
return [{ name: "DIRECT" }]; return [{ name: "DIRECT" }];
} }
if (isGlobalMode && state.proxyData.records) { if (isGlobalMode && proxies?.global) {
// 全局模式下的选项 const options = proxies.global.all
return Object.keys(state.proxyData.records) .filter((p: any) => {
.filter((name) => name !== "DIRECT" && name !== "REJECT") const name = typeof p === 'string' ? p : p.name;
.map((name) => ({ 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( const group = state.proxyData.groups.find(
(g: { name: string }) => g.name === state.selection.group, (g: { name: string }) => g.name === state.selection.group,
); );
if (group) { if (group) {
return group.all.map((name) => ({ name })); const options = group.all.map((name) => ({ name }));
return sortProxies(options);
} }
return []; return [];
}, [isDirectMode, isGlobalMode, state.proxyData, state.selection.group]); }, [isDirectMode, isGlobalMode, proxies, state.proxyData, state.selection.group, sortProxies]);
// 获取排序图标
const getSortIcon = () => {
switch (sortType) {
case 1:
return <AccessTimeRounded fontSize="small" />;
case 2:
return <SortByAlphaRounded fontSize="small" />;
default:
return <SortRounded fontSize="small" />;
}
};
// 获取排序提示文本
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 ( return (
<EnhancedCard <EnhancedCard
@ -439,6 +507,17 @@ export const CurrentProxyCard = () => {
} }
iconColor={currentProxy ? "primary" : undefined} iconColor={currentProxy ? "primary" : undefined}
action={ action={
<Box sx={{ display: "flex", alignItems: "center" }}>
<Tooltip title={getSortTooltip()}>
<IconButton
size="small"
color="inherit"
onClick={handleSortTypeChange}
sx={{ mr: 1 }}
>
{getSortIcon()}
</IconButton>
</Tooltip>
<Button <Button
variant="outlined" variant="outlined"
size="small" size="small"
@ -448,6 +527,7 @@ export const CurrentProxyCard = () => {
> >
{t("Label-Proxies")} {t("Label-Proxies")}
</Button> </Button>
</Box>
} }
> >
{currentProxy ? ( {currentProxy ? (