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

517 lines
18 KiB
TypeScript
Raw Normal View History

"use client"
import { Card, CardBody, CardHeader, CardIcon, CardTool } from "@/components/window/Card"
import { ToolButton } from "@/components/window/ToolButton"
2025-11-08 13:24:00 +08:00
import { Chip, Skeleton } from "@heroui/react"
2025-03-13 03:44:48 +08:00
import { Refresh, SettingConfig } from "@icon-park/react"
// import { version } from "@tauri-apps/plugin-os"
2025-03-13 03:44:48 +08:00
import { type AllSystemInfo, allSysInfo } from "tauri-plugin-system-info-api"
2025-11-08 13:24:00 +08:00
import { invoke } from "@tauri-apps/api/core"
2025-11-08 13:28:41 +08:00
import useSWR, { useSWRConfig } from "swr"
2025-11-05 13:20:50 +08:00
2024-09-20 23:15:42 +08:00
export default function Page() {
return (
2025-11-05 02:24:17 +08:00
<section className="flex flex-col gap-4 overflow-hidden rounded-lg">
2025-11-05 13:20:50 +08:00
<div className="flex flex-col h-full gap-4 overflow-hidden hide-scrollbar">
<Card className="overflow-hidden">
2025-11-05 02:24:17 +08:00
<CardHeader>
<CardIcon type="menu">
<SettingConfig />
</CardIcon>
<CardTool>
{/* <ToolButton>
<UploadOne />
</ToolButton> */}
2025-11-08 13:24:00 +08:00
<HardwareInfo />
2025-11-05 02:24:17 +08:00
</CardTool>
</CardHeader>
2025-11-05 02:24:17 +08:00
<CardBody>
2025-11-08 13:24:00 +08:00
<HardwareInfoContent />
2025-11-05 02:24:17 +08:00
</CardBody>
</Card>
</div>
</section>
)
}
function HardwareInfo() {
2025-11-08 13:28:41 +08:00
const { mutate } = useSWRConfig()
2025-11-08 13:24:00 +08:00
return (
<ToolButton
onClick={() => {
// 使用 SWR 的 mutate 来刷新数据
mutate("/api/hardware-info")
}}
>
2025-11-08 13:24:00 +08:00
<Refresh />
</ToolButton>
)
}
2025-11-08 13:24:00 +08:00
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 {
2025-11-08 16:34:37 +08:00
capacity?: number // 容量(字节)
manufacturer?: string
2025-11-08 16:34:37 +08:00
speed?: number // MHz实际频率 ConfiguredClockSpeed
default_speed?: number // MHz默认频率 Speed如果存在
}
interface MonitorInfo {
name?: string
refresh_rate?: number // Hz
resolution_width?: number
resolution_height?: number
2025-11-08 13:24:00 +08:00
}
2025-11-08 16:34:37 +08:00
interface MotherboardInfo {
manufacturer?: string // 制造商
model?: string // 型号
version?: string
}
2025-11-08 13:28:41 +08:00
interface HardwareData {
allSysData: AllSystemInfo
computerInfo: ComputerInfo
gpuInfo: GpuInfo | null
memoryInfo: MemoryInfo[]
monitorInfo: MonitorInfo[]
2025-11-08 16:34:37 +08:00
motherboardInfo: MotherboardInfo | null
2025-11-08 13:28:41 +08:00
}
2025-11-08 13:24:00 +08:00
2025-11-08 13:28:41 +08:00
// 硬件信息 fetcher
const hardwareInfoFetcher = async (): Promise<HardwareData> => {
2025-11-08 16:34:37 +08:00
// 并行获取系统信息、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
}),
])
2025-11-08 13:28:41 +08:00
console.log("系统信息:", sys)
console.log("PowerShell 信息:", computerInfoData)
console.log("GPU 信息:", gpuInfoData)
console.log("内存信息:", memoryInfoData)
console.log("显示器信息:", monitorInfoData)
2025-11-08 16:34:37 +08:00
console.log("主板信息:", motherboardInfoData)
2025-11-08 13:28:41 +08:00
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]))
}
2025-11-08 13:24:00 +08:00
}
2025-11-08 13:28:41 +08:00
return {
allSysData: sys,
computerInfo: computerInfoData,
gpuInfo: gpuInfoData,
memoryInfo: memoryInfoData,
monitorInfo: monitorInfoData,
2025-11-08 16:34:37 +08:00
motherboardInfo: motherboardInfoData,
2025-11-08 13:28:41 +08:00
}
}
2025-11-08 13:28:41 +08:00
function HardwareInfoContent() {
2025-11-08 16:34:37 +08:00
const { data, isLoading, isValidating } = useSWR<HardwareData>("/api/hardware-info", hardwareInfoFetcher, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
dedupingInterval: 5 * 60 * 1000, // 5分钟内相同请求去重
})
2025-11-08 13:28:41 +08:00
const allSysData = data?.allSysData
const computerInfo = data?.computerInfo || {}
const gpuInfo = data?.gpuInfo
const memoryInfo = data?.memoryInfo || []
const monitorInfo = data?.monitorInfo || []
2025-11-08 16:34:37 +08:00
const motherboardInfo = data?.motherboardInfo
2025-11-08 13:24:00 +08:00
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
2025-11-08 13:24:00 +08:00
// 计算所有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
2025-11-08 13:24:00 +08:00
2025-11-08 16:34:37 +08:00
// 如果正在加载或正在验证(包括刷新),显示 Skeleton 骨架屏
if (isLoading || isValidating) {
2025-11-08 13:24:00 +08:00
return (
<div className="flex flex-col gap-4">
{/* 系统信息 Skeleton */}
<div className="flex flex-col gap-2">
<Skeleton className="w-24 h-5 rounded" />
2025-11-08 13:24:00 +08:00
<div className="flex flex-wrap gap-2">
<Skeleton className="w-48 h-8 rounded-full" />
<Skeleton className="w-32 h-8 rounded-full" />
2025-11-08 13:24:00 +08:00
</div>
</div>
{/* CPU 信息 Skeleton */}
2025-11-08 13:24:00 +08:00
<div className="flex flex-col gap-2">
<Skeleton className="w-20 h-5 rounded" />
2025-11-08 13:24:00 +08:00
<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" />
2025-11-08 13:24:00 +08:00
</div>
</div>
{/* 内存信息 Skeleton */}
2025-11-08 13:24:00 +08:00
<div className="flex flex-col gap-2">
<Skeleton className="w-16 h-5 rounded" />
2025-11-08 13:24:00 +08:00
<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" />
2025-11-08 13:24:00 +08:00
</div>
</div>
{/* GPU 信息 Skeleton */}
2025-11-08 13:24:00 +08:00
<div className="flex flex-col gap-2">
<Skeleton className="w-16 h-5 rounded" />
2025-11-08 13:24:00 +08:00
<div className="flex flex-wrap gap-2">
<Skeleton className="h-8 rounded-full w-52" />
2025-11-08 13:24:00 +08:00
</div>
</div>
{/* 主板信息 Skeleton */}
2025-11-08 13:24:00 +08:00
<div className="flex flex-col gap-2">
<Skeleton className="w-16 h-5 rounded" />
2025-11-08 13:24:00 +08:00
<div className="flex flex-wrap gap-2">
<Skeleton className="w-40 h-8 rounded-full" />
<Skeleton className="h-8 rounded-full w-36" />
2025-11-08 13:24:00 +08:00
</div>
</div>
</div>
)
}
return (
2025-11-08 13:24:00 +08:00
<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>
2025-11-08 13:24:00 +08:00
{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>
2025-11-08 16:34:37 +08:00
{/* 第一行:总容量和已用内存 */}
2025-11-08 13:24:00 +08:00
<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>
2025-11-08 16:34:37 +08:00
{/* 每个内存条的详细信息 */}
{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>
)}
2025-11-08 13:24:00 +08:00
</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 &&
2025-11-08 13:24:00 +08:00
(() => {
const gpuComponents = allSysData.components.filter(
(comp) =>
comp.label?.toLowerCase().includes("gpu") ||
comp.label?.toLowerCase().includes("graphics") ||
comp.label?.toLowerCase().includes("显卡")
2025-11-08 13:24:00 +08:00
)
2025-11-08 13:24:00 +08:00
if (gpuComponents.length === 0) return null
2025-11-08 13:24:00 +08:00
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 || "未知"}
2025-11-08 13:24:00 +08:00
{gpu.temperature !== undefined && (
<span className="ml-1">
({gpu.temperature}°C{gpu.max !== undefined ? ` / ${gpu.max}°C` : ""})
</span>
2025-11-08 13:24:00 +08:00
)}
</Chip>
))}
</div>
</div>
)
})()
)}
{/* 主板信息 */}
2025-11-08 16:34:37 +08:00
{(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">
2025-11-08 16:34:37 +08:00
{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>
2025-11-08 16:34:37 +08:00
<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>
)}
2025-11-08 13:24:00 +08:00
{/* 电池信息 */}
{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>
2025-11-08 13:24:00 +08:00
)}
</Chip>
))}
</div>
</div>
)}
</div>
)
2024-09-20 23:15:42 +08:00
}