add custom bsod
This commit is contained in:
104
src/App.vue
104
src/App.vue
@@ -158,9 +158,16 @@
|
||||
<div v-if="currentTab === 'bsod'" class="tab-view bsod-view">
|
||||
<div class="view-header">
|
||||
<h2>蓝屏死机分析 (Minidump)</h2>
|
||||
<button class="secondary-btn" @click="loadMinidumps" :disabled="bsodLoading">🔄 刷新列表</button>
|
||||
<!-- 按钮组 -->
|
||||
<div class="actions-row">
|
||||
<button class="secondary-btn" @click="triggerBsodImport" :disabled="bsodAnalyzing">📂 导入文件</button>
|
||||
<button class="secondary-btn" @click="loadMinidumps" :disabled="bsodLoading">🔄 刷新列表</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的 BSOD 文件输入框 -->
|
||||
<input type="file" ref="bsodFileInput" @change="handleBsodFileImport" accept=".dmp" style="display: none" />
|
||||
|
||||
<div class="bsod-layout">
|
||||
<div class="bsod-list-panel">
|
||||
<div v-if="bsodList.length === 0 && !bsodLoading" class="empty-state">
|
||||
@@ -211,7 +218,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-detail">
|
||||
👈 请从左侧选择一个蓝屏文件开始分析
|
||||
👈 请从左侧选择一个蓝屏文件开始分析,<br>或点击上方“导入文件”分析外部文件
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -255,6 +262,7 @@ const selectedBsod = ref(null);
|
||||
const bsodResult = ref(null);
|
||||
const bsodLoading = ref(false);
|
||||
const bsodAnalyzing = ref(false);
|
||||
const bsodFileInput = ref(null); // 新增 BSOD 文件输入引用
|
||||
|
||||
const toast = reactive({ show: false, title: '', message: '', type: 'success' });
|
||||
let toastTimer = null;
|
||||
@@ -272,52 +280,23 @@ 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); }
|
||||
|
||||
// --- 导出/导入 ---
|
||||
// --- 概览:导出/导入 ---
|
||||
async function exportReport() {
|
||||
if (!isReportValid.value) return;
|
||||
|
||||
const fileName = `SystemDoctor_Report_${new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-")}.json`;
|
||||
const content = JSON.stringify(report.value, null, 2);
|
||||
|
||||
// 1. 尝试使用现代浏览器 API (支持选择保存位置)
|
||||
if ('showSaveFilePicker' in window) {
|
||||
try {
|
||||
const handle = await window.showSaveFilePicker({
|
||||
suggestedName: fileName,
|
||||
types: [{
|
||||
description: 'JSON Report',
|
||||
accept: { 'application/json': ['.json'] },
|
||||
}],
|
||||
});
|
||||
const writable = await handle.createWritable();
|
||||
await writable.write(content);
|
||||
await writable.close();
|
||||
triggerToast('导出成功', '文件已保存到指定位置', 'success');
|
||||
return;
|
||||
} catch (err) {
|
||||
if (err.name === 'AbortError') return; // 用户取消
|
||||
console.warn("File System Access API warning:", err);
|
||||
// 继续执行降级方案
|
||||
}
|
||||
const handle = await window.showSaveFilePicker({ suggestedName: fileName, types: [{ description: 'JSON Report', accept: { 'application/json': ['.json'] }, }], });
|
||||
const writable = await handle.createWritable(); await writable.write(content); await writable.close();
|
||||
triggerToast('导出成功', '文件已保存到指定位置', 'success'); return;
|
||||
} catch (err) { if (err.name === 'AbortError') return; }
|
||||
}
|
||||
|
||||
// 2. 降级方案:传统的 Blob 下载 (通常保存到下载文件夹)
|
||||
try {
|
||||
const blob = new Blob([content], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
const blob = new Blob([content], { type: "application/json" }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = fileName; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url);
|
||||
triggerToast('导出成功', '已保存到默认下载目录', 'success');
|
||||
} catch (err) {
|
||||
triggerToast('导出失败', err.message, 'error');
|
||||
}
|
||||
} 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 = ''; }
|
||||
|
||||
@@ -338,24 +317,51 @@ async function startScan() {
|
||||
} catch (e) { loading.value = false; errorMsg.value = e; }
|
||||
}
|
||||
|
||||
// --- BSOD 功能 ---
|
||||
async function loadMinidumps() { bsodLoading.value = true; try { bsodList.value = await invoke('list_minidumps'); } catch (e) { triggerToast('加载失败', e, 'error'); } finally { bsodLoading.value = false; } }
|
||||
|
||||
async function analyzeBsod(file) { if (bsodAnalyzing.value) return; selectedBsod.value = file; bsodResult.value = null; bsodAnalyzing.value = true; try { bsodResult.value = await invoke('analyze_minidump', { filepath: file.path }); } catch (e) { triggerToast('分析失败', e, 'error'); } finally { bsodAnalyzing.value = false; } }
|
||||
|
||||
// [新增] BSOD 导入功能
|
||||
function triggerBsodImport() { bsodFileInput.value.click(); }
|
||||
function handleBsodFileImport(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// 更新 UI 状态,模拟选中了一个“外部文件”
|
||||
selectedBsod.value = { path: 'external', filename: file.name, created_time: 'Imported', size_kb: Math.round(file.size / 1024) };
|
||||
bsodResult.value = null;
|
||||
bsodAnalyzing.value = true;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (e) => {
|
||||
try {
|
||||
// 将 ArrayBuffer 转换为 Uint8Array (Rust Vec<u8>)
|
||||
const arrayBuffer = e.target.result;
|
||||
const bytes = new Uint8Array(arrayBuffer);
|
||||
const byteArray = Array.from(bytes); // 转换为普通数组以便序列化传输
|
||||
|
||||
const result = await invoke('analyze_minidump_bytes', { fileContent: byteArray });
|
||||
bsodResult.value = result;
|
||||
triggerToast('分析成功', '已完成外部文件解析', 'success');
|
||||
} catch (err) {
|
||||
triggerToast('分析失败', err, 'error');
|
||||
} finally {
|
||||
bsodAnalyzing.value = false;
|
||||
}
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
event.target.value = '';
|
||||
}
|
||||
|
||||
watch(currentTab, (newVal) => { if (newVal === 'bsod' && bsodList.value.length === 0) loadMinidumps(); });
|
||||
|
||||
function triggerToast(title, message, type = 'success') { toast.title = title; toast.message = message; toast.type = type; toast.show = true; if (toastTimer) clearTimeout(toastTimer); toastTimer = setTimeout(() => { toast.show = false; }, 3000); }
|
||||
onUnmounted(() => { for (const fn of unlistenFns) fn(); if (toastTimer) clearTimeout(toastTimer); });
|
||||
</script>
|
||||
|
||||
<!-- [修复 2] 全局滚动条样式优化 -->
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f4f6f9;
|
||||
/* 改为 hidden,因为使用了内部 flex 滚动区域 (.main-content) */
|
||||
overflow: hidden;
|
||||
}
|
||||
body { margin: 0; padding: 0; background-color: #f4f6f9; overflow: hidden; }
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
@@ -418,11 +424,7 @@ body {
|
||||
.content-box { padding: 5px 0; } .main-text { font-weight: 500; color: #2c3e50; margin-bottom: 8px; }
|
||||
.card.danger { border-top-color: #ff4757; }
|
||||
|
||||
/* [修复 1] 硬件概览独占一行 (Explicit Override) */
|
||||
.card.summary {
|
||||
border-top-color: #3498db;
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.card.summary { border-top-color: #3498db; grid-column: 1 / -1; }
|
||||
|
||||
.primary-btn { background: #2ecc71; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-weight: 600; display: flex; align-items: center; gap: 8px; transition: 0.2s; }
|
||||
.primary-btn:hover:not(:disabled) { background: #27ae60; } .primary-btn:disabled { background: #bdc3c7; cursor: not-allowed; }
|
||||
|
||||
Reference in New Issue
Block a user