1
This commit is contained in:
36
src-tauri/Cargo.lock
generated
36
src-tauri/Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
14
src/services/windowBranding.js
Normal file
14
src/services/windowBranding.js
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user