diff --git a/README.md b/README.md index 0d9d89e..63ea8b2 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,10 @@ pnpm dev ### 常用脚本 +开机自启需要使用 `pnpm tauri build` 或 `pnpm tauri build --no-bundle` 生成的正式程序。正式程序内置前端资源,不依赖 Vite,Windows 下也不会弹出调试控制台。开发模式和 debug 构建不支持开启自启。 + +自启路径由插件动态获取当前运行的正式程序路径,不写死盘符、安装目录或项目构建目录。正式程序启动时,若已开启自启,会自动更新已有启动项的路径。因此重新安装或移动程序后,只需从新位置打开一次正式程序,即可校正旧路径;已关闭的自启不会被自动开启。 + | 命令 | 说明 | | ------------------ | ----------------------------------------- | | `pnpm dev` | 启动 Vite 开发服务器(端口 `1420`) | diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 35acfa1..1875e31 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,21 @@ --- +## v1.3.2(2026-10-22) + +### 🐛 修复 + +- **开机自启路径动态校正**:自启项动态获取当前正式程序的 EXE 路径,不写死盘符、安装目录或项目构建目录。已开启自启时,正式程序启动后会自动更新已有启动项;重新安装或移动程序后,从新位置打开一次即可校正旧路径,已关闭的自启保持关闭。 +- **阻止开发版本注册自启**:开发模式及 debug 构建不再注册自启插件,避免将依赖 Vite 本地服务的开发程序写入启动项,导致重启后主界面显示 `ERR_CONNECTION_REFUSED`,并弹出调试控制台窗口。 + +### 🎨 优化 + +- **自启设置提示**:开发版本的自启开关自动置灰,并明确提示需要在正式构建中开启;同步补充简体中文与英文文案。 + +### 📝 文档 + +- README 补充正式构建的自启使用说明,以及重新安装或移动程序后的路径校正方式。 + ## v1.3.0(2026-10-20) ### ✨ 新增功能 diff --git a/package.json b/package.json index 5040682..fdeb1d1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frpcpilot", "private": true, - "version": "1.3.0", + "version": "1.3.2", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/src/commands/system.rs b/src-tauri/src/commands/system.rs index a7fc7d9..f3bba0e 100644 --- a/src-tauri/src/commands/system.rs +++ b/src-tauri/src/commands/system.rs @@ -7,6 +7,12 @@ use tauri_helper::auto_collect_command; use crate::models::{ApiResponse, SystemDetails, SystemInfo}; use crate::services::system::SystemStats; +#[tauri::command] +#[auto_collect_command] +pub fn is_autostart_supported() -> bool { + !tauri::is_dev() && !cfg!(debug_assertions) +} + /// 获取系统资源占用(CPU / 内存百分比) #[tauri::command] #[auto_collect_command] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 34c00a6..678c801 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -3,6 +3,7 @@ mod models; mod services; use tauri::{Manager, RunEvent}; +use tauri_plugin_autostart::ManagerExt as _; use crate::commands::frp::{ delete_frp, download_frp, get_frp_downloads, get_frp_releases, import_frp, @@ -12,7 +13,7 @@ use crate::commands::server::{ create_server, delete_server, get_server_list, set_server_enabled, set_server_pinned, update_server, }; -use crate::commands::system::{get_system_details, get_system_info}; +use crate::commands::system::{get_system_details, get_system_info, is_autostart_supported}; use crate::commands::tunnel::{ create_tunnel, delete_tunnel, get_tunnel_list, set_tunnel_enabled, set_tunnel_pinned, update_tunnel, @@ -23,18 +24,39 @@ use crate::services::system::SystemStats; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - let app = tauri::Builder::default() + let mut builder = tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_dialog::init()) // 对话框插件 .plugin(tauri_plugin_updater::Builder::new().build()) // 更新插件 .plugin(tauri_plugin_process::init()) // 进程插件(更新后重启) - // 开机自启插件:读写系统启动项(Windows 注册表 Run 键 / macOS LaunchAgent / Linux XDG autostart) - .plugin(tauri_plugin_autostart::init( + .plugin(tauri_plugin_prevent_default::Builder::new().build()); + + // Development executables require Vite; debug builds also open a Windows console. + if is_autostart_supported() { + builder = builder.plugin(tauri_plugin_autostart::init( tauri_plugin_autostart::MacosLauncher::LaunchAgent, None, - )) - .plugin(tauri_plugin_prevent_default::Builder::new().build()) + )); + } + + let app = builder .setup(|app| { + // The plugin resolves the running executable dynamically. Refresh enabled + // entries after reinstalling or moving the app, preserving disabled state. + if is_autostart_supported() { + let autostart = app.autolaunch(); + let result = autostart.is_enabled().and_then(|enabled| { + if enabled { + autostart.enable() + } else { + Ok(()) + } + }); + if let Err(error) = result { + eprintln!("Failed to refresh autostart executable path: {error}"); + } + } + let pool = tauri::async_runtime::block_on(async { init_pool(app.handle()) .await diff --git a/src/api/autostart.ts b/src/api/autostart.ts index ec6a46e..1ad0ad9 100644 --- a/src/api/autostart.ts +++ b/src/api/autostart.ts @@ -1,4 +1,10 @@ import { isTauri } from "@/utils/window" +import { invoke } from "@tauri-apps/api/core" + +export async function isAutostartSupported(): Promise { + if (!isTauri()) return false + return invoke("is_autostart_supported") +} /** * 开机自启封装(Tauri 官方 autostart 插件)。 diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 6861879..a9adf21 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -88,6 +88,7 @@ "autostart": "Launch at startup", "autostartTip": "Start FrpcPilot automatically after you sign in. The switch writes straight to the system startup entry.", "autostartUnsupported": "System startup entries are not accessible here, so this setting is unavailable.", + "autostartRequiresRelease": "Enable launch at startup in a release build. Development builds require a local server and cannot run independently after a restart.", "autostartEnabled": "Launch at startup enabled", "autostartDisabled": "Launch at startup disabled", "autostartFailed": "Failed to update launch at startup: {message}", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index b721f21..cd9a7e5 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -88,6 +88,7 @@ "autostart": "开机自启", "autostartTip": "登录系统后自动启动 FrpcPilot,开关状态直接写入系统启动项", "autostartUnsupported": "当前环境无法读写系统启动项,暂不支持该设置", + "autostartRequiresRelease": "请在正式构建的应用中开启自启;开发版本依赖本地服务,无法在重启后独立运行", "autostartEnabled": "已开启开机自启", "autostartDisabled": "已关闭开机自启", "autostartFailed": "设置开机自启失败:{message}", diff --git a/src/stores/autostart.ts b/src/stores/autostart.ts index fc2288e..077981b 100644 --- a/src/stores/autostart.ts +++ b/src/stores/autostart.ts @@ -1,7 +1,7 @@ import { defineStore } from "pinia" import { message } from "ant-design-vue" import { isTauri } from "@/utils/window" -import { isAutostartEnabled, setAutostartEnabled } from "@/api/autostart" +import { isAutostartEnabled, isAutostartSupported, setAutostartEnabled } from "@/api/autostart" import { translate } from "@/i18n" /** @@ -17,7 +17,8 @@ export const useAutostartStore = defineStore("autostart", () => { /** 读取 / 写入系统启动项期间显示加载态,同时防止重复操作 */ const loading = ref(isTauri()) /** 当前环境能否读写系统启动项 */ - const supported = ref(isTauri()) + const supported = ref(false) + const requiresRelease = ref(false) function errMsg(e: unknown) { return e instanceof Error ? e.message : String(e) @@ -28,6 +29,12 @@ export const useAutostartStore = defineStore("autostart", () => { if (!isTauri()) return loading.value = true try { + requiresRelease.value = !(await isAutostartSupported()) + if (requiresRelease.value) { + enabled.value = false + supported.value = false + return + } enabled.value = await isAutostartEnabled() supported.value = true } catch { @@ -61,5 +68,5 @@ export const useAutostartStore = defineStore("autostart", () => { } } - return { enabled, loading, supported, refresh, toggle } + return { enabled, loading, supported, requiresRelease, refresh, toggle } }) diff --git a/src/views/setting/General.vue b/src/views/setting/General.vue index 31c03fc..9aeb92b 100644 --- a/src/views/setting/General.vue +++ b/src/views/setting/General.vue @@ -81,9 +81,11 @@ const languageRadios = languageOptions.map((item) => ({ label: item.label, value /** 无法访问系统启动项时(非桌面环境)给出说明,避免开关无故置灰 */ const autostartTip = computed(() => - autostartStore.supported - ? t("settings.general.autostartTip") - : t("settings.general.autostartUnsupported"), + autostartStore.requiresRelease + ? t("settings.general.autostartRequiresRelease") + : autostartStore.supported + ? t("settings.general.autostartTip") + : t("settings.general.autostartUnsupported"), ) /** 切换开机自启:失败时由 store 回滚开关状态并提示原因 */