diff --git a/next-env.d.ts b/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/package.json b/package.json index 8d0b041..fa31dd4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "tauri": "tauri", "build": "tauri build", "build-fast": "tauri build -b nsis -- --profile dev", + "build-fast-prod": "tauri build -b nsis -- --profile fast-release", "dev": "tauri dev", "lint": "next lint", "fix": "next lint --fix" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a2b0425..de6ec72 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -21,6 +21,13 @@ strip = true # Remove debug symbols opt-level = 0 # 关闭优化 debug = true # 保留调试信息 +[profile.fast-release] +inherits = "release" +lto = false # 关闭链接时优化,加快构建速度 +codegen-units = 16 # 增加并行编译单元,加快构建速度 +strip = false # 不剥离调试符号,加快构建速度 +opt-level = 2 # 使用适中的优化级别(比 release 的 "s" 更快) + [build-dependencies] tauri-build = { version = "2.5.1", features = [] } diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index cb3ae5e..df429af 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -377,14 +377,23 @@ pub fn install_app_update(installer_path: String) -> Result<(), String> { #[cfg(target_os = "windows")] pub async fn get_computer_info() -> Result { use tokio::process::Command; + #[cfg(windows)] + use std::os::windows::process::CommandExt; + #[cfg(windows)] + const CREATE_NO_WINDOW: u32 = 0x08000000; // 异步执行 PowerShell 命令获取计算机信息并转换为 JSON - let output = Command::new("powershell") - .args(&[ - "-NoProfile", - "-Command", - "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-ComputerInfo | Select-Object OsName, OSDisplayVersion, BiosSMBIOSBIOSVersion, CsManufacturer, CsName | ConvertTo-Json -Compress" - ]) + let mut cmd = Command::new("powershell"); + cmd.args(&[ + "-NoProfile", + "-WindowStyle", + "Hidden", + "-Command", + "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-ComputerInfo | Select-Object OsName, OSDisplayVersion, BiosSMBIOSBIOSVersion, CsManufacturer, CsName | ConvertTo-Json -Compress" + ]); + #[cfg(windows)] + cmd.creation_flags(CREATE_NO_WINDOW); + let output = cmd .output() .await .map_err(|e| format!("执行 PowerShell 命令失败: {}", e))?; @@ -423,14 +432,17 @@ pub async fn get_computer_info() -> Result { // 如果没有从 OSDisplayVersion 获取到版本代码,尝试从注册表获取 ReleaseId if !json.get("ReleaseId").and_then(|v| v.as_str()).is_some() { - let release_id_output = Command::new("powershell") - .args(&[ - "-NoProfile", - "-Command", - "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; try { (Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion').ReleaseId } catch { $null }" - ]) - .output() - .await; + let mut release_cmd = Command::new("powershell"); + release_cmd.args(&[ + "-NoProfile", + "-WindowStyle", + "Hidden", + "-Command", + "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; try { (Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion').ReleaseId } catch { $null }" + ]); + #[cfg(windows)] + release_cmd.creation_flags(CREATE_NO_WINDOW); + let release_id_output = release_cmd.output().await; if let Ok(release_output) = release_id_output { if release_output.status.success() { @@ -552,14 +564,23 @@ pub struct MotherboardInfo { #[cfg(target_os = "windows")] pub async fn get_memory_info() -> Result, String> { use tokio::process::Command; + #[cfg(windows)] + use std::os::windows::process::CommandExt; + #[cfg(windows)] + const CREATE_NO_WINDOW: u32 = 0x08000000; // 执行 PowerShell 命令获取内存信息 - let output = Command::new("powershell") - .args(&[ - "-NoProfile", - "-Command", - "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-WmiObject Win32_PhysicalMemory | Select-Object Capacity, Manufacturer, ConfiguredClockSpeed, Speed | ConvertTo-Json -Compress" - ]) + let mut cmd = Command::new("powershell"); + cmd.args(&[ + "-NoProfile", + "-WindowStyle", + "Hidden", + "-Command", + "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-WmiObject Win32_PhysicalMemory | Select-Object Capacity, Manufacturer, ConfiguredClockSpeed, Speed | ConvertTo-Json -Compress" + ]); + #[cfg(windows)] + cmd.creation_flags(CREATE_NO_WINDOW); + let output = cmd .output() .await .map_err(|e| format!("执行 PowerShell 命令失败: {}", e))?; @@ -635,14 +656,23 @@ pub async fn get_memory_info() -> Result, String> { #[cfg(target_os = "windows")] pub async fn get_monitor_info() -> Result, String> { use tokio::process::Command; + #[cfg(windows)] + use std::os::windows::process::CommandExt; + #[cfg(windows)] + const CREATE_NO_WINDOW: u32 = 0x08000000; // 执行 PowerShell 命令获取显示器信息 - let output = Command::new("powershell") - .args(&[ - "-NoProfile", - "-Command", - "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-CimInstance -Namespace root/wmi -ClassName WmiMonitorID | ForEach-Object { [PSCustomObject]@{ Manufacturer = [System.Text.Encoding]::ASCII.GetString($_.ManufacturerName) -replace \"`0\"; Model = [System.Text.Encoding]::ASCII.GetString($_.UserFriendlyName) -replace \"`0\" } } | ConvertTo-Json -Compress" - ]) + let mut cmd = Command::new("powershell"); + cmd.args(&[ + "-NoProfile", + "-WindowStyle", + "Hidden", + "-Command", + "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-CimInstance -Namespace root/wmi -ClassName WmiMonitorID | ForEach-Object { [PSCustomObject]@{ Manufacturer = [System.Text.Encoding]::ASCII.GetString($_.ManufacturerName) -replace \"`0\"; Model = [System.Text.Encoding]::ASCII.GetString($_.UserFriendlyName) -replace \"`0\" } } | ConvertTo-Json -Compress" + ]); + #[cfg(windows)] + cmd.creation_flags(CREATE_NO_WINDOW); + let output = cmd .output() .await .map_err(|e| format!("执行 PowerShell 命令失败: {}", e))?; @@ -673,34 +703,43 @@ pub async fn get_monitor_info() -> Result, String> { } // 尝试获取刷新率和分辨率信息 - let _display_output = Command::new("powershell") - .args(&[ - "-NoProfile", - "-Command", - "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-CimInstance -Namespace root/wmi -ClassName WmiMonitorBasicDisplayParams | Select-Object MaxHorizontalImageSize, MaxVerticalImageSize | ConvertTo-Json -Compress" - ]) - .output() - .await; + let mut _display_cmd = Command::new("powershell"); + _display_cmd.args(&[ + "-NoProfile", + "-WindowStyle", + "Hidden", + "-Command", + "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-CimInstance -Namespace root/wmi -ClassName WmiMonitorBasicDisplayParams | Select-Object MaxHorizontalImageSize, MaxVerticalImageSize | ConvertTo-Json -Compress" + ]); + #[cfg(windows)] + _display_cmd.creation_flags(CREATE_NO_WINDOW); + let _display_output = _display_cmd.output().await; // 获取刷新率信息 - let _refresh_output = Command::new("powershell") - .args(&[ - "-NoProfile", - "-Command", - "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-CimInstance -Namespace root/wmi -ClassName WmiMonitorListedSupportedSourceModes | Select-Object -First 1 -ExpandProperty ModeTimings | Select-Object -First 1 -ExpandProperty RefreshRate | ConvertTo-Json -Compress" - ]) - .output() - .await; + let mut _refresh_cmd = Command::new("powershell"); + _refresh_cmd.args(&[ + "-NoProfile", + "-WindowStyle", + "Hidden", + "-Command", + "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-CimInstance -Namespace root/wmi -ClassName WmiMonitorListedSupportedSourceModes | Select-Object -First 1 -ExpandProperty ModeTimings | Select-Object -First 1 -ExpandProperty RefreshRate | ConvertTo-Json -Compress" + ]); + #[cfg(windows)] + _refresh_cmd.creation_flags(CREATE_NO_WINDOW); + let _refresh_output = _refresh_cmd.output().await; // 获取当前显示器的分辨率和刷新率 - let current_display_output = Command::new("powershell") - .args(&[ - "-NoProfile", - "-Command", - "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Add-Type -AssemblyName System.Windows.Forms; $screens = [System.Windows.Forms.Screen]::AllScreens; $screens | ForEach-Object { [PSCustomObject]@{ Width = $_.Bounds.Width; Height = $_.Bounds.Height; Primary = $_.Primary } } | ConvertTo-Json -Compress" - ]) - .output() - .await; + let mut current_display_cmd = Command::new("powershell"); + current_display_cmd.args(&[ + "-NoProfile", + "-WindowStyle", + "Hidden", + "-Command", + "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Add-Type -AssemblyName System.Windows.Forms; $screens = [System.Windows.Forms.Screen]::AllScreens; $screens | ForEach-Object { [PSCustomObject]@{ Width = $_.Bounds.Width; Height = $_.Bounds.Height; Primary = $_.Primary } } | ConvertTo-Json -Compress" + ]); + #[cfg(windows)] + current_display_cmd.creation_flags(CREATE_NO_WINDOW); + let current_display_output = current_display_cmd.output().await; // 合并显示器信息 if let Ok(display_result) = current_display_output { @@ -783,14 +822,23 @@ pub async fn get_monitor_info() -> Result, String> { #[cfg(target_os = "windows")] pub async fn get_motherboard_info() -> Result { use tokio::process::Command; + #[cfg(windows)] + use std::os::windows::process::CommandExt; + #[cfg(windows)] + const CREATE_NO_WINDOW: u32 = 0x08000000; // 执行 PowerShell 命令获取主板信息 - let output = Command::new("powershell") - .args(&[ - "-NoProfile", - "-Command", - "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-CimInstance -ClassName Win32_BaseBoard | Select-Object Manufacturer, Product, Version | ConvertTo-Json -Compress" - ]) + let mut cmd = Command::new("powershell"); + cmd.args(&[ + "-NoProfile", + "-WindowStyle", + "Hidden", + "-Command", + "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-CimInstance -ClassName Win32_BaseBoard | Select-Object Manufacturer, Product, Version | ConvertTo-Json -Compress" + ]); + #[cfg(windows)] + cmd.creation_flags(CREATE_NO_WINDOW); + let output = cmd .output() .await .map_err(|e| format!("执行 PowerShell 命令失败: {}", e))?; diff --git a/src-tauri/src/tool/common.rs b/src-tauri/src/tool/common.rs index 4ed09a2..84ddc96 100644 --- a/src-tauri/src/tool/common.rs +++ b/src-tauri/src/tool/common.rs @@ -36,10 +36,15 @@ pub fn get_exe_path(name: &str) -> Result { // ---- // 进程路径 let command = format!("Get-Process {} | Select-Object path", name); - let args = command.split_whitespace().collect::>(); #[cfg(windows)] let output = Command::new("powershell.exe") - .args(&args) + .args(&[ + "-NoProfile", + "-WindowStyle", + "Hidden", + "-Command", + &command, + ]) .creation_flags(CREATE_NO_WINDOW) .output()?; diff --git a/src-tauri/src/tool/updater.rs b/src-tauri/src/tool/updater.rs index b3e53a5..7585c79 100644 --- a/src-tauri/src/tool/updater.rs +++ b/src-tauri/src/tool/updater.rs @@ -415,17 +415,23 @@ pub async fn download_update( /// 安装更新(Windows) #[cfg(target_os = "windows")] pub fn install_update(installer_path: &str) -> Result<()> { + // 使用 /S 静默安装 let mut cmd = Command::new(installer_path); cmd.args(&["/S"]); // 静默安装 cmd.creation_flags(CREATE_NO_WINDOW); - cmd.spawn()?; + let mut child = cmd.spawn()?; + // 等待安装程序完成 + child.wait()?; + // 安装完成后,由前端调用 relaunch() 来启动新版本 Ok(()) } /// 安装更新(macOS) #[cfg(target_os = "macos")] pub fn install_update(installer_path: &str) -> Result<()> { - Command::new("open").arg(installer_path).spawn()?; + let mut child = Command::new("open").arg(installer_path).spawn()?; + // 等待安装程序完成 + child.wait()?; Ok(()) } @@ -433,13 +439,15 @@ pub fn install_update(installer_path: &str) -> Result<()> { #[cfg(target_os = "linux")] pub fn install_update(installer_path: &str) -> Result<()> { if installer_path.ends_with(".deb") { - Command::new("sudo") + let mut child = Command::new("sudo") .args(&["dpkg", "-i", installer_path]) .spawn()?; + child.wait()?; } else if installer_path.ends_with(".AppImage") { - Command::new("chmod") + let mut child = Command::new("chmod") .args(&["+x", installer_path]) .spawn()?; + child.wait()?; } Ok(()) } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f4bd293..f76c468 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -51,7 +51,7 @@ }, "productName": "CS工具箱", "mainBinaryName": "cstb", - "version": "0.0.6-beta.7", + "version": "0.0.6-beta.6", "identifier": "upup.cool", "plugins": { "deep-link": { diff --git a/src/app/(main)/gear/page.tsx b/src/app/(main)/gear/page.tsx index 45b3c3e..d165952 100644 --- a/src/app/(main)/gear/page.tsx +++ b/src/app/(main)/gear/page.tsx @@ -3,10 +3,8 @@ import { Card, CardBody, CardHeader, CardIcon, CardTool } from "@/components/win import { ToolButton } from "@/components/window/ToolButton" import { Chip, Skeleton } from "@heroui/react" import { Refresh, SettingConfig } from "@icon-park/react" -// import { version } from "@tauri-apps/plugin-os" -import { type AllSystemInfo, allSysInfo } from "tauri-plugin-system-info-api" -import { invoke } from "@tauri-apps/api/core" -import useSWR, { useSWRConfig } from "swr" +import { useHardwareStore } from "@/store/hardware" +import { useEffect, useState } from "react" export default function Page() { return ( @@ -36,135 +34,47 @@ export default function Page() { } function HardwareInfo() { - const { mutate } = useSWRConfig() + const { refreshHardwareInfo } = useHardwareStore() + const [isRefreshing, setIsRefreshing] = useState(false) return ( { - // 使用 SWR 的 mutate 来刷新数据 - mutate("/api/hardware-info") + onClick={async () => { + setIsRefreshing(true) + try { + await refreshHardwareInfo() + } finally { + setIsRefreshing(false) + } }} + disabled={isRefreshing} > 刷新 ) } -interface ComputerInfo { - OsName?: string - OSDisplayVersion?: string - BiosSMBIOSBIOSVersion?: string - CsManufacturer?: string - CsName?: string - ReleaseId?: string -} - -interface GpuInfo { - vendor: string - model: string - family: string - device_id: string - total_vram: number - used_vram: number - load_pct: number - temperature: number -} - -interface MemoryInfo { - capacity?: number // 容量(字节) - manufacturer?: string - speed?: number // MHz,实际频率 ConfiguredClockSpeed - default_speed?: number // MHz,默认频率 Speed(如果存在) -} - -interface MonitorInfo { - name?: string - refresh_rate?: number // Hz - resolution_width?: number - resolution_height?: number -} - -interface MotherboardInfo { - manufacturer?: string // 制造商 - model?: string // 型号 - version?: string -} - -interface HardwareData { - allSysData: AllSystemInfo - computerInfo: ComputerInfo - gpuInfo: GpuInfo | null - memoryInfo: MemoryInfo[] - monitorInfo: MonitorInfo[] - motherboardInfo: MotherboardInfo | null -} - -// 硬件信息 fetcher -const hardwareInfoFetcher = async (): Promise => { - // 并行获取系统信息、PowerShell 信息、GPU 信息、内存信息、显示器信息和主板信息 - const [sys, computerInfoData, gpuInfoData, memoryInfoData, monitorInfoData, motherboardInfoData] = - await Promise.all([ - allSysInfo(), - invoke("get_computer_info").catch((error) => { - console.error("获取 PowerShell 信息失败:", error) - return {} as ComputerInfo - }), - invoke("get_gpu_info").catch((error) => { - console.error("获取 GPU 信息失败:", error) - return null - }), - invoke("get_memory_info").catch((error) => { - console.error("获取内存信息失败:", error) - return [] as MemoryInfo[] - }), - invoke("get_monitor_info").catch((error) => { - console.error("获取显示器信息失败:", error) - return [] as MonitorInfo[] - }), - invoke("get_motherboard_info").catch((error) => { - console.error("获取主板信息失败:", error) - return { manufacturer: undefined, model: undefined, version: undefined } as MotherboardInfo - }), - ]) - - console.log("系统信息:", sys) - console.log("PowerShell 信息:", computerInfoData) - console.log("GPU 信息:", gpuInfoData) - console.log("内存信息:", memoryInfoData) - console.log("显示器信息:", monitorInfoData) - console.log("主板信息:", motherboardInfoData) - - if (sys?.cpus) { - console.log("CPU数据:", sys.cpus) - console.log("第一个CPU:", sys.cpus[0]) - if (sys.cpus[0]) { - console.log("CPU字段:", Object.keys(sys.cpus[0])) - } - } - - return { - allSysData: sys, - computerInfo: computerInfoData, - gpuInfo: gpuInfoData, - memoryInfo: memoryInfoData, - monitorInfo: monitorInfoData, - motherboardInfo: motherboardInfoData, - } -} - function HardwareInfoContent() { - const { data, isLoading, isValidating } = useSWR("/api/hardware-info", hardwareInfoFetcher, { - revalidateOnFocus: false, - revalidateOnReconnect: false, - dedupingInterval: 5 * 60 * 1000, // 5分钟内相同请求去重 - }) + const { state, fetchHardwareInfo } = useHardwareStore() + const [isLoading, setIsLoading] = useState(!state.allSysData) - const allSysData = data?.allSysData - const computerInfo = data?.computerInfo || {} - const gpuInfo = data?.gpuInfo - const memoryInfo = data?.memoryInfo || [] - const monitorInfo = data?.monitorInfo || [] - const motherboardInfo = data?.motherboardInfo + useEffect(() => { + // 如果数据不存在,则加载数据 + if (!state.allSysData) { + setIsLoading(true) + void fetchHardwareInfo().finally(() => { + setIsLoading(false) + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) // 只在组件挂载时执行一次,state 是响应式的,不需要作为依赖 + + const allSysData = state.allSysData + const computerInfo = state.computerInfo || {} + const gpuInfo = state.gpuInfo + const memoryInfo = state.memoryInfo || [] + const monitorInfo = state.monitorInfo || [] + const motherboardInfo = state.motherboardInfo const formatBytes = (bytes?: number) => { if (!bytes) return "未知" @@ -243,8 +153,8 @@ function HardwareInfoContent() { })() : null - // 如果正在加载或正在验证(包括刷新),显示 Skeleton 骨架屏 - if (isLoading || isValidating) { + // 如果正在加载,显示 Skeleton 骨架屏 + if (isLoading || !allSysData) { return (
{/* 系统信息 Skeleton */} diff --git a/src/components/cstb/FpsTest/hooks/useHardwareInfo.ts b/src/components/cstb/FpsTest/hooks/useHardwareInfo.ts index 5f91306..605c09b 100644 --- a/src/components/cstb/FpsTest/hooks/useHardwareInfo.ts +++ b/src/components/cstb/FpsTest/hooks/useHardwareInfo.ts @@ -1,48 +1,13 @@ -import { useState, useEffect } from "react" -import { allSysInfo, type AllSystemInfo } from "tauri-plugin-system-info-api" -import { invoke } from "@tauri-apps/api/core" - -interface GpuInfo { - vendor: string - model: string - family: string - device_id: string - total_vram: number - used_vram: number - load_pct: number - temperature: number -} - -interface ComputerInfo { - OsName?: string - OSDisplayVersion?: string - BiosSMBIOSBIOSVersion?: string - CsManufacturer?: string - CsName?: string - ReleaseId?: string -} - -interface MemoryInfo { - capacity?: number // 容量(字节) - manufacturer?: string - speed?: number // MHz,实际频率 ConfiguredClockSpeed - default_speed?: number // MHz,默认频率 Speed(如果存在) -} - -interface MonitorInfo { - manufacturer?: string - model?: string - name?: string - refresh_rate?: number // Hz - resolution_width?: number - resolution_height?: number -} - -interface MotherboardInfo { - manufacturer?: string // 制造商 - model?: string // 型号 - version?: string -} +import { useEffect } from "react" +import { useHardwareStore } from "@/store/hardware" +import type { AllSystemInfo } from "tauri-plugin-system-info-api" +import type { + GpuInfo, + ComputerInfo, + MemoryInfo, + MonitorInfo, + MotherboardInfo, +} from "@/store/hardware" export interface HardwareInfoWithGpu { systemInfo: AllSystemInfo | null @@ -54,49 +19,23 @@ export interface HardwareInfoWithGpu { } export function useHardwareInfo() { - const [hardwareInfo, setHardwareInfo] = useState(null) + const { state, fetchHardwareInfo } = useHardwareStore() useEffect(() => { - const fetchHardwareInfo = async () => { - try { - const [sys, gpuInfo, computerInfo, memoryInfo, monitorInfo, motherboardInfo] = await Promise.all([ - allSysInfo(), - invoke("get_gpu_info").catch((error) => { - console.error("获取 GPU 信息失败:", error) - return null - }), - invoke("get_computer_info").catch((error) => { - console.error("获取 PowerShell 信息失败:", error) - return {} as ComputerInfo - }), - invoke("get_memory_info").catch((error) => { - console.error("获取内存信息失败:", error) - return [] as MemoryInfo[] - }), - invoke("get_monitor_info").catch((error) => { - console.error("获取显示器信息失败:", error) - return [] as MonitorInfo[] - }), - invoke("get_motherboard_info").catch((error) => { - console.error("获取主板信息失败:", error) - return { manufacturer: undefined, model: undefined, version: undefined } as MotherboardInfo - }) - ]) - setHardwareInfo({ - systemInfo: sys, - gpuInfo, - computerInfo, - memoryInfo, - monitorInfo, - motherboardInfo - }) - } catch (error) { - console.error("获取硬件信息失败:", error) - } + // 如果数据不存在,则加载数据(store 初始化时已经加载,这里只是确保) + if (!state.allSysData) { + void fetchHardwareInfo() } - void fetchHardwareInfo() - }, []) + }, []) // 只在组件挂载时执行一次 - return hardwareInfo + // 将 store 中的数据转换为兼容的格式 + return { + systemInfo: state.allSysData, + gpuInfo: state.gpuInfo, + computerInfo: state.computerInfo, + memoryInfo: state.memoryInfo, + monitorInfo: state.monitorInfo, + motherboardInfo: state.motherboardInfo, + } as HardwareInfoWithGpu } diff --git a/src/components/cstb/UpdateChecker.tsx b/src/components/cstb/UpdateChecker.tsx index f453d40..9ea3309 100644 --- a/src/components/cstb/UpdateChecker.tsx +++ b/src/components/cstb/UpdateChecker.tsx @@ -170,19 +170,22 @@ export function UpdateChecker({ useMirror = true, customEndpoint, includePrerele if (!installerPath) return try { + addToast({ + title: "安装已启动", + description: "应用将在安装完成后自动重启", + color: "success", + }) + + // 调用安装命令(这会阻塞直到安装完成) await invoke("install_app_update", { installerPath: installerPath, }) - addToast({ - title: "安装已启动", - description: "应用将在安装完成后重启", - color: "success", - }) - - setTimeout(async () => { - await relaunch() - }, 1000) + // 安装完成后,等待一小段时间确保安装程序完全退出 + await new Promise(resolve => setTimeout(resolve, 500)) + + // 启动新版本 + await relaunch() } catch (error) { console.error("安装更新失败:", error) addToast({ diff --git a/src/store/hardware.ts b/src/store/hardware.ts new file mode 100644 index 0000000..dc8baa0 --- /dev/null +++ b/src/store/hardware.ts @@ -0,0 +1,151 @@ +import { store } from "@tauri-store/valtio" +import { useSnapshot } from "valtio" +import { DEFAULT_STORE_CONFIG } from "./config" +import { allSysInfo, type AllSystemInfo } from "tauri-plugin-system-info-api" +import { invoke } from "@tauri-apps/api/core" + +export interface ComputerInfo { + OsName?: string + OSDisplayVersion?: string + BiosSMBIOSBIOSVersion?: string + CsManufacturer?: string + CsName?: string + ReleaseId?: string +} + +export interface GpuInfo { + vendor: string + model: string + family: string + device_id: string + total_vram: number + used_vram: number + load_pct: number + temperature: number +} + +export interface MemoryInfo { + capacity?: number // 容量(字节) + manufacturer?: string + speed?: number // MHz,实际频率 ConfiguredClockSpeed + default_speed?: number // MHz,默认频率 Speed(如果存在) +} + +export interface MonitorInfo { + name?: string + refresh_rate?: number // Hz + resolution_width?: number + resolution_height?: number +} + +export interface MotherboardInfo { + manufacturer?: string // 制造商 + model?: string // 型号 + version?: string +} + +export interface HardwareData { + allSysData: AllSystemInfo | null + computerInfo: ComputerInfo + gpuInfo: GpuInfo | null + memoryInfo: MemoryInfo[] + monitorInfo: MonitorInfo[] + motherboardInfo: MotherboardInfo | null + lastUpdated: number // 最后更新时间戳 +} + +const defaultValue: HardwareData = { + allSysData: null, + computerInfo: {}, + gpuInfo: null, + memoryInfo: [], + monitorInfo: [], + motherboardInfo: null, + lastUpdated: 0, +} + +// 硬件信息 fetcher +const hardwareInfoFetcher = async (): Promise => { + // 并行获取系统信息、PowerShell 信息、GPU 信息、内存信息、显示器信息和主板信息 + const [sys, computerInfoData, gpuInfoData, memoryInfoData, monitorInfoData, motherboardInfoData] = + await Promise.all([ + allSysInfo(), + invoke("get_computer_info").catch((error) => { + console.error("获取 PowerShell 信息失败:", error) + return {} as ComputerInfo + }), + invoke("get_gpu_info").catch((error) => { + console.error("获取 GPU 信息失败:", error) + return null + }), + invoke("get_memory_info").catch((error) => { + console.error("获取内存信息失败:", error) + return [] as MemoryInfo[] + }), + invoke("get_monitor_info").catch((error) => { + console.error("获取显示器信息失败:", error) + return [] as MonitorInfo[] + }), + invoke("get_motherboard_info").catch((error) => { + console.error("获取主板信息失败:", error) + return { manufacturer: undefined, model: undefined, version: undefined } as MotherboardInfo + }), + ]) + + return { + allSysData: sys, + computerInfo: computerInfoData, + gpuInfo: gpuInfoData, + memoryInfo: memoryInfoData, + monitorInfo: monitorInfoData, + motherboardInfo: motherboardInfoData, + lastUpdated: Date.now(), + } +} + +export const hardwareStore = store("hardware", { ...defaultValue }, DEFAULT_STORE_CONFIG) + +// 检查数据是否过期(30分钟) +const isDataStale = (lastUpdated: number): boolean => { + const thirtyMinutes = 30 * 60 * 1000 + return Date.now() - lastUpdated > thirtyMinutes +} + +// 获取硬件信息(如果数据过期或不存在则重新获取) +export const fetchHardwareInfo = async (force = false): Promise => { + // 如果数据存在且未过期,且不是强制刷新,则直接返回 + if (!force && hardwareStore.state.allSysData && !isDataStale(hardwareStore.state.lastUpdated)) { + return + } + + try { + const data = await hardwareInfoFetcher() + hardwareStore.state.allSysData = data.allSysData + hardwareStore.state.computerInfo = data.computerInfo + hardwareStore.state.gpuInfo = data.gpuInfo + hardwareStore.state.memoryInfo = data.memoryInfo + hardwareStore.state.monitorInfo = data.monitorInfo + hardwareStore.state.motherboardInfo = data.motherboardInfo + hardwareStore.state.lastUpdated = data.lastUpdated + } catch (error) { + console.error("获取硬件信息失败:", error) + } +} + +// 强制刷新硬件信息 +export const refreshHardwareInfo = async (): Promise => { + await fetchHardwareInfo(true) +} + +export const useHardwareStore = () => { + void hardwareStore.start + const state = useSnapshot(hardwareStore.state) + + return { + state, + store: hardwareStore, + fetchHardwareInfo, + refreshHardwareInfo, + } +} + diff --git a/src/store/index.ts b/src/store/index.ts index 801e0a1..b3e5bb6 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -3,6 +3,7 @@ import { authStore } from "./auth" import { steamStore } from "./steam" import { toolStore } from "./tool" import { fpsTestStore } from "./fps_test" +import { hardwareStore, fetchHardwareInfo } from "./hardware" export async function init() { await appStore.start() @@ -10,4 +11,7 @@ export async function init() { await toolStore.start() await steamStore.start() await fpsTestStore.start() + await hardwareStore.start() + // 初始化时自动加载硬件信息(如果数据过期或不存在) + await fetchHardwareInfo() }