Files
cstb-next/src/app/(main)/gear/page.tsx

517 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { Card, CardBody, CardHeader, CardIcon, CardTool } from "@/components/window/Card"
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"
export default function Page() {
return (
<section className="flex flex-col gap-4 overflow-hidden rounded-lg">
<div className="flex flex-col h-full gap-4 overflow-hidden hide-scrollbar">
<Card className="overflow-hidden">
<CardHeader>
<CardIcon type="menu">
<SettingConfig />
</CardIcon>
<CardTool>
{/* <ToolButton>
<UploadOne />
云同步
</ToolButton> */}
<HardwareInfo />
</CardTool>
</CardHeader>
<CardBody>
<HardwareInfoContent />
</CardBody>
</Card>
</div>
</section>
)
}
function HardwareInfo() {
const { mutate } = useSWRConfig()
return (
<ToolButton
onClick={() => {
// 使用 SWR 的 mutate 来刷新数据
mutate("/api/hardware-info")
}}
>
<Refresh />
</ToolButton>
)
}
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<HardwareData> => {
// 并行获取系统信息、PowerShell 信息、GPU 信息、内存信息、显示器信息和主板信息
const [sys, computerInfoData, gpuInfoData, memoryInfoData, monitorInfoData, motherboardInfoData] =
await Promise.all([
allSysInfo(),
invoke<ComputerInfo>("get_computer_info").catch((error) => {
console.error("获取 PowerShell 信息失败:", error)
return {} as ComputerInfo
}),
invoke<GpuInfo | null>("get_gpu_info").catch((error) => {
console.error("获取 GPU 信息失败:", error)
return null
}),
invoke<MemoryInfo[]>("get_memory_info").catch((error) => {
console.error("获取内存信息失败:", error)
return [] as MemoryInfo[]
}),
invoke<MonitorInfo[]>("get_monitor_info").catch((error) => {
console.error("获取显示器信息失败:", error)
return [] as MonitorInfo[]
}),
invoke<MotherboardInfo>("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<HardwareData>("/api/hardware-info", hardwareInfoFetcher, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
dedupingInterval: 5 * 60 * 1000, // 5分钟内相同请求去重
})
const allSysData = data?.allSysData
const computerInfo = data?.computerInfo || {}
const gpuInfo = data?.gpuInfo
const memoryInfo = data?.memoryInfo || []
const monitorInfo = data?.monitorInfo || []
const motherboardInfo = data?.motherboardInfo
const formatBytes = (bytes?: number) => {
if (!bytes) return "未知"
const gb = bytes / 1024 / 1024 / 1024
if (gb >= 1) {
return `${gb.toFixed(2)}GB`
}
const mb = bytes / 1024 / 1024
return `${mb.toFixed(2)}MB`
}
// 格式化系统信息Windows 11 (26200) 25H2
const formatSystemInfo = () => {
const osVersion = allSysData?.os_version || null
// 使用 OSDisplayVersion 作为版本代码(如 "25H2"
const osDisplayVersion = computerInfo.OSDisplayVersion || null
let systemStr = allSysData?.name || "未知"
if (osVersion) {
systemStr += ` ${osVersion}`
}
if (osDisplayVersion) {
systemStr += ` ${osDisplayVersion}`
}
return systemStr
}
const memoryUsagePercent =
allSysData?.total_memory && allSysData?.used_memory !== undefined
? Math.round((allSysData.used_memory / allSysData.total_memory) * 100)
: null
// 计算所有CPU核心的平均频率统一转换为GHz
const averageCpuFrequency =
allSysData?.cpus && allSysData.cpus.length > 0
? (() => {
// 尝试多个可能的频率字段名
const frequencies = allSysData.cpus
.map((cpu) => {
// 尝试不同的字段名
const freq = (cpu as any).frequency ?? (cpu as any).freq ?? (cpu as any).clock_speed
return freq
})
.filter((freq): freq is number => {
// 确保是有效的数字且大于0
return typeof freq === "number" && !isNaN(freq) && freq > 0
})
if (frequencies.length === 0) {
console.log("未找到有效的CPU频率数据CPU对象:", allSysData.cpus[0])
return null
}
const sum = frequencies.reduce((acc, freq) => acc + freq, 0)
const avg = sum / frequencies.length
// 判断单位并统一转换为GHz
// 如果值在2-10范围可能是GHz
// 如果值在2000-10000范围可能是MHz需要除以1000
// 如果值在百万级别2000000+可能是Hz需要除以1,000,000
let freqInGhz: number
if (avg >= 1_000_000) {
// Hz单位转换为GHz
freqInGhz = avg / 1_000_000
} else if (avg >= 1000) {
// MHz单位转换为GHz
freqInGhz = avg / 1000
} else {
// 已经是GHz单位
freqInGhz = avg
}
console.log("CPU频率数据:", frequencies, "原始平均值:", avg, "转换为GHz:", freqInGhz)
return freqInGhz
})()
: null
// 如果正在加载或正在验证(包括刷新),显示 Skeleton 骨架屏
if (isLoading || isValidating) {
return (
<div className="flex flex-col gap-4">
{/* 系统信息 Skeleton */}
<div className="flex flex-col gap-2">
<Skeleton className="w-24 h-5 rounded" />
<div className="flex flex-wrap gap-2">
<Skeleton className="w-48 h-8 rounded-full" />
<Skeleton className="w-32 h-8 rounded-full" />
</div>
</div>
{/* CPU 信息 Skeleton */}
<div className="flex flex-col gap-2">
<Skeleton className="w-20 h-5 rounded" />
<div className="flex flex-wrap gap-2">
<Skeleton className="w-56 h-8 rounded-full" />
<Skeleton className="w-32 h-8 rounded-full" />
<Skeleton className="h-8 rounded-full w-28" />
</div>
</div>
{/* 内存信息 Skeleton */}
<div className="flex flex-col gap-2">
<Skeleton className="w-16 h-5 rounded" />
<div className="flex flex-wrap gap-2">
<Skeleton className="h-8 rounded-full w-36" />
<Skeleton className="w-40 h-8 rounded-full" />
<Skeleton className="h-8 rounded-full w-36" />
</div>
</div>
{/* GPU 信息 Skeleton */}
<div className="flex flex-col gap-2">
<Skeleton className="w-16 h-5 rounded" />
<div className="flex flex-wrap gap-2">
<Skeleton className="h-8 rounded-full w-52" />
</div>
</div>
{/* 主板信息 Skeleton */}
<div className="flex flex-col gap-2">
<Skeleton className="w-16 h-5 rounded" />
<div className="flex flex-wrap gap-2">
<Skeleton className="w-40 h-8 rounded-full" />
<Skeleton className="h-8 rounded-full w-36" />
</div>
</div>
</div>
)
}
return (
<div className="flex flex-col gap-4">
{/* 系统信息 */}
<div className="flex flex-col gap-2">
<div className="text-sm font-medium text-foreground-600"></div>
<div className="flex flex-wrap gap-2">
<Chip>
<span className="font-medium"></span> {formatSystemInfo()}
</Chip>
{computerInfo.CsName && (
<Chip>
<span className="font-medium"></span> {computerInfo.CsName}
</Chip>
)}
{!computerInfo.CsName && allSysData?.hostname && (
<Chip>
<span className="font-medium"></span> {allSysData.hostname}
</Chip>
)}
</div>
</div>
{/* CPU 信息 */}
<div className="flex flex-col gap-2">
<div className="text-sm font-medium text-foreground-600"></div>
<div className="flex flex-wrap gap-2">
<Chip>
<span className="font-medium"></span> {allSysData?.cpus?.[0]?.brand || "未知"}
</Chip>
{averageCpuFrequency !== null && (
<Chip>
<span className="font-medium"></span> {averageCpuFrequency.toFixed(2)} GHz
</Chip>
)}
<Chip>
<span className="font-medium"></span> {allSysData?.cpu_count || "未知"}
</Chip>
</div>
</div>
{/* 内存信息 */}
{allSysData?.total_memory && (
<div className="flex flex-col gap-2">
<div className="text-sm font-medium text-foreground-600"></div>
{/* 第一行:总容量和已用内存 */}
<div className="flex flex-wrap gap-2">
<Chip>
<span className="font-medium"></span> {formatBytes(allSysData.total_memory)}
</Chip>
{allSysData.used_memory !== undefined && (
<Chip>
<span className="font-medium"></span> {formatBytes(allSysData.used_memory)}
{memoryUsagePercent !== null && (
<span className="ml-1">({memoryUsagePercent}%)</span>
)}
</Chip>
)}
</div>
{/* 每个内存条的详细信息 */}
{memoryInfo.length > 0 && (
<div className="flex flex-col gap-1.5">
{memoryInfo.map((mem, index) => (
<div key={index} className="flex flex-wrap gap-2">
{mem.capacity && (
<Chip>
<span className="font-medium"></span> {formatBytes(mem.capacity)}
</Chip>
)}
{mem.manufacturer && (
<Chip>
<span className="font-medium"></span> {mem.manufacturer}
</Chip>
)}
{mem.speed && (
<Chip>
<span className="font-medium"></span> {mem.speed} MHz
{mem.default_speed && (
<span className="ml-1">({mem.default_speed} MHz)</span>
)}
</Chip>
)}
</div>
))}
</div>
)}
</div>
)}
{/* GPU 信息 */}
{gpuInfo ? (
<div className="flex flex-col gap-2">
<div className="text-sm font-medium text-foreground-600"></div>
<div className="flex flex-wrap gap-2">
<Chip>
<span className="font-medium"></span> {gpuInfo.model}
</Chip>
<Chip>
<span className="font-medium"></span> {formatBytes(gpuInfo.total_vram)}
</Chip>
<Chip>
<span className="font-medium"></span> {formatBytes(gpuInfo.used_vram)}
</Chip>
<Chip>
<span className="font-medium"></span> {gpuInfo.temperature.toFixed(2)}°C
</Chip>
</div>
</div>
) : (
allSysData?.components &&
allSysData.components.length > 0 &&
(() => {
const gpuComponents = allSysData.components.filter(
(comp) =>
comp.label?.toLowerCase().includes("gpu") ||
comp.label?.toLowerCase().includes("graphics") ||
comp.label?.toLowerCase().includes("显卡")
)
if (gpuComponents.length === 0) return null
return (
<div className="flex flex-col gap-2">
<div className="text-sm font-medium text-foreground-600"></div>
<div className="flex flex-wrap gap-2">
{gpuComponents.map((gpu, index) => (
<Chip key={index}>
<span className="font-medium">GPU{index > 0 ? ` ${index + 1}` : ""}</span>{" "}
{gpu.label || "未知"}
{gpu.temperature !== undefined && (
<span className="ml-1">
({gpu.temperature}°C{gpu.max !== undefined ? ` / ${gpu.max}°C` : ""})
</span>
)}
</Chip>
))}
</div>
</div>
)
})()
)}
{/* 主板信息 */}
{(motherboardInfo?.model ||
motherboardInfo?.manufacturer ||
motherboardInfo?.version ||
computerInfo.BiosSMBIOSBIOSVersion) && (
<div className="flex flex-col gap-2">
<div className="text-sm font-medium text-foreground-600"></div>
<div className="flex flex-wrap gap-2">
{motherboardInfo?.model && (
<Chip>
<span className="font-medium"></span> {motherboardInfo.model}
</Chip>
)}
{motherboardInfo?.manufacturer && (
<Chip>
<span className="font-medium"></span> {motherboardInfo.manufacturer}
</Chip>
)}
</div>
<div className="flex flex-wrap gap-2">
{motherboardInfo?.version && (
<Chip>
<span className="font-medium"></span> {motherboardInfo.version}
</Chip>
)}
{computerInfo.BiosSMBIOSBIOSVersion && (
<Chip>
<span className="font-medium">BIOS版本</span> {computerInfo.BiosSMBIOSBIOSVersion}
</Chip>
)}
</div>
</div>
)}
{/* 显示器信息 */}
{monitorInfo.length > 0 && monitorInfo.some((m) => m.refresh_rate) && (
<div className="flex flex-col gap-2">
<div className="text-sm font-medium text-foreground-600"></div>
<div className="flex flex-wrap gap-2">
{monitorInfo.map(
(monitor, index) =>
monitor.refresh_rate && (
<Chip key={index}>
<span className="font-medium">
{monitorInfo.length > 1 ? ` ${index + 1}` : ""}
</span>{" "}
{monitor.refresh_rate} Hz
</Chip>
)
)}
</div>
</div>
)}
{/* 电池信息 */}
{allSysData?.batteries && allSysData.batteries.length > 0 && (
<div className="flex flex-col gap-2">
<div className="text-sm font-medium text-foreground-600"></div>
<div className="flex flex-wrap gap-2">
{allSysData.batteries.map((battery, index) => (
<Chip key={index}>
<span className="font-medium">{index > 0 ? ` ${index + 1}` : ""}</span>
{battery.state && `${battery.state} `}
{battery.state_of_charge !== undefined && `${battery.state_of_charge}% `}
{battery.energy_full !== undefined && battery.energy !== undefined && (
<span>
({formatBytes(battery.energy)} / {formatBytes(battery.energy_full)})
</span>
)}
</Chip>
))}
</div>
</div>
)}
</div>
)
}