From 3cf51de850975d45fc526bbe0003b4ff5a6a4b70 Mon Sep 17 00:00:00 2001 From: Slinetrac Date: Wed, 3 Dec 2025 21:21:31 +0800 Subject: [PATCH] refactor: reorganize component and hook structure; remove unused modules --- src/App.tsx | 12 --- src/components/center.tsx | 21 ------ src/components/home/clash-info-card.tsx | 4 +- src/components/home/clash-mode-card.tsx | 2 +- src/components/home/current-proxy-card.tsx | 6 +- src/components/home/home-profile-card.tsx | 2 +- src/components/home/system-info-card.tsx | 2 +- src/components/proxy/provider-button.tsx | 2 +- src/components/proxy/proxy-chain.tsx | 2 +- src/components/proxy/proxy-groups.tsx | 2 +- src/components/proxy/use-render-list.ts | 2 +- src/components/rule/provider-button.tsx | 5 +- .../setting/mods/sysproxy-viewer.tsx | 2 +- .../shared/ProxyControlSwitches.tsx | 4 +- src/hooks/{app-data.ts => use-clash-data.ts} | 0 src/hooks/use-cleanup.ts | 35 --------- src/hooks/use-current-proxy.ts | 74 ------------------- ...eInstaller.ts => use-service-installer.ts} | 0 ...nstaller.ts => use-service-uninstaller.ts} | 0 src/hooks/use-system-proxy-state.ts | 2 +- src/pages/_layout.tsx | 10 ++- src/pages/_layout/hooks/index.ts | 3 + .../use-app-initialization.ts} | 26 ++++--- .../use-layout-events.ts} | 32 +++++--- .../use-loading-overlay.ts} | 32 ++++---- src/pages/_layout/useLazyDataLoad.ts | 25 ------- src/pages/_layout/utils/index.ts | 2 + .../_layout/utils/initial-loading-overlay.ts | 45 +++++++++++ .../notification-handlers.ts} | 0 src/pages/rules.tsx | 2 +- 30 files changed, 130 insertions(+), 226 deletions(-) delete mode 100644 src/App.tsx delete mode 100644 src/components/center.tsx rename src/hooks/{app-data.ts => use-clash-data.ts} (100%) delete mode 100644 src/hooks/use-cleanup.ts delete mode 100644 src/hooks/use-current-proxy.ts rename src/hooks/{useServiceInstaller.ts => use-service-installer.ts} (100%) rename src/hooks/{useServiceUninstaller.ts => use-service-uninstaller.ts} (100%) create mode 100644 src/pages/_layout/hooks/index.ts rename src/pages/_layout/{useAppInitialization.ts => hooks/use-app-initialization.ts} (81%) rename src/pages/_layout/{useLayoutEvents.ts => hooks/use-layout-events.ts} (75%) rename src/pages/_layout/{useLoadingOverlay.ts => hooks/use-loading-overlay.ts} (52%) delete mode 100644 src/pages/_layout/useLazyDataLoad.ts create mode 100644 src/pages/_layout/utils/index.ts create mode 100644 src/pages/_layout/utils/initial-loading-overlay.ts rename src/pages/_layout/{notificationHandlers.ts => utils/notification-handlers.ts} (100%) diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index c1fe73a7d..000000000 --- a/src/App.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import Layout from "./pages/_layout"; -import { AppDataProvider } from "./providers/app-data-provider"; - -function App() { - return ( - - - - ); -} - -export default App; diff --git a/src/components/center.tsx b/src/components/center.tsx deleted file mode 100644 index 8204c3275..000000000 --- a/src/components/center.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Box, BoxProps } from "@mui/material"; -import React from "react"; - -interface CenterProps extends BoxProps { - children: React.ReactNode; -} - -export const Center: React.FC = ({ children, ...props }) => { - return ( - - {children} - - ); -}; diff --git a/src/components/home/clash-info-card.tsx b/src/components/home/clash-info-card.tsx index bb89c7d31..7a4bff47f 100644 --- a/src/components/home/clash-info-card.tsx +++ b/src/components/home/clash-info-card.tsx @@ -3,14 +3,14 @@ import { Divider, Stack, Typography } from "@mui/material"; import { useMemo } from "react"; import { useTranslation } from "react-i18next"; +import { useClash } from "@/hooks/use-clash"; import { useAppUptime, useClashConfig, useRulesData, useSystemProxyAddress, useSystemProxyData, -} from "@/hooks/app-data"; -import { useClash } from "@/hooks/use-clash"; +} from "@/hooks/use-clash-data"; import { EnhancedCard } from "./enhanced-card"; diff --git a/src/components/home/clash-mode-card.tsx b/src/components/home/clash-mode-card.tsx index 2624ce2dd..e6fb20841 100644 --- a/src/components/home/clash-mode-card.tsx +++ b/src/components/home/clash-mode-card.tsx @@ -9,7 +9,7 @@ import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { closeAllConnections } from "tauri-plugin-mihomo-api"; -import { useClashConfig } from "@/hooks/app-data"; +import { useClashConfig } from "@/hooks/use-clash-data"; import { useVerge } from "@/hooks/use-verge"; import { patchClashMode } from "@/services/cmds"; import type { TranslationKey } from "@/types/generated/i18n-keys"; diff --git a/src/components/home/current-proxy-card.tsx b/src/components/home/current-proxy-card.tsx index 2aabba29b..6032152e9 100644 --- a/src/components/home/current-proxy-card.tsx +++ b/src/components/home/current-proxy-card.tsx @@ -34,7 +34,11 @@ import { useNavigate } from "react-router"; import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api"; import { EnhancedCard } from "@/components/home/enhanced-card"; -import { useClashConfig, useProxiesData, useRulesData } from "@/hooks/app-data"; +import { + useClashConfig, + useProxiesData, + useRulesData, +} from "@/hooks/use-clash-data"; import { useProfiles } from "@/hooks/use-profiles"; import { useProxySelection } from "@/hooks/use-proxy-selection"; import { useVerge } from "@/hooks/use-verge"; diff --git a/src/components/home/home-profile-card.tsx b/src/components/home/home-profile-card.tsx index 8e40287f4..21937ff3a 100644 --- a/src/components/home/home-profile-card.tsx +++ b/src/components/home/home-profile-card.tsx @@ -24,7 +24,7 @@ import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router"; -import { useRefreshAll } from "@/hooks/app-data"; +import { useRefreshAll } from "@/hooks/use-clash-data"; import { openWebUrl, updateProfile } from "@/services/cmds"; import { showNotice } from "@/services/noticeService"; import parseTraffic from "@/utils/parse-traffic"; diff --git a/src/components/home/system-info-card.tsx b/src/components/home/system-info-card.tsx index 8697291f5..c7b8cbfc4 100644 --- a/src/components/home/system-info-card.tsx +++ b/src/components/home/system-info-card.tsx @@ -20,9 +20,9 @@ import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router"; import useSWR from "swr"; +import { useServiceInstaller } from "@/hooks/use-service-installer"; import { useSystemState } from "@/hooks/use-system-state"; import { useVerge } from "@/hooks/use-verge"; -import { useServiceInstaller } from "@/hooks/useServiceInstaller"; import { getSystemInfo } from "@/services/cmds"; import { showNotice } from "@/services/noticeService"; import { checkUpdateSafe as checkUpdate } from "@/services/update"; diff --git a/src/components/proxy/provider-button.tsx b/src/components/proxy/provider-button.tsx index 68217775a..861a57aec 100644 --- a/src/components/proxy/provider-button.tsx +++ b/src/components/proxy/provider-button.tsx @@ -22,7 +22,7 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { updateProxyProvider } from "tauri-plugin-mihomo-api"; -import { useProxiesData, useProxyProvidersData } from "@/hooks/app-data"; +import { useProxiesData, useProxyProvidersData } from "@/hooks/use-clash-data"; import { showNotice } from "@/services/noticeService"; import parseTraffic from "@/utils/parse-traffic"; diff --git a/src/components/proxy/proxy-chain.tsx b/src/components/proxy/proxy-chain.tsx index b10676ce1..9e47a289e 100644 --- a/src/components/proxy/proxy-chain.tsx +++ b/src/components/proxy/proxy-chain.tsx @@ -39,7 +39,7 @@ import { selectNodeForGroup, } from "tauri-plugin-mihomo-api"; -import { useProxiesData } from "@/hooks/app-data"; +import { useProxiesData } from "@/hooks/use-clash-data"; import { calcuProxies, updateProxyChainConfigInRuntime } from "@/services/cmds"; import { debugLog } from "@/utils/debug"; diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx index ab4814262..d103e06dd 100644 --- a/src/components/proxy/proxy-groups.tsx +++ b/src/components/proxy/proxy-groups.tsx @@ -15,7 +15,7 @@ import { useTranslation } from "react-i18next"; import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"; import { delayGroup, healthcheckProxyProvider } from "tauri-plugin-mihomo-api"; -import { useProxiesData } from "@/hooks/app-data"; +import { useProxiesData } from "@/hooks/use-clash-data"; import { useProxySelection } from "@/hooks/use-proxy-selection"; import { useVerge } from "@/hooks/use-verge"; import { updateProxyChainConfigInRuntime } from "@/services/cmds"; diff --git a/src/components/proxy/use-render-list.ts b/src/components/proxy/use-render-list.ts index 9e25c58ec..dd7e96d6e 100644 --- a/src/components/proxy/use-render-list.ts +++ b/src/components/proxy/use-render-list.ts @@ -1,7 +1,7 @@ import { useEffect, useMemo } from "react"; import useSWR from "swr"; -import { useProxiesData } from "@/hooks/app-data"; +import { useProxiesData } from "@/hooks/use-clash-data"; import { useVerge } from "@/hooks/use-verge"; import { getRuntimeConfig } from "@/services/cmds"; import delayManager from "@/services/delay"; diff --git a/src/components/rule/provider-button.tsx b/src/components/rule/provider-button.tsx index 875be01b3..59d6f651f 100644 --- a/src/components/rule/provider-button.tsx +++ b/src/components/rule/provider-button.tsx @@ -21,7 +21,10 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { updateRuleProvider } from "tauri-plugin-mihomo-api"; -import type { useRuleProvidersData, useRulesData } from "@/hooks/app-data"; +import type { + useRuleProvidersData, + useRulesData, +} from "@/hooks/use-clash-data"; import { showNotice } from "@/services/noticeService"; // 辅助组件 - 类型框 diff --git a/src/components/setting/mods/sysproxy-viewer.tsx b/src/components/setting/mods/sysproxy-viewer.tsx index 4b7a4bd02..7a3f43b93 100644 --- a/src/components/setting/mods/sysproxy-viewer.tsx +++ b/src/components/setting/mods/sysproxy-viewer.tsx @@ -30,7 +30,7 @@ import { useClashConfig, useSystemProxyAddress, useSystemProxyData, -} from "@/hooks/app-data"; +} from "@/hooks/use-clash-data"; import { useVerge } from "@/hooks/use-verge"; import { getAutotemProxy, diff --git a/src/components/shared/ProxyControlSwitches.tsx b/src/components/shared/ProxyControlSwitches.tsx index 79466dfb6..4eba4bc3f 100644 --- a/src/components/shared/ProxyControlSwitches.tsx +++ b/src/components/shared/ProxyControlSwitches.tsx @@ -16,11 +16,11 @@ import { TooltipIcon } from "@/components/base/base-tooltip-icon"; import { GuardState } from "@/components/setting/mods/guard-state"; import { SysproxyViewer } from "@/components/setting/mods/sysproxy-viewer"; import { TunViewer } from "@/components/setting/mods/tun-viewer"; +import { useServiceInstaller } from "@/hooks/use-service-installer"; +import { useServiceUninstaller } from "@/hooks/use-service-uninstaller"; import { useSystemProxyState } from "@/hooks/use-system-proxy-state"; import { useSystemState } from "@/hooks/use-system-state"; import { useVerge } from "@/hooks/use-verge"; -import { useServiceInstaller } from "@/hooks/useServiceInstaller"; -import { useServiceUninstaller } from "@/hooks/useServiceUninstaller"; import { showNotice } from "@/services/noticeService"; interface ProxySwitchProps { diff --git a/src/hooks/app-data.ts b/src/hooks/use-clash-data.ts similarity index 100% rename from src/hooks/app-data.ts rename to src/hooks/use-clash-data.ts diff --git a/src/hooks/use-cleanup.ts b/src/hooks/use-cleanup.ts deleted file mode 100644 index 3766cec8c..000000000 --- a/src/hooks/use-cleanup.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { useEffect, useRef } from "react"; - -/** - * 资源清理 Hook - * 用于在组件卸载或窗口关闭时统一清理资源 - */ -export const useCleanup = () => { - const cleanupFnsRef = useRef void>>(new Set()); - - const registerCleanup = (fn: () => void) => { - cleanupFnsRef.current.add(fn); - return () => { - cleanupFnsRef.current.delete(fn); - }; - }; - - const cleanup = () => { - cleanupFnsRef.current.forEach((fn) => { - try { - fn(); - } catch (error) { - console.error("[资源清理] 清理失败:", error); - } - }); - cleanupFnsRef.current.clear(); - }; - - useEffect(() => { - return () => { - cleanup(); - }; - }, []); - - return { registerCleanup, cleanup }; -}; diff --git a/src/hooks/use-current-proxy.ts b/src/hooks/use-current-proxy.ts deleted file mode 100644 index e1d1ab1ba..000000000 --- a/src/hooks/use-current-proxy.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { useMemo } from "react"; - -import { useClashConfig, useProxiesData } from "@/hooks/app-data"; - -// 获取当前代理节点信息的自定义Hook -export const useCurrentProxy = () => { - const { proxies, refreshProxy } = useProxiesData(); - const { clashConfig } = useClashConfig(); - - // 获取当前模式 - const currentMode = clashConfig?.mode?.toLowerCase() || "rule"; - - // 获取当前代理节点信息 - const currentProxyInfo = useMemo(() => { - if (!proxies) return { currentProxy: null, primaryGroupName: null }; - - const globalGroup = proxies.global as IProxyGroupItem | undefined; - const groups: IProxyGroupItem[] = Array.isArray(proxies.groups) - ? (proxies.groups as IProxyGroupItem[]) - : []; - const records = (proxies.records || {}) as Record; - - // 默认信息 - let primaryGroupName = "GLOBAL"; - let currentName = globalGroup?.now; - - // 在规则模式下,寻找主要代理组(通常是第一个或者名字包含特定关键词的组) - if (currentMode === "rule" && groups.length > 0) { - // 查找主要的代理组(优先级:包含关键词 > 第一个非GLOBAL组) - const primaryKeywords = [ - "auto", - "select", - "proxy", - "节点选择", - "自动选择", - ]; - const primaryGroup = - groups.find((group) => - primaryKeywords.some((keyword) => - group.name.toLowerCase().includes(keyword.toLowerCase()), - ), - ) || groups.filter((g) => g.name !== "GLOBAL")[0]; - - if (primaryGroup) { - primaryGroupName = primaryGroup.name; - currentName = primaryGroup.now; - } - } - - // 如果找不到当前节点,返回null - if (!currentName) return { currentProxy: null, primaryGroupName }; - - // 获取完整的节点信息 - const currentProxy = records[currentName] || { - name: currentName, - type: "Unknown", - udp: false, - xudp: false, - tfo: false, - mptcp: false, - smux: false, - history: [], - }; - - return { currentProxy, primaryGroupName }; - }, [proxies, currentMode]); - - return { - currentProxy: currentProxyInfo.currentProxy, - primaryGroupName: currentProxyInfo.primaryGroupName, - mode: currentMode, - refreshProxy, - }; -}; diff --git a/src/hooks/useServiceInstaller.ts b/src/hooks/use-service-installer.ts similarity index 100% rename from src/hooks/useServiceInstaller.ts rename to src/hooks/use-service-installer.ts diff --git a/src/hooks/useServiceUninstaller.ts b/src/hooks/use-service-uninstaller.ts similarity index 100% rename from src/hooks/useServiceUninstaller.ts rename to src/hooks/use-service-uninstaller.ts diff --git a/src/hooks/use-system-proxy-state.ts b/src/hooks/use-system-proxy-state.ts index ef25274b1..b6dc94752 100644 --- a/src/hooks/use-system-proxy-state.ts +++ b/src/hooks/use-system-proxy-state.ts @@ -2,7 +2,7 @@ import { useLockFn } from "ahooks"; import useSWR, { mutate } from "swr"; import { closeAllConnections } from "tauri-plugin-mihomo-api"; -import { useSystemProxyData } from "@/hooks/app-data"; +import { useSystemProxyData } from "@/hooks/use-clash-data"; import { useVerge } from "@/hooks/use-verge"; import { getAutotemProxy } from "@/services/cmds"; diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx index 73ff968a4..1d8e5309b 100644 --- a/src/pages/_layout.tsx +++ b/src/pages/_layout.tsx @@ -54,10 +54,12 @@ import { useWindowDecorations } from "@/hooks/use-window"; import { useThemeMode } from "@/services/states"; import getSystem from "@/utils/get-system"; -import { handleNoticeMessage } from "./_layout/notificationHandlers"; -import { useAppInitialization } from "./_layout/useAppInitialization"; -import { useLayoutEvents } from "./_layout/useLayoutEvents"; -import { useLoadingOverlay } from "./_layout/useLoadingOverlay"; +import { + useAppInitialization, + useLayoutEvents, + useLoadingOverlay, +} from "./_layout/hooks"; +import { handleNoticeMessage } from "./_layout/utils"; import { navItems } from "./_routers"; import "dayjs/locale/ru"; diff --git a/src/pages/_layout/hooks/index.ts b/src/pages/_layout/hooks/index.ts new file mode 100644 index 000000000..11de552f2 --- /dev/null +++ b/src/pages/_layout/hooks/index.ts @@ -0,0 +1,3 @@ +export { useAppInitialization } from "./use-app-initialization"; +export { useLayoutEvents } from "./use-layout-events"; +export { useLoadingOverlay } from "./use-loading-overlay"; diff --git a/src/pages/_layout/useAppInitialization.ts b/src/pages/_layout/hooks/use-app-initialization.ts similarity index 81% rename from src/pages/_layout/useAppInitialization.ts rename to src/pages/_layout/hooks/use-app-initialization.ts index 34abf9f7e..d791ecb5b 100644 --- a/src/pages/_layout/useAppInitialization.ts +++ b/src/pages/_layout/hooks/use-app-initialization.ts @@ -1,6 +1,8 @@ import { invoke } from "@tauri-apps/api/core"; import { useEffect, useRef } from "react"; +import { hideInitialOverlay } from "../utils"; + export const useAppInitialization = () => { const initRef = useRef(false); @@ -25,6 +27,8 @@ export const useAppInitialization = () => { }; const notifyBackend = async (stage?: string) => { + if (isCancelled) return; + try { if (stage) { await invoke("update_ui_stage", { stage }); @@ -32,20 +36,16 @@ export const useAppInitialization = () => { await invoke("notify_ui_ready"); } } catch (err) { - console.error(`[初始化] 通知后端失败:`, err); + console.error(`[Initialization] Failed to notify backend:`, err); } }; const removeLoadingOverlay = () => { - const overlay = document.getElementById("initial-loading-overlay"); - if (overlay) { - overlay.style.opacity = "0"; - scheduleTimeout(() => overlay.remove(), 300); - } + hideInitialOverlay({ schedule: scheduleTimeout }); }; const performInitialization = async () => { - if (isInitialized) return; + if (isCancelled || isInitialized) return; isInitialized = true; try { @@ -70,14 +70,18 @@ export const useAppInitialization = () => { await notifyBackend("ResourcesLoaded"); await notifyBackend(); } catch (error) { - console.error("[初始化] 失败:", error); - removeLoadingOverlay(); - notifyBackend().catch(console.error); + if (!isCancelled) { + console.error("[Initialization] Failed:", error); + removeLoadingOverlay(); + notifyBackend().catch(console.error); + } } }; const checkBackendReady = async () => { try { + if (isCancelled) return; + await invoke("update_ui_stage", { stage: "Loading" }); performInitialization(); } catch { @@ -99,7 +103,7 @@ export const useAppInitialization = () => { try { window.clearTimeout(id); } catch (error) { - console.warn("[初始化] 清理定时器失败:", error); + console.warn("[Initialization] Failed to clear timer:", error); } }); timers.clear(); diff --git a/src/pages/_layout/useLayoutEvents.ts b/src/pages/_layout/hooks/use-layout-events.ts similarity index 75% rename from src/pages/_layout/useLayoutEvents.ts rename to src/pages/_layout/hooks/use-layout-events.ts index 0b7955b30..3f71ab34f 100644 --- a/src/pages/_layout/useLayoutEvents.ts +++ b/src/pages/_layout/hooks/use-layout-events.ts @@ -13,6 +13,10 @@ export const useLayoutEvents = ( useEffect(() => { const unlisteners: Array<() => void> = []; let disposed = false; + const revalidateKeys = (keys: readonly string[]) => { + const keySet = new Set(keys); + mutate((key) => typeof key === "string" && keySet.has(key)); + }; const register = ( maybeUnlisten: void | (() => void) | Promise void)>, @@ -33,25 +37,31 @@ export const useLayoutEvents = ( unlisteners.push(unlisten); } }) - .catch((error) => console.error("[事件监听] 注册失败", error)); + .catch((error) => + console.error("[Event Listener] Registration failed:", error), + ); }; register( addListener("verge://refresh-clash-config", async () => { - mutate("getProxies"); - mutate("getVersion"); - mutate("getClashConfig"); - mutate("getProxyProviders"); + revalidateKeys([ + "getProxies", + "getVersion", + "getClashConfig", + "getProxyProviders", + ]); }), ); register( addListener("verge://refresh-verge-config", () => { - mutate("getVergeConfig"); - mutate("getSystemProxy"); - mutate("getAutotemProxy"); - mutate("getRunningMode"); - mutate("isServiceAvailable"); + revalidateKeys([ + "getVergeConfig", + "getSystemProxy", + "getAutotemProxy", + "getRunningMode", + "isServiceAvailable", + ]); }), ); @@ -91,7 +101,7 @@ export const useLayoutEvents = ( if (errors.length > 0) { console.error( - `[事件监听] 清理过程中发生 ${errors.length} 个错误:`, + `[Event Listener] Encountered ${errors.length} errors during cleanup:`, errors, ); } diff --git a/src/pages/_layout/useLoadingOverlay.ts b/src/pages/_layout/hooks/use-loading-overlay.ts similarity index 52% rename from src/pages/_layout/useLoadingOverlay.ts rename to src/pages/_layout/hooks/use-loading-overlay.ts index d26e138db..641baf15b 100644 --- a/src/pages/_layout/useLoadingOverlay.ts +++ b/src/pages/_layout/hooks/use-loading-overlay.ts @@ -1,13 +1,15 @@ import { useEffect, useRef } from "react"; +import { hideInitialOverlay } from "../utils"; + export const useLoadingOverlay = (themeReady: boolean) => { const overlayRemovedRef = useRef(false); useEffect(() => { if (!themeReady || overlayRemovedRef.current) return; - let fadeTimer: number | null = null; - let retryTimer: number | null = null; + let removalTimer: number | undefined; + let retryTimer: number | undefined; let attempts = 0; const maxAttempts = 50; let stopped = false; @@ -15,19 +17,15 @@ export const useLoadingOverlay = (themeReady: boolean) => { const tryRemoveOverlay = () => { if (stopped || overlayRemovedRef.current) return; - const overlay = document.getElementById("initial-loading-overlay"); - if (overlay) { - overlayRemovedRef.current = true; - overlay.style.opacity = "0"; - overlay.style.pointerEvents = "none"; + const { removed, removalTimer: timerId } = hideInitialOverlay({ + assumeMissingAsRemoved: true, + }); + if (typeof timerId === "number") { + removalTimer = timerId; + } - fadeTimer = window.setTimeout(() => { - try { - overlay.remove(); - } catch (error) { - console.warn("[加载遮罩] 移除失败:", error); - } - }, 300); + if (removed) { + overlayRemovedRef.current = true; return; } @@ -35,7 +33,7 @@ export const useLoadingOverlay = (themeReady: boolean) => { attempts += 1; retryTimer = window.setTimeout(tryRemoveOverlay, 100); } else { - console.warn("[加载遮罩] 未找到元素"); + console.warn("[Loading Overlay] Element not found"); } }; @@ -43,8 +41,8 @@ export const useLoadingOverlay = (themeReady: boolean) => { return () => { stopped = true; - if (fadeTimer) window.clearTimeout(fadeTimer); - if (retryTimer) window.clearTimeout(retryTimer); + if (typeof removalTimer === "number") window.clearTimeout(removalTimer); + if (typeof retryTimer === "number") window.clearTimeout(retryTimer); }; }, [themeReady]); }; diff --git a/src/pages/_layout/useLazyDataLoad.ts b/src/pages/_layout/useLazyDataLoad.ts deleted file mode 100644 index 3eb0434cf..000000000 --- a/src/pages/_layout/useLazyDataLoad.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useEffect, useRef } from "react"; - -export const useLazyDataLoad = ( - callbacks: Array<() => void>, - delay: number = 1000, -) => { - const hasLoadedRef = useRef(false); - - useEffect(() => { - if (hasLoadedRef.current) return; - - const timer = window.setTimeout(() => { - hasLoadedRef.current = true; - callbacks.forEach((callback) => { - try { - callback(); - } catch (error) { - console.error("[延迟加载] 执行失败:", error); - } - }); - }, delay); - - return () => window.clearTimeout(timer); - }, [callbacks, delay]); -}; diff --git a/src/pages/_layout/utils/index.ts b/src/pages/_layout/utils/index.ts new file mode 100644 index 000000000..76dde298c --- /dev/null +++ b/src/pages/_layout/utils/index.ts @@ -0,0 +1,2 @@ +export { hideInitialOverlay } from "./initial-loading-overlay"; +export { handleNoticeMessage } from "./notification-handlers"; diff --git a/src/pages/_layout/utils/initial-loading-overlay.ts b/src/pages/_layout/utils/initial-loading-overlay.ts new file mode 100644 index 000000000..e57a14593 --- /dev/null +++ b/src/pages/_layout/utils/initial-loading-overlay.ts @@ -0,0 +1,45 @@ +const OVERLAY_ID = "initial-loading-overlay"; +const REMOVE_DELAY = 300; + +let overlayRemoved = false; + +type HideOverlayOptions = { + schedule?: (handler: () => void, delay: number) => number; + assumeMissingAsRemoved?: boolean; +}; + +type HideOverlayResult = { + removed: boolean; + removalTimer?: number; +}; + +export const hideInitialOverlay = ( + options: HideOverlayOptions = {}, +): HideOverlayResult => { + if (overlayRemoved) { + return { removed: true }; + } + + const overlay = document.getElementById(OVERLAY_ID); + if (!overlay) { + if (options.assumeMissingAsRemoved) { + overlayRemoved = true; + return { removed: true }; + } + return { removed: false }; + } + + overlayRemoved = true; + overlay.dataset.hidden = "true"; + + const schedule = options.schedule ?? window.setTimeout; + const removalTimer = schedule(() => { + try { + overlay.remove(); + } catch (error) { + console.warn("[Loading Overlay] Removal failed:", error); + } + }, REMOVE_DELAY); + + return { removed: true, removalTimer }; +}; diff --git a/src/pages/_layout/notificationHandlers.ts b/src/pages/_layout/utils/notification-handlers.ts similarity index 100% rename from src/pages/_layout/notificationHandlers.ts rename to src/pages/_layout/utils/notification-handlers.ts diff --git a/src/pages/rules.tsx b/src/pages/rules.tsx index 266db15d7..b418e54ad 100644 --- a/src/pages/rules.tsx +++ b/src/pages/rules.tsx @@ -8,7 +8,7 @@ import { BaseSearchBox } from "@/components/base/base-search-box"; import { ScrollTopButton } from "@/components/layout/scroll-top-button"; import { ProviderButton } from "@/components/rule/provider-button"; import RuleItem from "@/components/rule/rule-item"; -import { useRuleProvidersData, useRulesData } from "@/hooks/app-data"; +import { useRuleProvidersData, useRulesData } from "@/hooks/use-clash-data"; import { useVisibility } from "@/hooks/use-visibility"; const RulesPage = () => {