From 14cf9b7bf562140e1f7f8fa08a9c25581c9bb0f9 Mon Sep 17 00:00:00 2001 From: "fengchuanhn@gmail.com" Date: Thu, 21 May 2026 01:53:54 +0800 Subject: [PATCH] 1 --- src-tauri/Cargo.lock | 36 ++++++++++ src-tauri/Cargo.toml | 2 +- src-tauri/capabilities/default.json | 2 + src-tauri/src/commands/software_info.rs | 89 ++++++++++++++++++++++--- src/components/AppTitlebar.vue | 2 + src/services/windowBranding.js | 14 ++++ 6 files changed, 134 insertions(+), 11 deletions(-) create mode 100644 src/services/windowBranding.js diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 4983a2f..5c88fd2 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -443,6 +443,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.11.1" @@ -1978,6 +1984,19 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png 0.18.1", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -2363,6 +2382,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + [[package]] name = "muda" version = "0.19.1" @@ -3017,6 +3046,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + [[package]] name = "quick-xml" version = "0.39.4" @@ -4070,6 +4105,7 @@ dependencies = [ "heck 0.5.0", "http", "http-range", + "image", "jni", "libc", "log", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index ece4769..efcd197 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -13,7 +13,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = ["protocol-asset"] } +tauri = { version = "2", features = ["protocol-asset", "image-png"] } tauri-plugin-opener = "2" tauri-plugin-dialog = "2" serde = { version = "1", features = ["derive"] } diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 569e9a3..ed3e2cd 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -11,6 +11,8 @@ "core:window:allow-maximize", "core:window:allow-unmaximize", "core:window:allow-is-maximized", + "core:window:allow-set-title", + "core:window:allow-set-icon", "core:event:default", "core:event:allow-listen", "core:event:allow-unlisten", diff --git a/src-tauri/src/commands/software_info.rs b/src-tauri/src/commands/software_info.rs index f322adc..6bcc190 100644 --- a/src-tauri/src/commands/software_info.rs +++ b/src-tauri/src/commands/software_info.rs @@ -1,14 +1,17 @@ -//! 按本地 oem 文件中的 ID 拉取公开品牌信息。 +//! 按本地 oem 文件中的 ID 拉取公开品牌信息,并同步窗口/任务栏标题与图标。 use std::collections::HashMap; use serde::Serialize; use serde_json::Value; +use tauri::{image::Image, WebviewWindow}; use tauri::State; use crate::api_client; use crate::oem_context::OemContext; +const DEFAULT_TITLE: &str = "aiclient"; + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct SoftwareInfo { @@ -52,20 +55,86 @@ fn parse_software_info(oem_id: i64, value: &Value) -> SoftwareInfo { } } +async fn apply_window_branding(window: &WebviewWindow, info: &SoftwareInfo) { + let title = info.software_name.trim(); + let title = if title.is_empty() { + DEFAULT_TITLE + } else { + title + }; + if let Err(e) = window.set_title(title) { + log::warn!("设置窗口标题失败: {e}"); + } + + let Some(logo_url) = info.logo_url.as_deref().filter(|s| !s.is_empty()) else { + return; + }; + + let client = match reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(30)) + .build() + { + Ok(c) => c, + Err(e) => { + log::warn!("创建 HTTP 客户端失败: {e}"); + return; + } + }; + + let response = match client.get(logo_url).send().await { + Ok(r) => r, + Err(e) => { + log::warn!("下载 logo 失败: {e}"); + return; + } + }; + + if !response.status().is_success() { + log::warn!("下载 logo HTTP {}", response.status()); + return; + } + + let bytes = match response.bytes().await { + Ok(b) => b, + Err(e) => { + log::warn!("读取 logo 内容失败: {e}"); + return; + } + }; + + if bytes.is_empty() { + log::warn!("logo 内容为空"); + return; + } + + match Image::from_bytes(&bytes) { + Ok(icon) => { + if let Err(e) = window.set_icon(icon) { + log::warn!("设置窗口图标失败: {e}"); + } + } + Err(e) => log::warn!("解析 logo 图片失败: {e}"), + } +} + #[tauri::command] -pub async fn get_software_info(ctx: State<'_, OemContext>) -> Result { +pub async fn get_software_info( + window: WebviewWindow, + ctx: State<'_, OemContext>, +) -> Result { let oem_id = ctx.oem_id; let path = format!("/public/oem-branding/{oem_id}"); let (status, value) = api_client::request_json("GET", &path, None, HashMap::new()).await?; - if status >= 400 { - return Ok(empty_info(oem_id)); - } + let info = if status >= 400 { + empty_info(oem_id) + } else if value.get("ok").and_then(|v| v.as_bool()) == Some(false) { + empty_info(oem_id) + } else { + parse_software_info(oem_id, &value) + }; - if value.get("ok").and_then(|v| v.as_bool()) == Some(false) { - return Ok(empty_info(oem_id)); - } - - Ok(parse_software_info(oem_id, &value)) + apply_window_branding(&window, &info).await; + Ok(info) } diff --git a/src/components/AppTitlebar.vue b/src/components/AppTitlebar.vue index 82a6843..aa0ea33 100644 --- a/src/components/AppTitlebar.vue +++ b/src/components/AppTitlebar.vue @@ -2,6 +2,7 @@ import { onMounted, onUnmounted, ref } from "vue"; import { getCurrentWindow } from "@tauri-apps/api/window"; import { fetchSoftwareInfo } from "../api/oemSoftwareInfo.js"; +import { applyWindowBranding } from "../services/windowBranding.js"; const software = ref({ softwareName: "", @@ -55,6 +56,7 @@ onMounted(async () => { loading.value = true; software.value = await fetchSoftwareInfo(); loading.value = false; + await applyWindowBranding(software.value); await syncMaximizedState(); if (isTauri()) { diff --git a/src/services/windowBranding.js b/src/services/windowBranding.js new file mode 100644 index 0000000..a6f1ce8 --- /dev/null +++ b/src/services/windowBranding.js @@ -0,0 +1,14 @@ +const DEFAULT_TITLE = "aiclient"; + +function isTauri() { + return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; +} + +/** + * 同步浏览器标签标题(任务栏标题/图标由 Rust get_software_info 设置)。 + * @param {{ softwareName?: string }} branding + */ +export async function applyWindowBranding({ softwareName } = {}) { + if (!isTauri()) return; + document.title = String(softwareName ?? "").trim() || DEFAULT_TITLE; +}