mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
fix: unify homepage node selection
This commit is contained in:
@@ -29,14 +29,9 @@ import {
|
|||||||
} 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";
|
||||||
import {
|
|
||||||
updateProxy,
|
|
||||||
deleteConnection,
|
|
||||||
syncTrayProxySelection,
|
|
||||||
} from "@/services/cmds";
|
|
||||||
import delayManager from "@/services/delay";
|
import delayManager from "@/services/delay";
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
|
||||||
import { useAppData } from "@/providers/app-data-provider";
|
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_GROUP = "clash-verge-selected-proxy-group";
|
||||||
@@ -98,8 +93,18 @@ export const CurrentProxyCard = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { verge } = useVerge();
|
const { proxies, clashConfig, refreshProxy } = useAppData();
|
||||||
const { proxies, connections, clashConfig, refreshProxy } = useAppData();
|
|
||||||
|
// 统一代理选择器
|
||||||
|
const { handleSelectChange } = useProxySelection({
|
||||||
|
onSuccess: () => {
|
||||||
|
refreshProxy();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("代理切换失败", error);
|
||||||
|
refreshProxy();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// 判断模式
|
// 判断模式
|
||||||
const mode = clashConfig?.mode?.toLowerCase() || "rule";
|
const mode = clashConfig?.mode?.toLowerCase() || "rule";
|
||||||
@@ -117,8 +122,6 @@ export const CurrentProxyCard = () => {
|
|||||||
proxyData: {
|
proxyData: {
|
||||||
groups: { name: string; now: string; all: string[] }[];
|
groups: { name: string; now: string; all: string[] }[];
|
||||||
records: Record<string, any>;
|
records: Record<string, any>;
|
||||||
globalProxy: string;
|
|
||||||
directProxy: any;
|
|
||||||
};
|
};
|
||||||
selection: {
|
selection: {
|
||||||
group: string;
|
group: string;
|
||||||
@@ -131,8 +134,6 @@ export const CurrentProxyCard = () => {
|
|||||||
proxyData: {
|
proxyData: {
|
||||||
groups: [],
|
groups: [],
|
||||||
records: {},
|
records: {},
|
||||||
globalProxy: "",
|
|
||||||
directProxy: { name: "DIRECT" }, // 默认值避免 undefined
|
|
||||||
},
|
},
|
||||||
selection: {
|
selection: {
|
||||||
group: "",
|
group: "",
|
||||||
@@ -257,8 +258,6 @@ export const CurrentProxyCard = () => {
|
|||||||
proxyData: {
|
proxyData: {
|
||||||
groups: filteredGroups,
|
groups: filteredGroups,
|
||||||
records: proxies.records || {},
|
records: proxies.records || {},
|
||||||
globalProxy: proxies.global?.now || "",
|
|
||||||
directProxy: proxies.records?.DIRECT || { name: "DIRECT" },
|
|
||||||
},
|
},
|
||||||
selection: {
|
selection: {
|
||||||
group: newGroup,
|
group: newGroup,
|
||||||
@@ -314,7 +313,7 @@ export const CurrentProxyCard = () => {
|
|||||||
|
|
||||||
// 处理代理节点变更
|
// 处理代理节点变更
|
||||||
const handleProxyChange = useCallback(
|
const handleProxyChange = useCallback(
|
||||||
async (event: SelectChangeEvent) => {
|
(event: SelectChangeEvent) => {
|
||||||
if (isDirectMode) return;
|
if (isDirectMode) return;
|
||||||
|
|
||||||
const newProxy = event.target.value;
|
const newProxy = event.target.value;
|
||||||
@@ -334,42 +333,15 @@ export const CurrentProxyCard = () => {
|
|||||||
localStorage.setItem(STORAGE_KEY_PROXY, newProxy);
|
localStorage.setItem(STORAGE_KEY_PROXY, newProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const skipConfigSave = isGlobalMode || isDirectMode;
|
||||||
await updateProxy(currentGroup, newProxy);
|
handleSelectChange(currentGroup, previousProxy, skipConfigSave)(event);
|
||||||
|
|
||||||
// 自动关闭连接设置
|
|
||||||
if (verge?.auto_close_connection && previousProxy) {
|
|
||||||
connections.data.forEach((conn: any) => {
|
|
||||||
if (conn.chains.includes(previousProxy)) {
|
|
||||||
deleteConnection(conn.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同步托盘菜单状态
|
|
||||||
try {
|
|
||||||
await syncTrayProxySelection();
|
|
||||||
} catch (syncError) {
|
|
||||||
console.warn("Failed to sync tray proxy selection:", syncError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 延长刷新延迟时间
|
|
||||||
setTimeout(() => {
|
|
||||||
refreshProxy();
|
|
||||||
}, 500);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("更新代理失败", error);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
isDirectMode,
|
isDirectMode,
|
||||||
isGlobalMode,
|
isGlobalMode,
|
||||||
state.proxyData.records,
|
|
||||||
state.selection,
|
state.selection,
|
||||||
verge?.auto_close_connection,
|
|
||||||
refreshProxy,
|
|
||||||
debouncedSetState,
|
debouncedSetState,
|
||||||
connections.data,
|
handleSelectChange,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,9 @@
|
|||||||
import { useRef, useState, useEffect, useCallback, useMemo } from "react";
|
import { useRef, useState, useEffect, useCallback, useMemo } from "react";
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
|
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso";
|
||||||
import {
|
import { providerHealthCheck, getGroupProxyDelays } from "@/services/cmds";
|
||||||
getConnections,
|
|
||||||
providerHealthCheck,
|
|
||||||
updateProxy,
|
|
||||||
deleteConnection,
|
|
||||||
getGroupProxyDelays,
|
|
||||||
syncTrayProxySelection,
|
|
||||||
updateProxyAndSync,
|
|
||||||
} from "@/services/cmds";
|
|
||||||
import { forceRefreshProxies } from "@/services/cmds";
|
|
||||||
import { useProfiles } from "@/hooks/use-profiles";
|
|
||||||
import { useVerge } from "@/hooks/use-verge";
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
|
import { useProxySelection } from "@/hooks/use-proxy-selection";
|
||||||
import { BaseEmpty } from "../base";
|
import { BaseEmpty } from "../base";
|
||||||
import { useRenderList } from "./use-render-list";
|
import { useRenderList } from "./use-render-list";
|
||||||
import { ProxyRender } from "./proxy-render";
|
import { ProxyRender } from "./proxy-render";
|
||||||
@@ -205,7 +196,17 @@ export const ProxyGroups = (props: Props) => {
|
|||||||
const { renderList, onProxies, onHeadState } = useRenderList(mode);
|
const { renderList, onProxies, onHeadState } = useRenderList(mode);
|
||||||
|
|
||||||
const { verge } = useVerge();
|
const { verge } = useVerge();
|
||||||
const { current, patchCurrent } = useProfiles();
|
|
||||||
|
// 统代理选择
|
||||||
|
const { handleProxyGroupChange } = useProxySelection({
|
||||||
|
onSuccess: () => {
|
||||||
|
onProxies();
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("代理切换失败", error);
|
||||||
|
onProxies();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// 获取自动滚动开关状态,默认为 true
|
// 获取自动滚动开关状态,默认为 true
|
||||||
const enableAutoScroll = verge?.enable_hover_jump_navigator ?? true;
|
const enableAutoScroll = verge?.enable_hover_jump_navigator ?? true;
|
||||||
@@ -337,73 +338,13 @@ export const ProxyGroups = (props: Props) => {
|
|||||||
[letterIndexMap],
|
[letterIndexMap],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 切换分组的节点代理
|
const handleChangeProxy = useCallback(
|
||||||
const handleChangeProxy = useLockFn(
|
(group: IProxyGroupItem, proxy: IProxyItem) => {
|
||||||
async (group: IProxyGroupItem, proxy: IProxyItem) => {
|
|
||||||
if (!["Selector", "URLTest", "Fallback"].includes(group.type)) return;
|
if (!["Selector", "URLTest", "Fallback"].includes(group.type)) return;
|
||||||
|
|
||||||
const { name, now } = group;
|
handleProxyGroupChange(group, proxy);
|
||||||
console.log(`[ProxyGroups] GUI代理切换: ${name} -> ${proxy.name}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. 保存到selected中 (先保存本地状态)
|
|
||||||
if (current) {
|
|
||||||
if (!current.selected) current.selected = [];
|
|
||||||
|
|
||||||
const index = current.selected.findIndex(
|
|
||||||
(item) => item.name === group.name,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
current.selected.push({ name, now: proxy.name });
|
|
||||||
} else {
|
|
||||||
current.selected[index] = { name, now: proxy.name };
|
|
||||||
}
|
|
||||||
await patchCurrent({ selected: current.selected });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 使用统一的同步命令更新代理并同步状态
|
|
||||||
await updateProxyAndSync(name, proxy.name);
|
|
||||||
console.log(
|
|
||||||
`[ProxyGroups] 代理和状态同步完成: ${name} -> ${proxy.name}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. 刷新前端显示
|
|
||||||
onProxies();
|
|
||||||
|
|
||||||
// 4. 断开连接 (异步处理,不影响UI更新)
|
|
||||||
if (verge?.auto_close_connection) {
|
|
||||||
getConnections().then(({ connections }) => {
|
|
||||||
connections.forEach((conn) => {
|
|
||||||
if (conn.chains.includes(now!)) {
|
|
||||||
deleteConnection(conn.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
`[ProxyGroups] 代理切换失败: ${name} -> ${proxy.name}`,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
// 如果统一命令失败,回退到原来的方式
|
|
||||||
try {
|
|
||||||
await updateProxy(name, proxy.name);
|
|
||||||
await forceRefreshProxies();
|
|
||||||
await syncTrayProxySelection();
|
|
||||||
onProxies();
|
|
||||||
console.log(
|
|
||||||
`[ProxyGroups] 代理切换回退成功: ${name} -> ${proxy.name}`,
|
|
||||||
);
|
|
||||||
} catch (fallbackError) {
|
|
||||||
console.error(
|
|
||||||
`[ProxyGroups] 代理切换回退也失败: ${name} -> ${proxy.name}`,
|
|
||||||
fallbackError,
|
|
||||||
);
|
|
||||||
onProxies(); // 至少刷新显示
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
[handleProxyGroupChange],
|
||||||
);
|
);
|
||||||
|
|
||||||
// 测全部延迟
|
// 测全部延迟
|
||||||
|
|||||||
143
src/hooks/use-proxy-selection.ts
Normal file
143
src/hooks/use-proxy-selection.ts
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import { useCallback, useMemo } from "react";
|
||||||
|
import { useLockFn } from "ahooks";
|
||||||
|
import {
|
||||||
|
updateProxy,
|
||||||
|
updateProxyAndSync,
|
||||||
|
forceRefreshProxies,
|
||||||
|
syncTrayProxySelection,
|
||||||
|
getConnections,
|
||||||
|
deleteConnection,
|
||||||
|
} from "@/services/cmds";
|
||||||
|
import { useProfiles } from "@/hooks/use-profiles";
|
||||||
|
import { useVerge } from "@/hooks/use-verge";
|
||||||
|
|
||||||
|
// 缓存连接清理
|
||||||
|
const cleanupConnections = async (previousProxy: string) => {
|
||||||
|
try {
|
||||||
|
const { connections } = await getConnections();
|
||||||
|
const cleanupPromises = connections
|
||||||
|
.filter((conn) => conn.chains.includes(previousProxy))
|
||||||
|
.map((conn) => deleteConnection(conn.id));
|
||||||
|
|
||||||
|
if (cleanupPromises.length > 0) {
|
||||||
|
await Promise.allSettled(cleanupPromises);
|
||||||
|
console.log(`[ProxySelection] 清理了 ${cleanupPromises.length} 个连接`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("[ProxySelection] 连接清理失败:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ProxySelectionOptions {
|
||||||
|
onSuccess?: () => void;
|
||||||
|
onError?: (error: any) => void;
|
||||||
|
enableConnectionCleanup?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代理选择 Hook
|
||||||
|
export const useProxySelection = (options: ProxySelectionOptions = {}) => {
|
||||||
|
const { current, patchCurrent } = useProfiles();
|
||||||
|
const { verge } = useVerge();
|
||||||
|
|
||||||
|
const { onSuccess, onError, enableConnectionCleanup = true } = options;
|
||||||
|
|
||||||
|
// 缓存
|
||||||
|
const config = useMemo(
|
||||||
|
() => ({
|
||||||
|
autoCloseConnection: verge?.auto_close_connection ?? false,
|
||||||
|
enableConnectionCleanup,
|
||||||
|
}),
|
||||||
|
[verge?.auto_close_connection, enableConnectionCleanup],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 切换节点
|
||||||
|
const changeProxy = useLockFn(
|
||||||
|
async (
|
||||||
|
groupName: string,
|
||||||
|
proxyName: string,
|
||||||
|
previousProxy?: string,
|
||||||
|
skipConfigSave: boolean = false,
|
||||||
|
) => {
|
||||||
|
console.log(`[ProxySelection] 代理切换: ${groupName} -> ${proxyName}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (current && !skipConfigSave) {
|
||||||
|
if (!current.selected) current.selected = [];
|
||||||
|
|
||||||
|
const index = current.selected.findIndex(
|
||||||
|
(item) => item.name === groupName,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
current.selected.push({ name: groupName, now: proxyName });
|
||||||
|
} else {
|
||||||
|
current.selected[index] = { name: groupName, now: proxyName };
|
||||||
|
}
|
||||||
|
await patchCurrent({ selected: current.selected });
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateProxyAndSync(groupName, proxyName);
|
||||||
|
console.log(
|
||||||
|
`[ProxySelection] 代理和状态同步完成: ${groupName} -> ${proxyName}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
onSuccess?.();
|
||||||
|
|
||||||
|
if (
|
||||||
|
config.enableConnectionCleanup &&
|
||||||
|
config.autoCloseConnection &&
|
||||||
|
previousProxy
|
||||||
|
) {
|
||||||
|
setTimeout(() => cleanupConnections(previousProxy), 0);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`[ProxySelection] 代理切换失败: ${groupName} -> ${proxyName}`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateProxy(groupName, proxyName);
|
||||||
|
await forceRefreshProxies();
|
||||||
|
await syncTrayProxySelection();
|
||||||
|
onSuccess?.();
|
||||||
|
console.log(
|
||||||
|
`[ProxySelection] 代理切换回退成功: ${groupName} -> ${proxyName}`,
|
||||||
|
);
|
||||||
|
} catch (fallbackError) {
|
||||||
|
console.error(
|
||||||
|
`[ProxySelection] 代理切换回退也失败: ${groupName} -> ${proxyName}`,
|
||||||
|
fallbackError,
|
||||||
|
);
|
||||||
|
onError?.(fallbackError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelectChange = useCallback(
|
||||||
|
(
|
||||||
|
groupName: string,
|
||||||
|
previousProxy?: string,
|
||||||
|
skipConfigSave: boolean = false,
|
||||||
|
) =>
|
||||||
|
(event: { target: { value: string } }) => {
|
||||||
|
const newProxy = event.target.value;
|
||||||
|
changeProxy(groupName, newProxy, previousProxy, skipConfigSave);
|
||||||
|
},
|
||||||
|
[changeProxy],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleProxyGroupChange = useCallback(
|
||||||
|
(group: { name: string; now?: string }, proxy: { name: string }) => {
|
||||||
|
changeProxy(group.name, proxy.name, group.now);
|
||||||
|
},
|
||||||
|
[changeProxy],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
changeProxy,
|
||||||
|
handleSelectChange,
|
||||||
|
handleProxyGroupChange,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -74,7 +74,8 @@ class DelayManager {
|
|||||||
if (delay >= 0 || delay === -2) return delay;
|
if (delay >= 0 || delay === -2) return delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proxy.history.length > 0) {
|
// 添加 history 属性的安全检查
|
||||||
|
if (proxy.history && proxy.history.length > 0) {
|
||||||
// 0ms以error显示
|
// 0ms以error显示
|
||||||
return proxy.history[proxy.history.length - 1].delay || 1e6;
|
return proxy.history[proxy.history.length - 1].delay || 1e6;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user