2025-03-13 03:41:21 +08:00
|
|
|
|
"use client"
|
2025-03-13 03:44:48 +08:00
|
|
|
|
import {
|
|
|
|
|
|
Card,
|
|
|
|
|
|
CardBody,
|
|
|
|
|
|
CardHeader,
|
|
|
|
|
|
CardIcon,
|
|
|
|
|
|
CardTool,
|
|
|
|
|
|
} from "@/components/window/Card"
|
2025-03-13 03:41:21 +08:00
|
|
|
|
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"
|
2025-03-21 18:00:16 +08:00
|
|
|
|
// import { version } from "@tauri-apps/plugin-os"
|
2025-03-13 03:44:48 +08:00
|
|
|
|
import { useEffect, useState } from "react"
|
|
|
|
|
|
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-05 13:20:50 +08:00
|
|
|
|
|
2024-09-20 23:15:42 +08:00
|
|
|
|
export default function Page() {
|
2025-03-13 03:41:21 +08:00
|
|
|
|
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-03-13 03:41:21 +08:00
|
|
|
|
|
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>
|
2025-03-13 03:41:21 +08:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function HardwareInfo() {
|
2025-11-08 13:24:00 +08:00
|
|
|
|
return (
|
|
|
|
|
|
<ToolButton onClick={() => {
|
|
|
|
|
|
// 触发刷新事件
|
|
|
|
|
|
window.dispatchEvent(new CustomEvent('refresh-hardware-info'))
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<Refresh /> 刷新
|
|
|
|
|
|
</ToolButton>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-03-21 16:02:47 +08:00
|
|
|
|
|
2025-11-08 13:24:00 +08:00
|
|
|
|
interface ComputerInfo {
|
|
|
|
|
|
OsName?: string
|
|
|
|
|
|
OSDisplayVersion?: string
|
|
|
|
|
|
BiosSMBIOSBIOSVersion?: string
|
|
|
|
|
|
CsManufacturer?: string
|
|
|
|
|
|
CsName?: string
|
|
|
|
|
|
}
|
2025-03-13 03:41:21 +08:00
|
|
|
|
|
2025-11-08 13:24:00 +08:00
|
|
|
|
function HardwareInfoContent() {
|
|
|
|
|
|
const [allSysData, setAllSysData] = useState<AllSystemInfo>()
|
|
|
|
|
|
const [computerInfo, setComputerInfo] = useState<ComputerInfo>({})
|
|
|
|
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
|
|
|
|
|
|
|
|
const fetchData = async () => {
|
|
|
|
|
|
setLoading(true)
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 并行获取系统信息和 PowerShell 信息
|
|
|
|
|
|
const [sys, computerInfoData] = await Promise.all([
|
|
|
|
|
|
allSysInfo(),
|
|
|
|
|
|
invoke<ComputerInfo>("get_computer_info").catch((error) => {
|
|
|
|
|
|
console.error("获取 PowerShell 信息失败:", error)
|
|
|
|
|
|
return {} as ComputerInfo
|
|
|
|
|
|
})
|
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
|
|
console.log("系统信息:", sys)
|
|
|
|
|
|
console.log("PowerShell 信息:", computerInfoData)
|
|
|
|
|
|
|
|
|
|
|
|
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-03-13 03:41:21 +08:00
|
|
|
|
setAllSysData(sys)
|
2025-11-08 13:24:00 +08:00
|
|
|
|
setComputerInfo(computerInfoData)
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error("获取系统信息失败:", error)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false)
|
2025-03-13 03:41:21 +08:00
|
|
|
|
}
|
2025-11-08 13:24:00 +08:00
|
|
|
|
}
|
2025-03-13 03:41:21 +08:00
|
|
|
|
|
2025-11-08 13:24:00 +08:00
|
|
|
|
useEffect(() => {
|
2025-03-13 03:44:48 +08:00
|
|
|
|
void fetchData()
|
2025-11-08 13:24:00 +08:00
|
|
|
|
|
|
|
|
|
|
// 监听刷新事件
|
|
|
|
|
|
const handleRefresh = () => {
|
|
|
|
|
|
void fetchData()
|
|
|
|
|
|
}
|
|
|
|
|
|
window.addEventListener('refresh-hardware-info', handleRefresh)
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
window.removeEventListener('refresh-hardware-info', handleRefresh)
|
|
|
|
|
|
}
|
2025-03-13 03:41:21 +08:00
|
|
|
|
}, [])
|
|
|
|
|
|
|
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`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果 PowerShell 提供了 OSDisplayVersion,直接使用;否则尝试从 kernel_version 推断
|
|
|
|
|
|
const windowsVersionCode = computerInfo.OSDisplayVersion || null
|
|
|
|
|
|
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 (loading) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
|
|
{/* 系统信息 Skeleton */}
|
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
|
<Skeleton className="h-5 w-24 rounded" />
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
|
<Skeleton className="h-8 w-48 rounded-full" />
|
|
|
|
|
|
<Skeleton className="h-8 w-32 rounded-full" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 主板信息 Skeleton */}
|
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
|
<Skeleton className="h-5 w-16 rounded" />
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
|
<Skeleton className="h-8 w-40 rounded-full" />
|
|
|
|
|
|
<Skeleton className="h-8 w-36 rounded-full" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* CPU 信息 Skeleton */}
|
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
|
<Skeleton className="h-5 w-20 rounded" />
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
|
<Skeleton className="h-8 w-56 rounded-full" />
|
|
|
|
|
|
<Skeleton className="h-8 w-32 rounded-full" />
|
|
|
|
|
|
<Skeleton className="h-8 w-28 rounded-full" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 内存信息 Skeleton */}
|
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
|
<Skeleton className="h-5 w-16 rounded" />
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
|
<Skeleton className="h-8 w-36 rounded-full" />
|
|
|
|
|
|
<Skeleton className="h-8 w-40 rounded-full" />
|
|
|
|
|
|
<Skeleton className="h-8 w-36 rounded-full" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* GPU 信息 Skeleton */}
|
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
|
<Skeleton className="h-5 w-16 rounded" />
|
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
|
<Skeleton className="h-8 w-52 rounded-full" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-13 03:41:21 +08:00
|
|
|
|
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">
|
|
|
|
|
|
{computerInfo.OsName && (
|
|
|
|
|
|
<Chip>
|
|
|
|
|
|
<span className="font-medium">系统:</span> {computerInfo.OsName}
|
|
|
|
|
|
{windowsVersionCode && (
|
|
|
|
|
|
<span className="ml-1 text-primary">({windowsVersionCode})</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</Chip>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{!computerInfo.OsName && (
|
|
|
|
|
|
<Chip>
|
|
|
|
|
|
<span className="font-medium">系统:</span> {allSysData?.name || "未知"} {allSysData?.os_version || ""}
|
|
|
|
|
|
{allSysData?.kernel_version && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{" "}{allSysData.kernel_version}
|
|
|
|
|
|
{windowsVersionCode && (
|
|
|
|
|
|
<span className="ml-1 text-primary">({windowsVersionCode})</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</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>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 主板信息 */}
|
|
|
|
|
|
{(computerInfo.CsManufacturer || 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">
|
|
|
|
|
|
{computerInfo.CsManufacturer && (
|
|
|
|
|
|
<Chip>
|
|
|
|
|
|
<span className="font-medium">制造商:</span> {computerInfo.CsManufacturer}
|
|
|
|
|
|
</Chip>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{computerInfo.BiosSMBIOSBIOSVersion && (
|
|
|
|
|
|
<Chip>
|
|
|
|
|
|
<span className="font-medium">BIOS版本:</span> {computerInfo.BiosSMBIOSBIOSVersion}
|
|
|
|
|
|
</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>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{allSysData.total_memory !== undefined && allSysData.used_memory !== undefined && (
|
|
|
|
|
|
<Chip>
|
|
|
|
|
|
<span className="font-medium">可用:</span> {formatBytes(allSysData.total_memory - allSysData.used_memory)}
|
|
|
|
|
|
</Chip>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* GPU 信息 */}
|
|
|
|
|
|
{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>
|
|
|
|
|
|
)
|
|
|
|
|
|
})()
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 电池信息 */}
|
|
|
|
|
|
{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>
|
|
|
|
|
|
)}
|
2025-03-13 03:41:21 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)
|
2024-09-20 23:15:42 +08:00
|
|
|
|
}
|