diff --git a/src/components/home/current-proxy-card.tsx b/src/components/home/current-proxy-card.tsx index b0cdf767..c20f9a74 100644 --- a/src/components/home/current-proxy-card.tsx +++ b/src/components/home/current-proxy-card.tsx @@ -29,14 +29,9 @@ import { } from "@mui/icons-material"; import { useNavigate } from "react-router-dom"; import { EnhancedCard } from "@/components/home/enhanced-card"; -import { - updateProxy, - deleteConnection, - syncTrayProxySelection, -} from "@/services/cmds"; import delayManager from "@/services/delay"; -import { useVerge } from "@/hooks/use-verge"; import { useAppData } from "@/providers/app-data-provider"; +import { useProxySelection } from "@/hooks/use-proxy-selection"; // 本地存储的键名 const STORAGE_KEY_GROUP = "clash-verge-selected-proxy-group"; @@ -98,8 +93,18 @@ export const CurrentProxyCard = () => { const { t } = useTranslation(); const navigate = useNavigate(); const theme = useTheme(); - const { verge } = useVerge(); - const { proxies, connections, clashConfig, refreshProxy } = useAppData(); + const { proxies, clashConfig, refreshProxy } = useAppData(); + + // 统一代理选择器 + const { handleSelectChange } = useProxySelection({ + onSuccess: () => { + refreshProxy(); + }, + onError: (error) => { + console.error("代理切换失败", error); + refreshProxy(); + }, + }); // 判断模式 const mode = clashConfig?.mode?.toLowerCase() || "rule"; @@ -117,8 +122,6 @@ export const CurrentProxyCard = () => { proxyData: { groups: { name: string; now: string; all: string[] }[]; records: Record; - globalProxy: string; - directProxy: any; }; selection: { group: string; @@ -131,8 +134,6 @@ export const CurrentProxyCard = () => { proxyData: { groups: [], records: {}, - globalProxy: "", - directProxy: { name: "DIRECT" }, // 默认值避免 undefined }, selection: { group: "", @@ -257,8 +258,6 @@ export const CurrentProxyCard = () => { proxyData: { groups: filteredGroups, records: proxies.records || {}, - globalProxy: proxies.global?.now || "", - directProxy: proxies.records?.DIRECT || { name: "DIRECT" }, }, selection: { group: newGroup, @@ -314,7 +313,7 @@ export const CurrentProxyCard = () => { // 处理代理节点变更 const handleProxyChange = useCallback( - async (event: SelectChangeEvent) => { + (event: SelectChangeEvent) => { if (isDirectMode) return; const newProxy = event.target.value; @@ -334,42 +333,15 @@ export const CurrentProxyCard = () => { localStorage.setItem(STORAGE_KEY_PROXY, newProxy); } - try { - await updateProxy(currentGroup, newProxy); - - // 自动关闭连接设置 - 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); - } + const skipConfigSave = isGlobalMode || isDirectMode; + handleSelectChange(currentGroup, previousProxy, skipConfigSave)(event); }, [ isDirectMode, isGlobalMode, - state.proxyData.records, state.selection, - verge?.auto_close_connection, - refreshProxy, debouncedSetState, - connections.data, + handleSelectChange, ], ); diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx index 015eb2c9..44318471 100644 --- a/src/components/proxy/proxy-groups.tsx +++ b/src/components/proxy/proxy-groups.tsx @@ -1,18 +1,9 @@ import { useRef, useState, useEffect, useCallback, useMemo } from "react"; import { useLockFn } from "ahooks"; import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"; -import { - getConnections, - providerHealthCheck, - updateProxy, - deleteConnection, - getGroupProxyDelays, - syncTrayProxySelection, - updateProxyAndSync, -} from "@/services/cmds"; -import { forceRefreshProxies } from "@/services/cmds"; -import { useProfiles } from "@/hooks/use-profiles"; +import { providerHealthCheck, getGroupProxyDelays } from "@/services/cmds"; import { useVerge } from "@/hooks/use-verge"; +import { useProxySelection } from "@/hooks/use-proxy-selection"; import { BaseEmpty } from "../base"; import { useRenderList } from "./use-render-list"; import { ProxyRender } from "./proxy-render"; @@ -205,7 +196,17 @@ export const ProxyGroups = (props: Props) => { const { renderList, onProxies, onHeadState } = useRenderList(mode); const { verge } = useVerge(); - const { current, patchCurrent } = useProfiles(); + + // 统代理选择 + const { handleProxyGroupChange } = useProxySelection({ + onSuccess: () => { + onProxies(); + }, + onError: (error) => { + console.error("代理切换失败", error); + onProxies(); + }, + }); // 获取自动滚动开关状态,默认为 true const enableAutoScroll = verge?.enable_hover_jump_navigator ?? true; @@ -337,73 +338,13 @@ export const ProxyGroups = (props: Props) => { [letterIndexMap], ); - // 切换分组的节点代理 - const handleChangeProxy = useLockFn( - async (group: IProxyGroupItem, proxy: IProxyItem) => { + const handleChangeProxy = useCallback( + (group: IProxyGroupItem, proxy: IProxyItem) => { if (!["Selector", "URLTest", "Fallback"].includes(group.type)) return; - const { name, now } = group; - 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(group, proxy); }, + [handleProxyGroupChange], ); // 测全部延迟 diff --git a/src/hooks/use-proxy-selection.ts b/src/hooks/use-proxy-selection.ts new file mode 100644 index 00000000..bb3a4674 --- /dev/null +++ b/src/hooks/use-proxy-selection.ts @@ -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, + }; +}; diff --git a/src/services/delay.ts b/src/services/delay.ts index 26008333..c69b2ae0 100644 --- a/src/services/delay.ts +++ b/src/services/delay.ts @@ -74,7 +74,8 @@ class DelayManager { if (delay >= 0 || delay === -2) return delay; } - if (proxy.history.length > 0) { + // 添加 history 属性的安全检查 + if (proxy.history && proxy.history.length > 0) { // 0ms以error显示 return proxy.history[proxy.history.length - 1].delay || 1e6; }