mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 03:03:46 +08:00
feat: add sorting functionality to current node list on homepage
This commit is contained in:
parent
ff5a2c6ca4
commit
cfe8328f9e
@ -33,6 +33,7 @@
|
|||||||
- 订阅卡片点击时间可切换下次自动更新时间,自动更新触发后页面有明确的成功与否提示
|
- 订阅卡片点击时间可切换下次自动更新时间,自动更新触发后页面有明确的成功与否提示
|
||||||
- 添加网络管理器以优化网络请求处理,防止资源竞争导致的启动时 UI 卡死
|
- 添加网络管理器以优化网络请求处理,防止资源竞争导致的启动时 UI 卡死
|
||||||
- 更新依赖
|
- 更新依赖
|
||||||
|
- 首页当前节点增加排序功能
|
||||||
|
|
||||||
#### 优化了:
|
#### 优化了:
|
||||||
- 系统代理 Bypass 设置
|
- 系统代理 Bypass 设置
|
||||||
|
@ -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,15 +507,27 @@ export const CurrentProxyCard = () => {
|
|||||||
}
|
}
|
||||||
iconColor={currentProxy ? "primary" : undefined}
|
iconColor={currentProxy ? "primary" : undefined}
|
||||||
action={
|
action={
|
||||||
<Button
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
variant="outlined"
|
<Tooltip title={getSortTooltip()}>
|
||||||
size="small"
|
<IconButton
|
||||||
onClick={goToProxies}
|
size="small"
|
||||||
sx={{ borderRadius: 1.5 }}
|
color="inherit"
|
||||||
endIcon={<ChevronRight fontSize="small" />}
|
onClick={handleSortTypeChange}
|
||||||
>
|
sx={{ mr: 1 }}
|
||||||
{t("Label-Proxies")}
|
>
|
||||||
</Button>
|
{getSortIcon()}
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
size="small"
|
||||||
|
onClick={goToProxies}
|
||||||
|
sx={{ borderRadius: 1.5 }}
|
||||||
|
endIcon={<ChevronRight fontSize="small" />}
|
||||||
|
>
|
||||||
|
{t("Label-Proxies")}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{currentProxy ? (
|
{currentProxy ? (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user