From 4c8322d96b5a78b4ff9db2eec2d80ef72f49ca51 Mon Sep 17 00:00:00 2001 From: Julian Freeman Date: Tue, 25 Nov 2025 23:35:54 -0400 Subject: [PATCH] stream cards --- src-tauri/src/main.rs | 459 +++++++++++++++----------------- src/App.vue | 599 ++++++++++++------------------------------ 2 files changed, 393 insertions(+), 665 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 43655ec..586b616 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -8,22 +8,13 @@ use serde::Serialize; use sysinfo::{System, Disks}; use wmi::{COMLibrary, WMIConnection}; use std::fs; -// 引入 chrono 用于时间计算和格式化 +// 引入 tauri::Emitter 用于发送事件 (Tauri v2) +use tauri::Emitter; use chrono::{Duration, FixedOffset, Local, NaiveDate, TimeZone}; -// --- 1. 数据结构 --- +// --- 1. 数据结构 (保持不变,用于序列化部分数据) --- -#[derive(Serialize)] -struct SystemHealthReport { - hardware: HardwareSummary, - storage: Vec, - events: Vec, - minidumps: MinidumpInfo, - drivers: Vec, - battery: Option, -} - -#[derive(Serialize)] +#[derive(Serialize, Clone)] // 增加 Clone trait 方便使用 struct HardwareSummary { cpu_name: String, sys_vendor: String, @@ -38,7 +29,7 @@ struct HardwareSummary { c_drive_used_gb: u64, } -#[derive(Serialize)] +#[derive(Serialize, Clone)] struct StorageDevice { model: String, health_status: String, @@ -46,7 +37,7 @@ struct StorageDevice { is_danger: bool, } -#[derive(Serialize)] +#[derive(Serialize, Clone)] struct SystemEvent { time_generated: String, event_id: u32, @@ -55,21 +46,21 @@ struct SystemEvent { analysis_hint: String, } -#[derive(Serialize)] +#[derive(Serialize, Clone)] struct MinidumpInfo { found: bool, count: usize, explanation: String, } -#[derive(Serialize)] +#[derive(Serialize, Clone)] struct DriverIssue { device_name: String, error_code: u32, description: String, } -#[derive(Serialize)] +#[derive(Serialize, Clone)] struct BatteryInfo { health_percentage: u32, is_ac_connected: bool, @@ -122,269 +113,255 @@ struct Win32_ComputerSystem { Model: Option, } -// --- 辅助函数:生成 WMI 查询用的时间字符串 --- -// WMI 要求的时间格式类似于: 20231125000000.000000+000 +// --- 辅助函数 --- fn get_wmi_query_time(days_ago: i64) -> String { let target_date = Local::now() - Duration::days(days_ago); - // 这里我们只生成前半部分 YYYYMMDDHHMMSS,WMI 字符串比较支持这种前缀 - // 或者生成完整格式 .000000+000 (简化处理,假设本地时区) target_date.format("%Y%m%d%H%M%S.000000+000").to_string() } -// --- 辅助函数:格式化显示用的时间 --- fn format_wmi_time(wmi_str: &str) -> String { if wmi_str.len() < 25 { return wmi_str.to_string(); } - let year = wmi_str[0..4].parse::().unwrap_or(1970); let month = wmi_str[4..6].parse::().unwrap_or(1); let day = wmi_str[6..8].parse::().unwrap_or(1); let hour = wmi_str[8..10].parse::().unwrap_or(0); let min = wmi_str[10..12].parse::().unwrap_or(0); let sec = wmi_str[12..14].parse::().unwrap_or(0); - let sign = &wmi_str[21..22]; let offset_val = wmi_str[22..25].parse::().unwrap_or(0); let offset_mins = if sign == "-" { -offset_val } else { offset_val }; - let offset = FixedOffset::east_opt(offset_mins * 60).unwrap_or(FixedOffset::east_opt(0).unwrap()); - let naive_date = NaiveDate::from_ymd_opt(year, month, day).unwrap_or(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()); let naive_dt = naive_date.and_hms_opt(hour, min, sec).unwrap_or_default(); - match offset.from_local_datetime(&naive_dt).single() { - Some(dt) => { - dt.with_timezone(&Local).format("%Y-%m-%d %H:%M:%S").to_string() - }, + Some(dt) => dt.with_timezone(&Local).format("%Y-%m-%d %H:%M:%S").to_string(), None => format!("{}-{:02}-{:02} {:02}:{:02}:{:02}", year, month, day, hour, min, sec) } } -// --- 核心逻辑 --- +// --- 核心逻辑:分步流式传输 --- #[tauri::command] -async fn run_diagnosis() -> Result { - let report = tokio::task::spawn_blocking(|| { - // 1. 高效初始化:只创建空实例,不扫描进程 - let mut sys = System::new(); - sys.refresh_memory(); - sys.refresh_cpu(); +async fn run_diagnosis(window: tauri::Window) -> Result<(), String> { + // 使用 spawn 在后台线程运行,避免阻塞 Tauri 主线程 + // 注意:这里不等待 join,而是让它在后台跑,通过 window.emit 发送进度 + std::thread::spawn(move || { + // 1. 硬件概览 (最快) + { + let mut sys = System::new(); + sys.refresh_memory(); + sys.refresh_cpu(); + + let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).ok(); + + let mut bios_ver = "Unknown".to_string(); + let mut mobo_vendor = "Unknown".to_string(); + let mut mobo_product = "Unknown".to_string(); + let mut sys_vendor = "Unknown".to_string(); + let mut sys_product = "Unknown".to_string(); + if let Some(con) = &wmi_con { + if let Ok(results) = con.raw_query::("SELECT SMBIOSBIOSVersion FROM Win32_BIOS") { + if let Some(bios) = results.first() { bios_ver = bios.SMBIOSBIOSVersion.clone().unwrap_or_default(); } + } + if let Ok(results) = con.raw_query::("SELECT Manufacturer, Product FROM Win32_BaseBoard") { + if let Some(board) = results.first() { + mobo_vendor = board.Manufacturer.clone().unwrap_or_default(); + mobo_product = board.Product.clone().unwrap_or_default(); + } + } + if let Ok(results) = con.raw_query::("SELECT Manufacturer, Model FROM Win32_ComputerSystem") { + if let Some(cs) = results.first() { + sys_vendor = cs.Manufacturer.clone().unwrap_or_default(); + sys_product = cs.Model.clone().unwrap_or_default(); + } + } + } + + // C盘 + let mut c_total = 0u64; + let mut c_used = 0u64; + let disks = Disks::new_with_refreshed_list(); + for disk in &disks { + if disk.mount_point().to_string_lossy().starts_with("C:") { + c_total = disk.total_space() / 1024 / 1024 / 1024; + let free = disk.available_space() / 1024 / 1024 / 1024; + c_used = c_total - free; + break; + } + } + + let cpu_brand = if let Some(cpu) = sys.cpus().first() { + cpu.brand().trim().to_string() + } else { + "Unknown CPU".to_string() + }; + + let hardware = HardwareSummary { + cpu_name: cpu_brand, + sys_vendor, sys_product, mobo_vendor, mobo_product, + memory_total_gb: sys.total_memory() / 1024 / 1024 / 1024, + memory_used_gb: sys.used_memory() / 1024 / 1024 / 1024, + os_version: System::long_os_version().unwrap_or("Unknown".to_string()), + bios_version: bios_ver, + c_drive_total_gb: c_total, + c_drive_used_gb: c_used, + }; + + // 发送硬件事件 + let _ = window.emit("report-hardware", hardware); + } + + // WMI 连接复用 (如果需要) 或者重新建立 let wmi_con = WMIConnection::new(COMLibrary::new().unwrap()).ok(); - // 1. 硬件基础信息 - let mut bios_ver = "Unknown".to_string(); - let mut mobo_vendor = "Unknown".to_string(); - let mut mobo_product = "Unknown".to_string(); - let mut sys_vendor = "Unknown".to_string(); - let mut sys_product = "Unknown".to_string(); - - if let Some(con) = &wmi_con { - if let Ok(results) = con.raw_query::("SELECT SMBIOSBIOSVersion FROM Win32_BIOS") { - if let Some(bios) = results.first() { - bios_ver = bios.SMBIOSBIOSVersion.clone().unwrap_or("Unknown".to_string()); - } - } - if let Ok(results) = con.raw_query::("SELECT Manufacturer, Product FROM Win32_BaseBoard") { - if let Some(board) = results.first() { - mobo_vendor = board.Manufacturer.clone().unwrap_or("Unknown".to_string()); - mobo_product = board.Product.clone().unwrap_or("Unknown".to_string()); - } - } - if let Ok(results) = con.raw_query::("SELECT Manufacturer, Model FROM Win32_ComputerSystem") { - if let Some(cs) = results.first() { - sys_vendor = cs.Manufacturer.clone().unwrap_or("Unknown".to_string()); - sys_product = cs.Model.clone().unwrap_or("Unknown".to_string()); - } - } - } - - // --- C 盘空间检查 --- - let mut c_total = 0u64; - let mut c_used = 0u64; - let disks = Disks::new_with_refreshed_list(); - for disk in &disks { - if disk.mount_point().to_string_lossy().starts_with("C:") { - c_total = disk.total_space() / 1024 / 1024 / 1024; - let free = disk.available_space() / 1024 / 1024 / 1024; - c_used = c_total - free; - break; - } - } - - let cpu_brand = if let Some(cpu) = sys.cpus().first() { - cpu.brand().trim().to_string() - } else { - "Unknown CPU".to_string() - }; - - let hardware = HardwareSummary { - cpu_name: cpu_brand, - mobo_vendor, - mobo_product, - sys_vendor, - sys_product, - memory_total_gb: sys.total_memory() / 1024 / 1024 / 1024, - memory_used_gb: sys.used_memory() / 1024 / 1024 / 1024, - os_version: System::long_os_version().unwrap_or("Unknown".to_string()), - bios_version: bios_ver, - c_drive_total_gb: c_total, - c_drive_used_gb: c_used, - }; - - // 2. 存储设备健康度 - let mut storage = Vec::new(); - if let Some(con) = &wmi_con { - let query = "SELECT Model, Status FROM Win32_DiskDrive"; - if let Ok(results) = con.raw_query::(query) { - for disk in results { - let status = disk.Status.unwrap_or("Unknown".to_string()); - let (explanation, is_danger) = match status.as_str() { - "OK" => ("健康".to_string(), false), - "Pred Fail" => ("预测即将损坏".to_string(), true), - _ => ("状态异常".to_string(), true), - }; - storage.push(StorageDevice { - model: disk.Model.unwrap_or("Generic Disk".to_string()), - health_status: status, - human_explanation: explanation, - is_danger, - }); - } - } - } - - // 3. 关键日志 (严格模式 + 时间优化 + 扩展) - let mut events = Vec::new(); - if let Some(con) = &wmi_con { - // [关键优化] 计算30天前的时间字符串 - let start_time_str = get_wmi_query_time(30); - - // [关键优化] SQL 中加入 TimeGenerated >= '...' 过滤,大幅减少扫描量 - let query = format!( - "SELECT TimeGenerated, EventCode, SourceName, Message FROM Win32_NTLogEvent WHERE Logfile = 'System' AND TimeGenerated >= '{}' AND (EventCode = 41 OR EventCode = 18 OR EventCode = 19 OR EventCode = 7 OR EventCode = 1001 OR EventCode = 4101)", - start_time_str - ); - - if let Ok(results) = con.raw_query::(&query) { - for event in results { - let mut is_target_event = false; - let mut hint = String::new(); - - // 严格来源校验 - if event.EventCode == 41 && event.SourceName == "Microsoft-Windows-Kernel-Power" { - is_target_event = true; - hint = "系统意外断电 (电源/强关)".to_string(); - } else if (event.EventCode == 18 || event.EventCode == 19) && event.SourceName == "Microsoft-Windows-WHEA-Logger" { - is_target_event = true; - hint = "WHEA 硬件致命错误 (CPU/超频/PCIe)".to_string(); - } else if event.EventCode == 7 && (event.SourceName == "Disk" || event.SourceName == "disk") { - is_target_event = true; - hint = "硬盘出现坏道 (Disk Bad Block)".to_string(); - } else if event.EventCode == 1001 && event.SourceName == "BugCheck" { - is_target_event = true; - hint = "系统发生蓝屏死机 (BSOD)".to_string(); - } else if event.EventCode == 4101 && event.SourceName == "Display" { - is_target_event = true; - hint = "显卡驱动停止响应并已恢复 (TDR)".to_string(); - } - - if is_target_event { - events.push(SystemEvent { - time_generated: format_wmi_time(&event.TimeGenerated), - event_id: event.EventCode, - source: event.SourceName, - message: event.Message.unwrap_or("无详细信息".to_string()), - analysis_hint: hint, - }); - } - - // 显示最近 10 条(因为加了时间范围,可以多显示一点) - if events.len() >= 10 { - break; - } - } - } - } - - // 4. Minidump - let mut minidump = MinidumpInfo { found: false, count: 0, explanation: "无蓝屏记录".to_string() }; - if let Ok(entries) = fs::read_dir("C:\\Windows\\Minidump") { - let count = entries.count(); - if count > 0 { - minidump = MinidumpInfo { - found: true, - count, - explanation: format!("发现 {} 次蓝屏崩溃", count), - }; - } - } - - // 5. 驱动设备检查 - let mut driver_issues = Vec::new(); - if let Some(con) = &wmi_con { - let query = "SELECT Name, ConfigManagerErrorCode FROM Win32_PnPEntity WHERE ConfigManagerErrorCode <> 0"; - if let Ok(results) = con.raw_query::(query) { - for dev in results { - let code = dev.ConfigManagerErrorCode.unwrap_or(0); - let desc = match code { - 10 => "设备无法启动 (Code 10)。通常是驱动不兼容。", - 28 => "驱动程序未安装 (Code 28)。", - 43 => "硬件报告问题已被停止 (Code 43)。显卡常见,可能虚焊。", - _ => "设备状态异常。", - }; - driver_issues.push(DriverIssue { - device_name: dev.Name.unwrap_or("未知设备".to_string()), - error_code: code, - description: desc.to_string(), - }); - } - } - } - - // 6. 电池健康度 - let mut battery_info = None; - if let Some(con) = &wmi_con { - if let Ok(results) = con.raw_query::("SELECT DesignCapacity, FullChargeCapacity, BatteryStatus FROM Win32_Battery") { - if let Some(bat) = results.first() { - let design = bat.DesignCapacity.unwrap_or(0); - let full = bat.FullChargeCapacity.unwrap_or(0); - let status = bat.BatteryStatus.unwrap_or(0); - - if design > 0 { - let health = ((full as f64 / design as f64) * 100.0) as u32; - let ac_plugged = status == 2 || status == 6 || status == 1; - - let explain = if health < 60 { - "电池老化严重,建议更换,否则可能导致供电不稳。".to_string() - } else { - "电池状态良好。".to_string() + // 2. 存储设备 (较快) + { + let mut storage = Vec::new(); + if let Some(con) = &wmi_con { + let query = "SELECT Model, Status FROM Win32_DiskDrive"; + if let Ok(results) = con.raw_query::(query) { + for disk in results { + let status = disk.Status.unwrap_or("Unknown".to_string()); + let (explanation, is_danger) = match status.as_str() { + "OK" => ("健康".to_string(), false), + "Pred Fail" => ("预测即将损坏".to_string(), true), + _ => ("状态异常".to_string(), true), }; - - battery_info = Some(BatteryInfo { - health_percentage: health, - is_ac_connected: ac_plugged, - explanation: explain, + storage.push(StorageDevice { + model: disk.Model.unwrap_or("Generic Disk".to_string()), + health_status: status, + human_explanation: explanation, + is_danger, }); } } } + let _ = window.emit("report-storage", storage); } - SystemHealthReport { - hardware, - storage, - events, - minidumps: minidump, - drivers: driver_issues, - battery: battery_info + // 3. 驱动 (快) + { + let mut driver_issues = Vec::new(); + if let Some(con) = &wmi_con { + let query = "SELECT Name, ConfigManagerErrorCode FROM Win32_PnPEntity WHERE ConfigManagerErrorCode <> 0"; + if let Ok(results) = con.raw_query::(query) { + for dev in results { + let code = dev.ConfigManagerErrorCode.unwrap_or(0); + let desc = match code { + 10 => "设备无法启动 (Code 10)。通常是驱动不兼容。", + 28 => "驱动程序未安装 (Code 28)。", + 43 => "硬件报告问题已被停止 (Code 43)。显卡常见,可能虚焊。", + _ => "设备状态异常。", + }; + driver_issues.push(DriverIssue { + device_name: dev.Name.unwrap_or("未知设备".to_string()), + error_code: code, + description: desc.to_string(), + }); + } + } + } + let _ = window.emit("report-drivers", driver_issues); } - }) - .await - .map_err(|e| e.to_string())?; - Ok(report) + // 4. Minidump (快) + { + let mut minidump = MinidumpInfo { found: false, count: 0, explanation: "无蓝屏记录".to_string() }; + if let Ok(entries) = fs::read_dir("C:\\Windows\\Minidump") { + let count = entries.count(); + if count > 0 { + minidump = MinidumpInfo { + found: true, + count, + explanation: format!("发现 {} 次蓝屏崩溃", count), + }; + } + } + let _ = window.emit("report-minidumps", minidump); + } + + // 5. 电池 (快) + { + let mut battery_info = None; + if let Some(con) = &wmi_con { + if let Ok(results) = con.raw_query::("SELECT DesignCapacity, FullChargeCapacity, BatteryStatus FROM Win32_Battery") { + if let Some(bat) = results.first() { + let design = bat.DesignCapacity.unwrap_or(0); + let full = bat.FullChargeCapacity.unwrap_or(0); + let status = bat.BatteryStatus.unwrap_or(0); + if design > 0 { + let health = ((full as f64 / design as f64) * 100.0) as u32; + let ac_plugged = status == 2 || status == 6 || status == 1; + let explain = if health < 60 { + "电池老化严重,建议更换,否则可能导致供电不稳。".to_string() + } else { + "电池状态良好。".to_string() + }; + battery_info = Some(BatteryInfo { + health_percentage: health, + is_ac_connected: ac_plugged, + explanation: explain, + }); + } + } + } + } + // 即使是 None 也要发,以便前端知道检查完成了 + // 这里为了简化,直接发 Option + let _ = window.emit("report-battery", battery_info); + } + + // 6. 日志 (最慢,放在最后) + { + let mut events = Vec::new(); + if let Some(con) = &wmi_con { + let start_time_str = get_wmi_query_time(30); + let query = format!( + "SELECT TimeGenerated, EventCode, SourceName, Message FROM Win32_NTLogEvent WHERE Logfile = 'System' AND TimeGenerated >= '{}' AND (EventCode = 41 OR EventCode = 18 OR EventCode = 19 OR EventCode = 7 OR EventCode = 1001 OR EventCode = 4101)", + start_time_str + ); + + if let Ok(results) = con.raw_query::(&query) { + for event in results { + let mut is_target_event = false; + let mut hint = String::new(); + + if event.EventCode == 41 && event.SourceName == "Microsoft-Windows-Kernel-Power" { + is_target_event = true; hint = "系统意外断电 (电源/强关)".to_string(); + } else if (event.EventCode == 18 || event.EventCode == 19) && event.SourceName == "Microsoft-Windows-WHEA-Logger" { + is_target_event = true; hint = "WHEA 硬件致命错误 (CPU/超频/PCIe)".to_string(); + } else if event.EventCode == 7 && (event.SourceName == "Disk" || event.SourceName == "disk") { + is_target_event = true; hint = "硬盘出现坏道 (Disk Bad Block)".to_string(); + } else if event.EventCode == 1001 && event.SourceName == "BugCheck" { + is_target_event = true; hint = "系统发生蓝屏死机 (BSOD)".to_string(); + } else if event.EventCode == 4101 && event.SourceName == "Display" { + is_target_event = true; hint = "显卡驱动停止响应并已恢复 (TDR)".to_string(); + } + + if is_target_event { + events.push(SystemEvent { + time_generated: format_wmi_time(&event.TimeGenerated), + event_id: event.EventCode, + source: event.SourceName, + message: event.Message.unwrap_or("无详细信息".to_string()), + analysis_hint: hint, + }); + } + if events.len() >= 10 { break; } + } + } + } + let _ = window.emit("report-events", events); + } + + // 7. 全部完成信号 + let _ = window.emit("diagnosis-finished", ()); + }); + + Ok(()) } fn main() { diff --git a/src/App.vue b/src/App.vue index 85fb6d1..8ce1ea2 100644 --- a/src/App.vue +++ b/src/App.vue @@ -17,53 +17,39 @@ - - - + -
- ⚠️ {{ errorMsg }} -
+
⚠️ {{ errorMsg }}
+ +
✅ 扫描已完成!所有项目检查完毕。
-
+
- -
+ +

