diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 79c126f..934b24d 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -9,7 +9,6 @@ use sysinfo::{System, Disks}; use wmi::{COMLibrary, WMIConnection}; use std::fs; use std::path::Path; -// [修复] 移除了未使用的 Write use std::io::Read; use std::process::Command; use tauri::Emitter; @@ -232,16 +231,15 @@ where T: Deref }) } -// [修复] BlueScreenView 外部调用分析 (适配新的命令行格式和 CSV 列) +// BlueScreenView 外部调用分析 fn analyze_with_bluescreenview(dump_path: &Path) -> Result { let bsv_exe = "BlueScreenView.exe"; let mut temp_csv_path = std::env::temp_dir(); temp_csv_path.push(format!("bsod_report_{}.csv", chrono::Utc::now().timestamp_millis())); - // [修改] 命令行格式: BlueScreenView.exe /scomma let status = Command::new(bsv_exe) - .arg(dump_path.to_string_lossy().to_string()) // 直接传文件路径 + .arg(dump_path.to_string_lossy().to_string()) .arg("/scomma") .arg(temp_csv_path.to_string_lossy().to_string()) .status(); @@ -250,35 +248,26 @@ fn analyze_with_bluescreenview(dump_path: &Path) -> Result 15 { - // 第一行就是数据,不需要跳过 Header + if &record[0] == "Dump File" { continue; } + let bug_check_string = &record[2]; let bug_check_code = &record[3]; let caused_by_driver = &record[8]; - let crash_addr = &record[15]; + let crash_addr = if record.len() > 15 { &record[15] } else { "N/A" }; let (human, recommend) = translate_bugcheck_str(bug_check_code); - // 优先使用 BlueScreenView 识别出的 Bug Check String let final_reason = if !bug_check_string.is_empty() { bug_check_string.to_string() } else { human }; return Ok(BsodAnalysisReport { @@ -375,7 +364,7 @@ async fn analyze_minidump(filepath: String) -> Result) -> Result { if file_content.is_empty() { @@ -386,7 +375,6 @@ async fn analyze_minidump_bytes(file_content: Vec) -> Result { - // [修复] 修复 Error 类型转换 let native_result = Minidump::read(file_content.clone()) .map_err(|e| e.to_string()) .and_then(|dump| analyze_dump_data_native(dump)); @@ -394,7 +382,6 @@ async fn analyze_minidump_bytes(file_content: Vec) -> Result { // 直接回退 @@ -402,7 +389,6 @@ async fn analyze_minidump_bytes(file_content: Vec) -> Result return Err("无效的文件签名。请确认这是 .dmp 文件。".to_string()) } - // --- 回退流程:写入临时文件调用 BlueScreenView --- let mut temp_dump_path = std::env::temp_dir(); temp_dump_path.push(format!("temp_dump_{}.dmp", chrono::Utc::now().timestamp_millis())); @@ -582,7 +568,7 @@ async fn run_diagnosis(window: tauri::Window) -> Result<(), String> { analysis_hint: hint, }); } - if events.len() >= 10 { break; } + // [修改] 移除了数量限制,让它获取所有符合条件的日志 } } } diff --git a/src/App.vue b/src/App.vue index 78bc2d3..9b8ff93 100644 --- a/src/App.vue +++ b/src/App.vue @@ -21,6 +21,13 @@ > ☠️ 蓝屏分析 + - +
⚠️ {{ errorMsg }}
-
✅ 扫描完成
@@ -133,24 +139,25 @@ - -
-
-

⚡ 关键日志

-
-
-
-
- ID: {{ evt.event_id }} - {{ evt.source }} - {{ formatTime(evt.time_generated) }} -
-

{{ evt.analysis_hint }}

-

{{ evt.message }}

-
-
-
无致命错误日志。
-
+ + + + +
+
+

⚡ 系统日志概况

+ + {{ report.events && report.events.length > 0 ? `${report.events.length} 条记录` : '无异常' }} + +
+
+

+ 最近30天发现关键错误,点击查看详情。 +

+

系统运行稳定。

+
+
+ @@ -224,6 +231,41 @@ + +
+
+

系统关键日志 (近30天)

+
+ +
+
+ +
+
+ 正在读取日志记录... + 请先点击“全面体检”或“刷新日志”以获取数据。 +
+
+ ✅ 过去30天内未发现 Kernel-Power(41) 或 WHEA(18/19) 等致命错误。 +
+
+
+
+
+ ID: {{ evt.event_id }} + {{ formatTime(evt.time_generated) }} +
+ {{ evt.source }} +
+
+

{{ evt.analysis_hint }}

+

{{ evt.message }}

