feat: 添加开机自启支持与开发版本提示

This commit is contained in:
Yuhang Wu
2026-10-22 16:36:11 +08:00
parent fcab06d28f
commit ffd0ef7935
10 changed files with 77 additions and 13 deletions
+4
View File
@@ -195,6 +195,10 @@ pnpm dev
### 常用脚本
开机自启需要使用 `pnpm tauri build` 或 `pnpm tauri build --no-bundle` 生成的正式程序。正式程序内置前端资源,不依赖 Vite,Windows 下也不会弹出调试控制台。开发模式和 debug 构建不支持开启自启。
自启路径由插件动态获取当前运行的正式程序路径,不写死盘符、安装目录或项目构建目录。正式程序启动时,若已开启自启,会自动更新已有启动项的路径。因此重新安装或移动程序后,只需从新位置打开一次正式程序,即可校正旧路径;已关闭的自启不会被自动开启。
| 命令 | 说明 |
| ------------------ | ----------------------------------------- |
| `pnpm dev` | 启动 Vite 开发服务器(端口 `1420`) |
+15
View File
@@ -4,6 +4,21 @@
---
## v1.3.2(2026-10-22)
### 🐛 修复
- **开机自启路径动态校正**:自启项动态获取当前正式程序的 EXE 路径,不写死盘符、安装目录或项目构建目录。已开启自启时,正式程序启动后会自动更新已有启动项;重新安装或移动程序后,从新位置打开一次即可校正旧路径,已关闭的自启保持关闭。
- **阻止开发版本注册自启**:开发模式及 debug 构建不再注册自启插件,避免将依赖 Vite 本地服务的开发程序写入启动项,导致重启后主界面显示 `ERR_CONNECTION_REFUSED`,并弹出调试控制台窗口。
### 🎨 优化
- **自启设置提示**:开发版本的自启开关自动置灰,并明确提示需要在正式构建中开启;同步补充简体中文与英文文案。
### 📝 文档
- README 补充正式构建的自启使用说明,以及重新安装或移动程序后的路径校正方式。
## v1.3.0(2026-10-20)
### ✨ 新增功能
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "frpcpilot",
"private": true,
"version": "1.3.0",
"version": "1.3.2",
"type": "module",
"scripts": {
"dev": "vite",
+6
View File
@@ -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]
+28 -6
View File
@@ -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
+6
View File
@@ -1,4 +1,10 @@
import { isTauri } from "@/utils/window"
import { invoke } from "@tauri-apps/api/core"
export async function isAutostartSupported(): Promise<boolean> {
if (!isTauri()) return false
return invoke<boolean>("is_autostart_supported")
}
/**
* 开机自启封装(Tauri 官方 autostart 插件)。
+1
View File
@@ -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}",
+1
View File
@@ -88,6 +88,7 @@
"autostart": "开机自启",
"autostartTip": "登录系统后自动启动 FrpcPilot,开关状态直接写入系统启动项",
"autostartUnsupported": "当前环境无法读写系统启动项,暂不支持该设置",
"autostartRequiresRelease": "请在正式构建的应用中开启自启;开发版本依赖本地服务,无法在重启后独立运行",
"autostartEnabled": "已开启开机自启",
"autostartDisabled": "已关闭开机自启",
"autostartFailed": "设置开机自启失败:{message}",
+10 -3
View File
@@ -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 }
})
+3 -1
View File
@@ -81,7 +81,9 @@ const languageRadios = languageOptions.map((item) => ({ label: item.label, value
/** 无法访问系统启动项时(非桌面环境)给出说明,避免开关无故置灰 */
const autostartTip = computed(() =>
autostartStore.supported
autostartStore.requiresRelease
? t("settings.general.autostartRequiresRelease")
: autostartStore.supported
? t("settings.general.autostartTip")
: t("settings.general.autostartUnsupported"),
)