🖥️ 硬件概览与资源占用

-
CPU 型号 {{ report.hardware.cpu_name }}
-
整机型号 (System) - - {{ report.hardware.sys_vendor }} {{ report.hardware.sys_product }} - + {{ report.hardware.sys_vendor }} {{ report.hardware.sys_product }}
-
主板型号 (BaseBoard) - - {{ report.hardware.mobo_vendor }} {{ report.hardware.mobo_product }} - + {{ report.hardware.mobo_vendor }} {{ report.hardware.mobo_product }}
BIOS 版本 @@ -75,49 +61,38 @@
-
-
C盘空间 (已用 {{ report.hardware.c_drive_used_gb }}GB / 总计 {{ report.hardware.c_drive_total_gb }}GB) {{ calculatePercent(report.hardware.c_drive_used_gb, report.hardware.c_drive_total_gb) }}%
-
-
+
- -
内存占用 (已用 {{ report.hardware.memory_used_gb }}GB / 总计 {{ report.hardware.memory_total_gb }}GB) {{ calculatePercent(report.hardware.memory_used_gb, report.hardware.memory_total_gb) }}%
-
-
+
- -
+ +

🔌 驱动与设备状态

{{ report.drivers.length > 0 ? '发现异常' : '正常' }}
- -
- ✅ 设备管理器中未发现带有黄色感叹号或错误的设备。 -
+
✅ 设备管理器中未发现带有黄色感叹号或错误的设备。
@@ -129,8 +104,8 @@
- -
+ +