+
+
+
+
+
+ @@ -245,14 +287,15 @@ import { invoke } from '@tauri-apps/api/core'; import { listen } from '@tauri-apps/api/event'; // --- 状态管理 --- -const currentTab = ref('overview'); // 'overview' | 'bsod' +const currentTab = ref('overview'); // 'overview' | 'bsod' | 'logs' // 概览相关 const emptyReport = () => ({ hardware: null, storage: null, events: null, minidumps: null, drivers: null, battery: null }); const report = ref(emptyReport()); const loading = ref(false); const errorMsg = ref(''); -const scanFinished = ref(false); +// [移除] scanFinished 状态变量,因为不再需要显示 div 提示 +// const scanFinished = ref(false); const fileInput = ref(null); let unlistenFns = []; @@ -270,7 +313,7 @@ let toastTimer = null; const hasStorageDanger = computed(() => report.value?.storage?.some(d => d.is_danger) || false); const isReportValid = computed(() => report.value && (report.value.hardware || report.value.storage)); -// --- 辅助函数 --- +// --- 辅助函数 (保留) --- function calculatePercent(used, total) { return (!total || total === 0) ? 0 : Math.round((used / total) * 100); } function getProgressStyle(used, total) { const percent = calculatePercent(used, total); @@ -280,7 +323,7 @@ function getProgressStyle(used, total) { function getBatteryColor(p) { return p < 50 ? '#ff4757' : (p < 80 ? '#ffa502' : '#2ed573'); } function formatTime(t) { return !t ? "Unknown Time" : t.replace('T', ' ').substring(0, 19); } -// [新增] JS日期格式化函数:yyyy-MM-dd HH:mm:ss +// JS日期格式化函数:yyyy-MM-dd HH:mm:ss function formatJsDate(date) { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); @@ -309,10 +352,13 @@ async function exportReport() { } catch (err) { triggerToast('导出失败', err.message, 'error'); } } function triggerImport() { fileInput.value.click(); } -function handleFileImport(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { try { const json = JSON.parse(e.target.result); if (json && (json.hardware || json.storage)) { report.value = json; scanFinished.value = false; triggerToast('导入成功', '已加载历史报告'); } else { triggerToast('导入失败', '文件格式错误', 'error'); } } catch (err) { triggerToast('解析失败', err.message, 'error'); } }; reader.readAsText(file); event.target.value = ''; } +function handleFileImport(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { try { const json = JSON.parse(e.target.result); if (json && (json.hardware || json.storage)) { report.value = json; triggerToast('导入成功', '已加载历史报告'); } else { triggerToast('导入失败', '文件格式错误', 'error'); } } catch (err) { triggerToast('解析失败', err.message, 'error'); } }; reader.readAsText(file); event.target.value = ''; } async function startScan() { - report.value = emptyReport(); errorMsg.value = ''; scanFinished.value = false; loading.value = true; + // 只有在从未扫描过时清空,或者强制刷新 + // report.value = emptyReport(); + errorMsg.value = ''; + loading.value = true; if (unlistenFns.length > 0) { for (const fn of unlistenFns) fn(); unlistenFns = []; } try { unlistenFns.push( @@ -322,7 +368,7 @@ async function startScan() { await listen('report-minidumps', (e) => report.value.minidumps = e.payload), await listen('report-battery', (e) => report.value.battery = e.payload), await listen('report-events', (e) => report.value.events = e.payload), - await listen('diagnosis-finished', () => { loading.value = false; scanFinished.value = true; triggerToast('扫描完成', '体检已结束', 'success'); }) + await listen('diagnosis-finished', () => { loading.value = false; triggerToast('体检完成', '所有项目检查完毕', 'success'); }) ); await invoke('run_diagnosis'); } catch (e) { loading.value = false; errorMsg.value = e; } @@ -470,6 +516,7 @@ body { margin: 0; padding: 0; background-color: #f4f6f9; overflow: hidden; } .badge-red { background-color: #ff4757; } .badge-green { background-color: #2ed573; } .badge-blue { background-color: #3498db; } .badge-gray { background-color: #95a5a6; } .code-tag { background: #ff4757; color: white; padding: 1px 5px; border-radius: 3px; font-size: 0.75rem; font-weight: bold; } .good-news { color: #27ae60; font-weight: 600; background: #eafaf1; padding: 12px; border-radius: 6px; border: 1px solid #d4efdf; text-align: center; font-size: 0.9rem; } +.good-news.large { padding: 40px; font-size: 1.1rem; } .tip { margin-top: 12px; font-size: 0.85rem; color: #d35400; background: #fff3e0; padding: 10px; border-radius: 6px; border: 1px solid #ffe0b2; } .progress-wrapper { margin-bottom: 12px; } .progress-bar { width: 100%; height: 8px; background: #f1f3f5; border-radius: 4px; overflow: hidden; } @@ -514,4 +561,16 @@ body { margin: 0; padding: 0; background-color: #f4f6f9; overflow: hidden; } .message-box { padding: 10px 15px; border-radius: 6px; margin-bottom: 15px; font-size: 0.9rem; font-weight: 500; } .message-box.success { background-color: #eafaf1; color: #27ae60; border: 1px solid #d4efdf; } .message-box.error { background-color: #ffeaea; color: #c0392b; border: 1px solid #ffcccc; } + +/* 新增日志视图样式 */ +.logs-view { display: flex; flex-direction: column; height: 100%; } +.logs-container { flex: 1; overflow-y: auto; padding-right: 5px; } +.log-list-full { display: flex; flex-direction: column; gap: 15px; } +.log-card { + background: white; border-radius: 8px; padding: 15px 20px; + border: 1px solid #eaecf0; box-shadow: 0 2px 6px rgba(0,0,0,0.02); +} +.log-card.warning-item { border-left: 4px solid #fce588; } +.log-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; } +.log-meta { display: flex; align-items: center; gap: 10px; } \ No newline at end of file