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 卡死
- 更新依赖
- 首页当前节点增加排序功能
#### 优化了:
- 系统代理 Bypass 设置

View File

@ -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: <SignalNone />, text: "未测试", color: "text.secondary" };
@ -97,6 +102,12 @@ export const CurrentProxyCard = () => {
const isGlobalMode = mode === "global";
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 = {
proxyData: {
@ -112,7 +123,6 @@ export const CurrentProxyCard = () => {
displayProxy: any;
};
// 合并状态,减少状态更新次数
const [state, setState] = useState<ProxyState>({
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 <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 (
<EnhancedCard
@ -439,15 +507,27 @@ export const CurrentProxyCard = () => {
}
iconColor={currentProxy ? "primary" : undefined}
action={
<Button
variant="outlined"
size="small"
onClick={goToProxies}
sx={{ borderRadius: 1.5 }}
endIcon={<ChevronRight fontSize="small" />}
>
{t("Label-Proxies")}
</Button>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Tooltip title={getSortTooltip()}>
<IconButton
size="small"
color="inherit"
onClick={handleSortTypeChange}
sx={{ mr: 1 }}
>
{getSortIcon()}
</IconButton>
</Tooltip>
<Button
variant="outlined"
size="small"
onClick={goToProxies}
sx={{ borderRadius: 1.5 }}
endIcon={<ChevronRight fontSize="small" />}
>
{t("Label-Proxies")}
</Button>
</Box>
}
>
{currentProxy ? (