From 9148bdacb5427159e99f3893d5466bd06bfe131e Mon Sep 17 00:00:00 2001 From: cysamurai Date: Wed, 22 Apr 2026 14:01:51 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BA=94=E7=94=A8=E5=A4=9A?= =?UTF-8?q?=E5=BC=80=E7=9A=84=E6=83=85=E5=86=B5=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=A8=A1=E5=9D=97=E7=9A=84=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- broadcast-client/package.json | 2 +- broadcast-client/src-tauri/Cargo.lock | 112 +++++++++++++++++++ broadcast-client/src-tauri/Cargo.toml | 5 +- broadcast-client/src-tauri/src/lib.rs | 106 ++++++++++++++++-- broadcast-client/src-tauri/tauri.conf.json | 2 +- broadcast-client/src/views/ConfigView.vue | 15 ++- call-client/package.json | 2 +- call-client/src-tauri/Cargo.lock | 13 ++- call-client/src-tauri/Cargo.toml | 3 +- call-client/src-tauri/src/commands/update.rs | 60 +++++++++- call-client/src-tauri/src/lib.rs | 49 ++++++++ call-client/src-tauri/tauri.conf.json | 9 +- call-client/src/host/window.ts | 4 +- call-client/src/utils/service.ts | 102 ++++++++++++++++- call-client/src/views/LoginView.vue | 11 ++ 15 files changed, 469 insertions(+), 26 deletions(-) diff --git a/broadcast-client/package.json b/broadcast-client/package.json index ca76e4d..4d26abb 100644 --- a/broadcast-client/package.json +++ b/broadcast-client/package.json @@ -1,7 +1,7 @@ { "name": "broadcast-client", "private": true, - "version": "0.1.0", + "version": "0.1.1", "type": "module", "scripts": { "dev": "vite", diff --git a/broadcast-client/src-tauri/Cargo.lock b/broadcast-client/src-tauri/Cargo.lock index a1309d1..cea5ab5 100644 --- a/broadcast-client/src-tauri/Cargo.lock +++ b/broadcast-client/src-tauri/Cargo.lock @@ -127,6 +127,7 @@ name = "broadcast-client" version = "0.1.0" dependencies = [ "chrono", + "fs2", "serde", "serde_json", "tauri", @@ -753,6 +754,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futf" version = "0.1.5" @@ -1518,6 +1529,8 @@ version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -1794,6 +1807,17 @@ dependencies = [ "objc_exception", ] +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + [[package]] name = "objc_exception" version = "0.1.2" @@ -2328,6 +2352,30 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rfd" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" +dependencies = [ + "block", + "dispatch", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "lazy_static", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.37.0", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -2850,6 +2898,7 @@ dependencies = [ "plist", "rand 0.8.5", "raw-window-handle", + "rfd", "semver", "serde", "serde_json", @@ -3406,6 +3455,16 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.117" @@ -3472,6 +3531,16 @@ dependencies = [ "semver", ] +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webkit2gtk" version = "0.18.2" @@ -3588,6 +3657,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + [[package]] name = "windows" version = "0.39.0" @@ -3750,6 +3832,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + [[package]] name = "windows_aarch64_msvc" version = "0.39.0" @@ -3762,6 +3850,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + [[package]] name = "windows_i686_gnu" version = "0.39.0" @@ -3774,6 +3868,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + [[package]] name = "windows_i686_msvc" version = "0.39.0" @@ -3786,6 +3886,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + [[package]] name = "windows_x86_64_gnu" version = "0.39.0" @@ -3804,6 +3910,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + [[package]] name = "windows_x86_64_msvc" version = "0.39.0" diff --git a/broadcast-client/src-tauri/Cargo.toml b/broadcast-client/src-tauri/Cargo.toml index b5b49ad..71d2de8 100644 --- a/broadcast-client/src-tauri/Cargo.toml +++ b/broadcast-client/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "broadcast-client" -version = "0.1.0" +version = "0.1.1" description = "Broadcast Ruler Client" authors = ["team"] edition = "2021" @@ -17,7 +17,8 @@ default = ["custom-protocol"] custom-protocol = ["tauri/custom-protocol"] [dependencies] -tauri = { version = "1", features = ["window-all"] } +tauri = { version = "1", features = ["window-all", "dialog"] } serde = { version = "1", features = ["derive"] } serde_json = "1" chrono = { version = "0.4", features = ["clock"] } +fs2 = "0.4" diff --git a/broadcast-client/src-tauri/src/lib.rs b/broadcast-client/src-tauri/src/lib.rs index 5c73837..27c279c 100644 --- a/broadcast-client/src-tauri/src/lib.rs +++ b/broadcast-client/src-tauri/src/lib.rs @@ -1,5 +1,6 @@ use std::{ - fs::{create_dir_all, read_dir, remove_file, OpenOptions}, + fs::{create_dir_all, read_dir, remove_file, File, OpenOptions}, + io, io::Read, io::Write, net::TcpListener, @@ -13,6 +14,7 @@ use std::{ }; use chrono::Local; +use fs2::FileExt; use serde::{Deserialize, Serialize}; use tauri::Manager; #[cfg(target_os = "linux")] @@ -26,6 +28,30 @@ const LOG_FILE_EXT: &str = ".log"; const LOG_FILE_MAX_BYTES: u64 = 5 * 1024 * 1024; const LOG_RETENTION_DAYS: u64 = 7; +#[allow(dead_code)] +struct SingleInstanceLock(File); + +fn acquire_single_instance_lock(app: &tauri::App) -> Result { + let base_dir = app.path_resolver().app_data_dir().ok_or("无法获取应用数据目录")?; + create_dir_all(&base_dir).map_err(|err| format!("创建锁目录失败: {err}"))?; + + let lock_path = base_dir.join("single-instance.lock"); + let lock_file = OpenOptions::new() + .create(true) + .read(true) + .write(true) + .open(lock_path) + .map_err(|err| format!("打开锁文件失败: {err}"))?; + + match lock_file.try_lock_exclusive() { + Ok(()) => Ok(SingleInstanceLock(lock_file)), + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + Err("duplicate instance".to_string()) + } + Err(err) => Err(format!("加锁失败: {err}")), + } +} + #[derive(Default)] struct SocketServiceState { runtime: Mutex>, @@ -98,6 +124,22 @@ struct AptUpdateCheckResult { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() + .setup(|app| match acquire_single_instance_lock(app) { + Ok(lock) => { + app.manage(lock); + Ok(()) + } + Err(reason) if reason == "duplicate instance" => { + tauri::api::dialog::blocking::message( + None::<&tauri::Window>, + "提示", + "请勿重复打开", + ); + app.handle().exit(0); + Ok(()) + } + Err(reason) => Err(reason.into()), + }) .manage(SocketServiceState::default()) .invoke_handler(tauri::generate_handler![ start_socket_service, @@ -245,10 +287,31 @@ fn extract_policy_value(output: &str, key: &str) -> String { } #[tauri::command] -fn check_apt_update(package_name: String, current_version: String) -> Result { +fn check_apt_update( + app: tauri::AppHandle, + package_name: String, + current_version: String, +) -> Result { + append_socket_log( + &app, + "INFO", + format!( + "检查更新开始 package={} current_version={}", + package_name, current_version + ), + ); + #[cfg(not(target_os = "linux"))] { let _ = (&package_name, ¤t_version); + append_socket_log( + &app, + "WARN", + format!( + "检查更新失败 package={} reason=当前系统不支持 apt 更新检测,仅支持 Linux", + package_name + ), + ); return Err("当前系统不支持 apt 更新检测,仅支持 Linux。".to_string()); } @@ -258,15 +321,29 @@ fn check_apt_update(package_name: String, current_version: String) -> Result Result Result("check_apt_update", { packageName: "broadcast-client", - currentVersion: "0.1.0", + currentVersion: appVersion.value, }); + console.info("[update] 检查更新结果", result); if (!result.sourceAvailable) { + console.warn("[update] 未检测到可用更新源", { packageName: result.packageName }); ElMessage.warning( `未检测到可用更新源,请先执行:${APT_SOURCE_SETUP_COMMAND}`, ); @@ -574,14 +580,21 @@ async function handleCheckUpdate() { } if (!result.hasUpdate) { + console.info("[update] 当前已是最新版本", { installedVersion: result.installedVersion }); ElMessage.success(`当前已是最新版本(${result.installedVersion})`); return; } + console.info("[update] 检测到新版本", { + installedVersion: result.installedVersion, + candidateVersion: result.candidateVersion, + updateCommand: result.updateCommand, + }); ElMessage.warning( `发现新版本 ${result.candidateVersion},请在终端执行:${result.updateCommand}`, ); } catch (error) { + console.error("[update] 检查更新失败", error); ElMessage.error(`检查更新失败:${String(error)}`); } finally { checkingUpdate.value = false; diff --git a/call-client/package.json b/call-client/package.json index 221e965..c44da7f 100644 --- a/call-client/package.json +++ b/call-client/package.json @@ -1,7 +1,7 @@ { "name": "call-client", "private": true, - "version": "0.1.0", + "version": "0.1.1", "type": "module", "scripts": { "dev": "vite", diff --git a/call-client/src-tauri/Cargo.lock b/call-client/src-tauri/Cargo.lock index 4d24930..1b1441f 100644 --- a/call-client/src-tauri/Cargo.lock +++ b/call-client/src-tauri/Cargo.lock @@ -394,6 +394,7 @@ dependencies = [ name = "call-client" version = "0.1.0" dependencies = [ + "fs2", "serde", "serde_json", "tauri", @@ -1075,6 +1076,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futf" version = "0.1.5" @@ -2670,7 +2681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/call-client/src-tauri/Cargo.toml b/call-client/src-tauri/Cargo.toml index af3a803..83afcdc 100644 --- a/call-client/src-tauri/Cargo.toml +++ b/call-client/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "call-client" -version = "0.1.0" +version = "0.1.1" description = "A Tauri App" authors = ["you"] edition = "2021" @@ -25,4 +25,5 @@ custom-protocol = ["tauri/custom-protocol"] tauri = { version = "1", features = ["api-all"] } serde = { version = "1", features = ["derive"] } serde_json = "1" +fs2 = "0.4" diff --git a/call-client/src-tauri/src/commands/update.rs b/call-client/src-tauri/src/commands/update.rs index d8a7e86..9818f00 100644 --- a/call-client/src-tauri/src/commands/update.rs +++ b/call-client/src-tauri/src/commands/update.rs @@ -1,6 +1,9 @@ use serde::Serialize; #[cfg(target_os = "linux")] use std::process::Command; +use tauri::AppHandle; + +use super::logger::app_log; #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] @@ -24,10 +27,29 @@ fn extract_policy_value(output: &str, key: &str) -> String { } #[tauri::command] -pub fn check_apt_update(package_name: String, current_version: String) -> Result { +pub fn check_apt_update( + _app: AppHandle, + package_name: String, + current_version: String, +) -> Result { + let _ = app_log( + "info".to_string(), + format!( + "检查更新开始: package={}, current_version={}", + package_name, current_version + ), + ); + #[cfg(not(target_os = "linux"))] { let _ = (&package_name, ¤t_version); + let _ = app_log( + "warn".to_string(), + format!( + "检查更新失败: package={}, reason=当前系统不支持 apt 更新检测,仅支持 Linux", + package_name + ), + ); return Err("当前系统不支持 apt 更新检测,仅支持 Linux。".to_string()); } @@ -37,15 +59,27 @@ pub fn check_apt_update(package_name: String, current_version: String) -> Result .arg("policy") .arg(&package_name) .output() - .map_err(|error| format!("执行 apt-cache 失败: {error}"))?; + .map_err(|error| { + let message = format!("执行 apt-cache 失败: {error}"); + let _ = app_log( + "error".to_string(), + format!("检查更新失败: package={}, {}", package_name, message), + ); + message + })?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); - return Err(if stderr.is_empty() { + let message = if stderr.is_empty() { "apt-cache policy 执行失败".to_string() } else { format!("apt-cache policy 执行失败: {stderr}") - }); + }; + let _ = app_log( + "error".to_string(), + format!("检查更新失败: package={}, {}", package_name, message), + ); + return Err(message); } let stdout = String::from_utf8_lossy(&output.stdout).to_string(); @@ -65,7 +99,7 @@ pub fn check_apt_update(package_name: String, current_version: String) -> Result package_name ); - Ok(AptUpdateCheckResult { + let result = AptUpdateCheckResult { package_name, current_version, installed_version, @@ -73,6 +107,20 @@ pub fn check_apt_update(package_name: String, current_version: String) -> Result has_update, source_available, update_command, - }) + }; + + let _ = app_log( + "info".to_string(), + format!( + "检查更新完成: package={}, installed_version={}, candidate_version={}, has_update={}, source_available={}", + result.package_name, + result.installed_version, + result.candidate_version, + result.has_update, + result.source_available + ), + ); + + Ok(result) } } diff --git a/call-client/src-tauri/src/lib.rs b/call-client/src-tauri/src/lib.rs index ac17612..c9654b4 100644 --- a/call-client/src-tauri/src/lib.rs +++ b/call-client/src-tauri/src/lib.rs @@ -1,6 +1,11 @@ mod commands; mod state; +use std::{ + fs::{create_dir_all, File, OpenOptions}, + io, +}; + use commands::{ config::{config_get_all, config_merge}, events::{emit_to_window, list_windows}, @@ -12,13 +17,57 @@ use commands::{ open_ticket_window, quit_app, }, }; +use fs2::FileExt; use state::AppState; +use tauri::Manager; + +#[allow(dead_code)] +struct SingleInstanceLock(File); + +fn acquire_single_instance_lock(app: &tauri::App) -> Result { + let base_dir = app.path_resolver().app_data_dir().ok_or("无法获取应用数据目录")?; + create_dir_all(&base_dir).map_err(|err| format!("创建锁目录失败: {err}"))?; + + let lock_path = base_dir.join("single-instance.lock"); + let lock_file = OpenOptions::new() + .create(true) + .read(true) + .write(true) + .open(lock_path) + .map_err(|err| format!("打开锁文件失败: {err}"))?; + + match lock_file.try_lock_exclusive() { + Ok(()) => Ok(SingleInstanceLock(lock_file)), + Err(err) if err.kind() == io::ErrorKind::WouldBlock => { + Err("duplicate instance".to_string()) + } + Err(err) => Err(format!("加锁失败: {err}")), + } +} #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .manage(AppState::default()) .setup(|app| { + match acquire_single_instance_lock(app) { + Ok(lock) => { + app.manage(lock); + } + Err(reason) if reason == "duplicate instance" => { + tauri::api::dialog::blocking::message( + None::<&tauri::Window>, + "提示", + "请勿重复打开", + ); + app.handle().exit(0); + return Ok(()); + } + Err(reason) => { + return Err(reason.into()); + } + } + ensure_main_window(app.handle())?; Ok(()) }) diff --git a/call-client/src-tauri/tauri.conf.json b/call-client/src-tauri/tauri.conf.json index 4ee0e83..c5d8fb0 100644 --- a/call-client/src-tauri/tauri.conf.json +++ b/call-client/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "../node_modules/@tauri-apps/cli/schema.json", "package": { "productName": "call-client", - "version": "0.1.0" + "version": "0.1.1" }, "build": { "beforeDevCommand": "npm run dev", @@ -12,7 +12,12 @@ }, "tauri": { "allowlist": { - "all": true + "all": true, + "http": { + "all": true, + "request": true, + "scope": ["http://*/*", "https://*/*"] + } }, "windows": [ { diff --git a/call-client/src/host/window.ts b/call-client/src/host/window.ts index 5c66952..ec3cf0e 100644 --- a/call-client/src/host/window.ts +++ b/call-client/src/host/window.ts @@ -13,11 +13,11 @@ export async function minimizeWindow(): Promise { } /** - * 关闭当前窗口。 + * 关闭应用进程(避免隐藏窗口残留导致进程驻留)。 */ export async function closeWindow(): Promise { try { - await appWindow.close(); + await invoke("quit_app"); } catch (error) { throw new Error(`关闭窗口失败: ${String(error)}`); } diff --git a/call-client/src/utils/service.ts b/call-client/src/utils/service.ts index 7286877..9249952 100644 --- a/call-client/src/utils/service.ts +++ b/call-client/src/utils/service.ts @@ -1,4 +1,10 @@ -import axios, { type AxiosRequestConfig } from "axios"; +import axios, { + AxiosError, + type AxiosRequestConfig, + type AxiosResponse, + type InternalAxiosRequestConfig, +} from "axios"; +import { Body, fetch as tauriFetch, ResponseType } from "@tauri-apps/api/http"; import { showErrorNative } from "../host/dialog"; import { getSession, setSession } from "../host/session"; import type { SessionState } from "../host/types"; @@ -27,6 +33,96 @@ type RetryableAxiosRequestConfig = AxiosRequestConfig & { let refreshTokenPromise: Promise | null = null; +function shouldUseTauriHttpTransport(): boolean { + return !import.meta.env.DEV; +} + +function buildRequestUrl(config: AxiosRequestConfig): string { + const rawUrl = config.url ?? ""; + const baseURL = config.baseURL ?? ""; + const isAbsoluteUrl = /^https?:\/\//i.test(rawUrl); + const finalUrl = isAbsoluteUrl + ? rawUrl + : `${baseURL.replace(/\/$/, "")}/${rawUrl.replace(/^\//, "")}`; + + if (!config.params) { + return finalUrl; + } + + const searchParams = new URLSearchParams(); + const params = config.params as Record; + Object.entries(params).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; + } + if (Array.isArray(value)) { + value.forEach((item) => searchParams.append(key, String(item))); + return; + } + searchParams.append(key, String(value)); + }); + + const query = searchParams.toString(); + if (!query) { + return finalUrl; + } + const connector = finalUrl.includes("?") ? "&" : "?"; + return `${finalUrl}${connector}${query}`; +} + +function buildTauriBody(config: AxiosRequestConfig): Body | undefined { + const method = (config.method ?? "GET").toUpperCase(); + if (method === "GET" || method === "HEAD" || config.data === undefined) { + return undefined; + } + + if (typeof config.data === "string") { + return Body.text(config.data); + } + return Body.json(config.data); +} + +/** + * 在打包环境中使用 Tauri 原生 HTTP 传输,规避 WebView 的跨域/混合内容限制。 + */ +async function tauriAxiosAdapter( + config: InternalAxiosRequestConfig, +): Promise { + try { + const url = buildRequestUrl(config); + const method = (config.method ?? "GET").toUpperCase() as + | "GET" + | "POST" + | "PUT" + | "DELETE" + | "PATCH" + | "HEAD"; + const headers = (config.headers ?? {}) as Record; + const response = await tauriFetch(url, { + method, + timeout: config.timeout, + headers, + body: buildTauriBody(config), + responseType: ResponseType.JSON, + }); + + return { + data: response.data, + status: response.status, + statusText: String(response.status), + headers: response.headers as Record, + config, + request: null, + }; + } catch (error) { + throw new AxiosError( + error instanceof Error ? error.message : String(error), + "ERR_NETWORK", + config, + ); + } +} + /** * 根据配置中的服务器地址拼出后端 baseURL。 */ @@ -57,6 +153,10 @@ const instance = axios.create({ }, }); +if (shouldUseTauriHttpTransport()) { + instance.defaults.adapter = tauriAxiosAdapter; +} + function isApiSuccessCode(code: unknown): boolean { return code === 200 || code === 0; } diff --git a/call-client/src/views/LoginView.vue b/call-client/src/views/LoginView.vue index 3f19c6b..5b03385 100644 --- a/call-client/src/views/LoginView.vue +++ b/call-client/src/views/LoginView.vue @@ -258,12 +258,18 @@ async function handleCheckUpdate(): Promise { checkingUpdate.value = true; try { + await log("info", `检查更新开始: package=call-client, currentVersion=${appVersion.value}`); const result = await invoke("check_apt_update", { packageName: "call-client", currentVersion: appVersion.value, }); + await log( + "info", + `检查更新结果: package=${result.packageName}, installed=${result.installedVersion}, candidate=${result.candidateVersion}, hasUpdate=${result.hasUpdate}, sourceAvailable=${result.sourceAvailable}`, + ); if (!result.sourceAvailable) { + await log("warn", `检查更新提示: 未检测到可用更新源, package=${result.packageName}`); try { await ElMessageBox.confirm( [ @@ -291,10 +297,15 @@ async function handleCheckUpdate(): Promise { } if (!result.hasUpdate) { + await log("info", `检查更新提示: 当前已是最新版本, version=${result.installedVersion}`); showMessage("success", `当前已是最新版本(${result.installedVersion})`); return; } + await log( + "info", + `检查更新提示: 检测到新版本, installed=${result.installedVersion}, candidate=${result.candidateVersion}`, + ); try { await ElMessageBox.confirm( [