refactor: reorganize component and hook structure; remove unused modules

This commit is contained in:
Slinetrac
2025-12-03 21:21:31 +08:00
parent 4f8ed63cf0
commit 3cf51de850
30 changed files with 130 additions and 226 deletions

View File

@@ -1,12 +0,0 @@
import Layout from "./pages/_layout";
import { AppDataProvider } from "./providers/app-data-provider";
function App() {
return (
<AppDataProvider>
<Layout />
</AppDataProvider>
);
}
export default App;

View File

@@ -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<CenterProps> = ({ children, ...props }) => {
return (
<Box
display="flex"
justifyContent="center"
alignItems="center"
width="100%"
height="100%"
{...props}
>
{children}
</Box>
);
};

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";
// 辅助组件 - 类型框

View File

@@ -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,

View File

@@ -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 {

View File

@@ -1,35 +0,0 @@
import { useEffect, useRef } from "react";
/**
* 资源清理 Hook
* 用于在组件卸载或窗口关闭时统一清理资源
*/
export const useCleanup = () => {
const cleanupFnsRef = useRef<Set<() => 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 };
};

View File

@@ -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<string, IProxyItem>;
// 默认信息
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,
};
};

View File

@@ -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";

View File

@@ -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";

View File

@@ -0,0 +1,3 @@
export { useAppInitialization } from "./use-app-initialization";
export { useLayoutEvents } from "./use-layout-events";
export { useLoadingOverlay } from "./use-loading-overlay";

View File

@@ -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();

View File

@@ -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 | (() => 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,
);
}

View File

@@ -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]);
};

View File

@@ -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]);
};

View File

@@ -0,0 +1,2 @@
export { hideInitialOverlay } from "./initial-loading-overlay";
export { handleNoticeMessage } from "./notification-handlers";

View File

@@ -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 };
};

View File

@@ -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 = () => {