This commit is contained in:
fengchuanhn@gmail.com
2026-05-21 01:53:54 +08:00
parent 54f080fbc1
commit 14cf9b7bf5
6 changed files with 134 additions and 11 deletions

36
src-tauri/Cargo.lock generated
View File

@@ -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",

View File

@@ -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"] }

View File

@@ -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",

View File

@@ -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<SoftwareInfo, String> {
pub async fn get_software_info(
window: WebviewWindow,
ctx: State<'_, OemContext>,
) -> Result<SoftwareInfo, String> {
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)
}

View File

@@ -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()) {

View File

@@ -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;
}