diff --git a/src-tauri/src/config/config.rs b/src-tauri/src/config/config.rs index edc85faa..b1f88b21 100644 --- a/src-tauri/src/config/config.rs +++ b/src-tauri/src/config/config.rs @@ -3,10 +3,12 @@ use crate::{ config::PrfItem, enhance, utils::{dirs, help}, + core::{handle, CoreManager}, }; use anyhow::{anyhow, Result}; use once_cell::sync::OnceCell; use std::path::PathBuf; +use tokio::time::{sleep, Duration}; pub const RUNTIME_CONFIG: &str = "clash-verge.yaml"; pub const CHECK_CONFIG: &str = "clash-verge-check.yaml"; @@ -64,12 +66,69 @@ impl Config { let script_item = PrfItem::from_script(Some("Script".to_string()))?; Self::profiles().data().append_item(script_item.clone())?; } - crate::log_err!(Self::generate().await); - if let Err(err) = Self::generate_file(ConfigType::Run) { - log::error!(target: "app", "{err}"); - let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG); - // 如果不存在就将默认的clash文件拿过来 + // 生成运行时配置 + crate::log_err!(Self::generate().await); + + // 生成运行时配置文件并验证 + let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG); + let config_result = Self::generate_file(ConfigType::Run); + + let validation_result = if let Ok(_) = config_result { + // 验证配置文件 + println!("[首次启动] 开始验证配置文件"); + + match CoreManager::global().validate_config().await { + Ok((is_valid, error_msg)) => { + if !is_valid { + println!("[首次启动] 配置验证失败,使用默认配置启动 {}", error_msg); + // 使用默认配置 + *Config::runtime().draft() = IRuntime { + config: Some(Config::clash().latest().0.clone()), + exists_keys: vec![], + chain_logs: Default::default(), + }; + help::save_yaml( + &runtime_path, + &Config::clash().latest().0, + Some("# Clash Verge Runtime"), + )?; + + if error_msg.is_empty() { + Some(("config_validate::boot_error", String::new())) + } else { + Some(("config_validate::stderr_error", error_msg)) + } + } else { + println!("[首次启动] 配置验证成功"); + Some(("config_validate::success", String::new())) + } + } + Err(err) => { + println!("[首次启动] 验证进程执行失败 {}", err); + // 使用默认配置 + *Config::runtime().draft() = IRuntime { + config: Some(Config::clash().latest().0.clone()), + exists_keys: vec![], + chain_logs: Default::default(), + }; + help::save_yaml( + &runtime_path, + &Config::clash().latest().0, + Some("# Clash Verge Runtime"), + )?; + + Some(("config_validate::process_terminated", String::new())) + } + } + } else { + println!("[首次启动] 生成配置文件失败,使用默认配置"); + // 如果生成失败就将默认的clash文件拿过来 + *Config::runtime().draft() = IRuntime { + config: Some(Config::clash().latest().0.clone()), + exists_keys: vec![], + chain_logs: Default::default(), + }; if !runtime_path.exists() { help::save_yaml( &runtime_path, @@ -77,7 +136,17 @@ impl Config { Some("# Clash Verge Runtime"), )?; } + Some(("config_validate::error", String::new())) + }; + + // 在单独的任务中发送通知 + if let Some((msg_type, msg_content)) = validation_result { + tauri::async_runtime::spawn(async move { + sleep(Duration::from_secs(2)).await; + handle::Handle::notice_message(msg_type, &msg_content); + }); } + Ok(()) } diff --git a/src/components/base/base-notice.tsx b/src/components/base/base-notice.tsx index 8e12e251..59d33729 100644 --- a/src/components/base/base-notice.tsx +++ b/src/components/base/base-notice.tsx @@ -18,7 +18,7 @@ interface InnerProps { } const NoticeInner = (props: InnerProps) => { - const { type, message, duration = 1500, onClose } = props; + const { type, message, duration, onClose } = props; const [visible, setVisible] = useState(true); const [isDark, setIsDark] = useState(false); const { verge } = useVerge(); @@ -72,7 +72,7 @@ const NoticeInner = (props: InnerProps) => { { return; } - // 直接调用,不使用 setTimeout Notice({ type, message, - duration: duration || 1500, // 确保有默认值 + // 错误类型通知显示 8 秒,其他类型默认 1.5 秒 + duration: type === "error" ? 8000 : duration || 1500, }); }; }); diff --git a/src/locales/zh.json b/src/locales/zh.json index 11b012fc..c71e140e 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -436,6 +436,7 @@ "Enable Tray Speed": "启用托盘速率", "Lite Mode": "轻量模式", "Lite Mode Info": "关闭GUI界面,仅保留内核运行", - "Config Validation Failed": "订阅配置校验失败,请检查配置文件", + "Config Validation Failed": "订阅配置校验失败,请检查订阅配置文件,修改已回滚", + "Boot Config Validation Failed": "启动订阅配置校验失败,使用默认配置启动,请检查订阅配置文件", "Config Validation Process Terminated": "验证进程被终止" } diff --git a/src/pages/_layout.tsx b/src/pages/_layout.tsx index ece3aeeb..99a08a50 100644 --- a/src/pages/_layout.tsx +++ b/src/pages/_layout.tsx @@ -2,7 +2,7 @@ import dayjs from "dayjs"; import i18next from "i18next"; import relativeTime from "dayjs/plugin/relativeTime"; import { SWRConfig, mutate } from "swr"; -import { useEffect } from "react"; +import { useEffect, useCallback } from "react"; import { useTranslation } from "react-i18next"; import { useLocation, useRoutes, useNavigate } from "react-router-dom"; import { List, Paper, ThemeProvider, SvgIcon } from "@mui/material"; @@ -36,91 +36,116 @@ dayjs.extend(relativeTime); const OS = getSystem(); +// 通知处理函数 +const handleNoticeMessage = ( + status: string, + msg: string, + t: (key: string) => string, + navigate: (path: string, options?: any) => void, +) => { + console.log("[通知监听] 收到消息:", status, msg); + + switch (status) { + case "import_sub_url::ok": + navigate("/profile", { state: { current: msg } }); + Notice.success(t("Import Subscription Successful")); + break; + case "import_sub_url::error": + navigate("/profile"); + Notice.error(msg); + break; + case "set_config::error": + Notice.error(msg); + break; + case "config_validate::boot_error": + Notice.error(t("Boot Config Validation Failed")); + break; + case "config_validate::error": + Notice.error(t("Config Validation Failed")); + break; + case "config_validate::process_terminated": + Notice.error(t("Config Validation Process Terminated")); + break; + case "config_validate::stderr_error": + Notice.error(msg); + break; + } +}; + const Layout = () => { const mode = useThemeMode(); const isDark = mode === "light" ? false : true; const { t } = useTranslation(); const { theme } = useCustomTheme(); - const { verge } = useVerge(); - const { language, start_page } = verge || {}; + const { language, start_page } = verge ?? {}; const navigate = useNavigate(); const location = useLocation(); const routersEles = useRoutes(routers); const { addListener, setupCloseListener } = useListen(); - if (!routersEles) return null; - setupCloseListener(); + const handleNotice = useCallback( + (payload: [string, string]) => { + const [status, msg] = payload; + handleNoticeMessage(status, msg, t, navigate); + }, + [t, navigate], + ); + // 设置监听器 useEffect(() => { - addListener("verge://refresh-clash-config", async () => { - // the clash info may be updated - await getAxios(true); - mutate("getProxies"); - mutate("getVersion"); - mutate("getClashConfig"); - mutate("getProxyProviders"); - }); + const listeners = [ + // 配置更新监听 + addListener("verge://refresh-clash-config", async () => { + await getAxios(true); + mutate("getProxies"); + mutate("getVersion"); + mutate("getClashConfig"); + mutate("getProxyProviders"); + }), - // update the verge config - addListener("verge://refresh-verge-config", () => mutate("getVergeConfig")); + // verge 配置更新监听 + addListener("verge://refresh-verge-config", () => + mutate("getVergeConfig"), + ), - // 设置提示监听 - addListener("verge://notice-message", ({ payload }) => { - const [status, msg] = payload as [string, string]; - switch (status) { - case "import_sub_url::ok": - navigate("/profile", { state: { current: msg } }); + // 通知消息监听 + addListener("verge://notice-message", ({ payload }) => + handleNotice(payload as [string, string]), + ), + ]; - Notice.success(t("Import Subscription Successful")); - break; - case "import_sub_url::error": - navigate("/profile"); - Notice.error(msg); - break; - case "set_config::error": - Notice.error(msg); - break; - case "config_validate::error": - Notice.error(t("Config Validation Failed")); - break; - case "config_validate::process_terminated": - Notice.error(t("Config Validation Process Terminated")); - break; - case "config_validate::stderr_error": - Notice.error(msg); - break; - default: - break; - } - }); - - setTimeout(async () => { - portableFlag = await getPortableFlag(); - await appWindow.unminimize(); - await appWindow.show(); - await appWindow.setFocus(); - }, 50); - - // 监听窗口显示/隐藏事件 - const setupListeners = async () => { - const unlisten1 = await listen("verge://hide-window", () => { - appWindow.hide(); - }); - - const unlisten2 = await listen("verge://show-window", () => { - appWindow.show(); - }); + // 设置窗口显示/隐藏监听 + const setupWindowListeners = async () => { + const [hideUnlisten, showUnlisten] = await Promise.all([ + listen("verge://hide-window", () => appWindow.hide()), + listen("verge://show-window", () => appWindow.show()), + ]); return () => { - unlisten1(); - unlisten2(); + hideUnlisten(); + showUnlisten(); }; }; - setupListeners(); - }, []); + // 初始化 + setupCloseListener(); + const cleanupWindow = setupWindowListeners(); + // 清理函数 + return () => { + // 清理主要监听器 + listeners.forEach((listener) => { + if (typeof listener.then === "function") { + listener.then((unlisten) => unlisten()); + } + }); + // 清理窗口监听器 + cleanupWindow.then((cleanup) => cleanup()); + }; + }, [handleNotice]); + + // 语言和起始页设置 useEffect(() => { if (language) { dayjs.locale(language === "zh" ? "zh-cn" : language); @@ -129,7 +154,9 @@ const Layout = () => { if (start_page) { navigate(start_page); } - }, [language, start_page]); + }, [language, start_page, navigate]); + + if (!routersEles) return null; return ( @@ -139,23 +166,18 @@ const Layout = () => { elevation={0} className={`${OS} layout`} onContextMenu={(e) => { - // only prevent it on Windows - const validList = ["input", "textarea"]; - const target = e.currentTarget; if ( OS === "windows" && - !( - validList.includes(target.tagName.toLowerCase()) || - target.isContentEditable - ) + !["input", "textarea"].includes( + e.currentTarget.tagName.toLowerCase(), + ) && + !e.currentTarget.isContentEditable ) { e.preventDefault(); } }} sx={[ - ({ palette }) => ({ - bgcolor: palette.background.paper, - }), + ({ palette }) => ({ bgcolor: palette.background.paper }), { borderRadius: "8px", border: "2px solid var(--divider-color)", @@ -186,7 +208,7 @@ const Layout = () => { /> - {} + @@ -207,16 +229,14 @@ const Layout = () => {
- { -
-
- {OS !== "macos" && } -
- } +
+
+ {OS !== "macos" && } +