🔋 电池健康度 (寿命)

@@ -144,17 +119,15 @@ {{ report.battery.health_percentage }}%
-
-
+

{{ report.battery.explanation }}

- -
+ +

💾 硬盘健康度 (S.M.A.R.T)

@@ -172,36 +145,30 @@
-
+

☠️ 蓝屏死机记录 (BSOD)

{{ report.minidumps.explanation }}

-

- 💡 建议使用 BlueScreenView 或 WinDbg 工具打开 C:\Windows\Minidump 文件夹下的 .dmp 文件进行深入分析。 -

+

💡 建议使用 BlueScreenView 或 WinDbg 工具打开 C:\Windows\Minidump 文件夹下的 .dmp 文件进行深入分析。

- -
+ +

⚡ 关键供电与硬件日志 (Event Log)

- -
- ✅ 近期日志中未发现 Kernel-Power(41) 或 WHEA(18/19) 等致命错误。 -
+
✅ 近期日志中未发现 Kernel-Power(41) 或 WHEA(18/19) 等致命错误。
ID: {{ evt.event_id }} {{ evt.source }} - {{ formatTime(evt.time_generated) }} + {{ evt.time_generated }}

{{ evt.analysis_hint }}

-

{{ evt.message }}

@@ -212,70 +179,67 @@ - - - \ No newline at end of file