improve title ui

This commit is contained in:
Julian Freeman
2025-11-25 23:49:47 -04:00
parent 52c0bd5bb3
commit fe330511b9

View File

@@ -1,34 +1,58 @@
<template> <template>
<div class="container"> <div class="container">
<header class="header"> <!-- 顶部标题栏 -->
<h1>🏥 电脑健康体检中心 Pro</h1> <header class="header-bar">
<p class="subtitle">静态排查仪表盘 - Powered by Rust & Tauri</p> <div class="brand">
<span class="icon">🩺</span>
<h1>静态排查仪表盘</h1>
</div>
<span class="version-tag">Powered by Rust & Tauri</span>
</header> </header>
<div class="actions"> <!-- 工具栏左侧主操作右侧辅助操作 -->
<!-- 主操作按钮 --> <div class="toolbar">
<button @click="startScan" :disabled="loading" class="scan-btn" :class="{ 'scanning': loading }"> <div class="left-actions">
<span v-if="loading">🔄 正在深入排查系统底层...</span> <button @click="startScan" :disabled="loading" class="primary-btn" :class="{ 'scanning': loading }">
<span v-else>🔍 开始全面体检</span> <span v-if="loading" class="icon-spin">🔄</span>
</button> <span v-if="loading">正在排查...</span>
<template v-else>
<!-- 辅助操作按钮区 --> <span class="btn-icon">🔍</span> 开始全面体检
<div class="secondary-actions"> </template>
<button class="secondary-btn" @click="triggerImport" :disabled="loading">
📂 导入报告
</button>
<button class="secondary-btn" @click="exportReport" :disabled="!isReportValid || loading">
💾 导出报告
</button> </button>
</div> </div>
<div class="right-actions">
<button class="icon-btn" @click="triggerImport" :disabled="loading" title="导入报告">
📂 导入
</button>
<button class="icon-btn" @click="exportReport" :disabled="!isReportValid || loading" title="导出报告">
💾 导出
</button>
</div>
</div>
<!-- 隐藏的文件输入框 -->
<input type="file" ref="fileInput" @change="handleFileImport" accept=".json" style="display: none" /> <input type="file" ref="fileInput" @change="handleFileImport" accept=".json" style="display: none" />
<div v-if="errorMsg" class="error-box"> {{ errorMsg }}</div> <!-- 错误提示 (保留在原本位置或顶部) -->
<!-- 完成提示 --> <div v-if="errorMsg" class="inline-error">
<div v-if="scanFinished" class="success-box"> 扫描已完成所有项目检查完毕</div> {{ errorMsg }}
</div> </div>
<!-- 右上角 Toast 通知 (扫描完成提示) -->
<transition name="toast-slide">
<div v-if="showToast" class="toast-notification">
<div class="toast-content">
<span class="check-icon"></span>
<div class="toast-text">
<h4>扫描已完成</h4>
<p>所有硬件与日志检查完毕</p>
</div>
</div>
<div class="toast-progress"></div>
</div>
</transition>
<!-- 结果显示区域 --> <!-- 结果显示区域 -->
<div v-if="report.hardware" class="dashboard fade-in"> <div v-if="report.hardware" class="dashboard fade-in">
@@ -44,11 +68,11 @@
<span class="value text-ellipsis" :title="report.hardware.cpu_name">{{ report.hardware.cpu_name }}</span> <span class="value text-ellipsis" :title="report.hardware.cpu_name">{{ report.hardware.cpu_name }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">整机型号 (System)</span> <span class="label">整机型号</span>
<span class="value text-ellipsis">{{ report.hardware.sys_vendor }} {{ report.hardware.sys_product }}</span> <span class="value text-ellipsis">{{ report.hardware.sys_vendor }} {{ report.hardware.sys_product }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">主板型号 (BaseBoard)</span> <span class="label">主板型号</span>
<span class="value text-ellipsis">{{ report.hardware.mobo_vendor }} {{ report.hardware.mobo_product }}</span> <span class="value text-ellipsis">{{ report.hardware.mobo_vendor }} {{ report.hardware.mobo_product }}</span>
</div> </div>
<div class="info-item"> <div class="info-item">
@@ -183,72 +207,45 @@ import { ref, computed, onUnmounted } from 'vue';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event'; import { listen } from '@tauri-apps/api/event';
// 初始化 report 为包含空字段的对象,避免 v-if 报错
const emptyReport = () => ({ const emptyReport = () => ({
hardware: null, hardware: null, storage: null, events: null, minidumps: null, drivers: null, battery: null
storage: null,
events: null,
minidumps: null,
drivers: null,
battery: null
}); });
const report = ref(emptyReport()); const report = ref(emptyReport());
const loading = ref(false); const loading = ref(false);
const errorMsg = ref(''); const errorMsg = ref('');
const scanFinished = ref(false); const showToast = ref(false);
const fileInput = ref(null); const fileInput = ref(null);
let unlistenFns = []; // 存储事件取消函数 let unlistenFns = [];
let toastTimer = null;
const hasStorageDanger = computed(() => { const hasStorageDanger = computed(() => report.value?.storage?.some(d => d.is_danger) || false);
return report.value?.storage?.some(d => d.is_danger) || false; const isReportValid = computed(() => report.value && (report.value.hardware || report.value.storage));
});
const isReportValid = computed(() => { // --- 辅助函数 ---
// 只要有任意一项数据,就认为可以导出 function calculatePercent(used, total) { return (!total || total === 0) ? 0 : Math.round((used / total) * 100); }
return report.value && (report.value.hardware || report.value.storage);
});
// --- 辅助函数保持不变 ---
function calculatePercent(used, total) {
if (!total || total === 0) return 0;
return Math.round((used / total) * 100);
}
function getProgressStyle(used, total) { function getProgressStyle(used, total) {
const percent = calculatePercent(used, total); const percent = calculatePercent(used, total);
let color = '#2ed573'; let color = percent >= 90 ? '#ff4757' : (percent >= 75 ? '#ffa502' : '#2ed573');
if (percent >= 90) color = '#ff4757';
else if (percent >= 75) color = '#ffa502';
return { width: `${percent}%`, background: color }; return { width: `${percent}%`, background: color };
} }
function getBatteryColor(percentage) { function getBatteryColor(p) { return p < 50 ? '#ff4757' : (p < 80 ? '#ffa502' : '#2ed573'); }
if (percentage < 50) return '#ff4757'; function formatTime(t) { return !t ? "Unknown Time" : t.replace('T', ' ').substring(0, 19); }
if (percentage < 80) return '#ffa502';
return '#2ed573';
}
// --- 导出/导入 --- // --- 导出/导入 ---
function exportReport() { function exportReport() {
if (!isReportValid.value) return; if (!isReportValid.value) return;
try { try {
const dataStr = JSON.stringify(report.value, null, 2); const blob = new Blob([JSON.stringify(report.value, null, 2)], { type: "application/json" });
const blob = new Blob([dataStr], { type: "application/json" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const link = document.createElement('a'); const link = document.createElement('a');
link.href = url; link.href = url;
const timestamp = new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-"); link.download = `SystemDoctor_Report_${new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-")}.json`;
link.download = `SystemDoctor_Report_${timestamp}.json`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url);
document.body.appendChild(link); } catch (err) { errorMsg.value = "导出失败: " + err.message; }
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
} catch (err) {
errorMsg.value = "导出失败: " + err.message;
}
} }
function triggerImport() { fileInput.value.click(); } function triggerImport() { fileInput.value.click(); }
function handleFileImport(event) { function handleFileImport(event) {
const file = event.target.files[0]; const file = event.target.files[0];
if (!file) return; if (!file) return;
@@ -257,52 +254,36 @@ function handleFileImport(event) {
try { try {
const json = JSON.parse(e.target.result); const json = JSON.parse(e.target.result);
if (json && (json.hardware || json.storage)) { if (json && (json.hardware || json.storage)) {
report.value = json; // 导入时一次性覆盖 report.value = json; errorMsg.value = '';
scanFinished.value = false; // 导入的不显示“扫描完成” } else { errorMsg.value = "无效的报告文件。"; }
errorMsg.value = ''; } catch (err) { errorMsg.value = "解析失败: " + err.message; }
} else {
errorMsg.value = "无效的报告文件。";
}
} catch (err) {
errorMsg.value = "解析失败: " + err.message;
}
}; };
reader.readAsText(file); reader.readAsText(file);
event.target.value = ''; event.target.value = '';
} }
// --- 扫描逻辑 (流式) --- // --- 扫描逻辑 ---
async function startScan() { async function startScan() {
// 1. 重置状态
report.value = emptyReport(); report.value = emptyReport();
errorMsg.value = ''; errorMsg.value = '';
scanFinished.value = false; showToast.value = false; // 重置 Toast
loading.value = true; loading.value = true;
// 2. 清理旧的监听器 if (unlistenFns.length > 0) { for (const fn of unlistenFns) fn(); unlistenFns = []; }
if (unlistenFns.length > 0) {
for (const fn of unlistenFns) fn();
unlistenFns = [];
}
// 3. 注册新的事件监听器
try { try {
const l1 = await listen('report-hardware', (e) => report.value.hardware = e.payload); const l1 = await listen('report-hardware', (e) => report.value.hardware = e.payload);
const l2 = await listen('report-storage', (e) => report.value.storage = e.payload); const l2 = await listen('report-storage', (e) => report.value.storage = e.payload);
const l3 = await listen('report-drivers', (e) => report.value.drivers = e.payload); const l3 = await listen('report-drivers', (e) => report.value.drivers = e.payload);
const l4 = await listen('report-minidumps', (e) => report.value.minidumps = e.payload); const l4 = await listen('report-minidumps', (e) => report.value.minidumps = e.payload);
const l5 = await listen('report-battery', (e) => report.value.battery = e.payload); // 可能是null const l5 = await listen('report-battery', (e) => report.value.battery = e.payload);
const l6 = await listen('report-events', (e) => report.value.events = e.payload); const l6 = await listen('report-events', (e) => report.value.events = e.payload);
const l7 = await listen('diagnosis-finished', () => { const l7 = await listen('diagnosis-finished', () => {
loading.value = false; loading.value = false;
scanFinished.value = true; triggerToast(); // 触发 Toast
}); });
unlistenFns.push(l1, l2, l3, l4, l5, l6, l7); unlistenFns.push(l1, l2, l3, l4, l5, l6, l7);
// 4. 触发后端任务 (fire and forget)
await invoke('run_diagnosis'); await invoke('run_diagnosis');
} catch (e) { } catch (e) {
console.error(e); console.error(e);
errorMsg.value = "启动扫描失败: " + e; errorMsg.value = "启动扫描失败: " + e;
@@ -310,149 +291,164 @@ async function startScan() {
} }
} }
// 组件销毁时清理监听 // --- Toast 逻辑 ---
function triggerToast() {
showToast.value = true;
if (toastTimer) clearTimeout(toastTimer);
toastTimer = setTimeout(() => {
showToast.value = false;
}, 3000);
}
onUnmounted(() => { onUnmounted(() => {
for (const fn of unlistenFns) fn(); for (const fn of unlistenFns) fn();
if (toastTimer) clearTimeout(toastTimer);
}); });
</script> </script>
<!-- 全局样式解决滚动条问题 -->
<style> <style>
body { body {
margin: 0; margin: 0; padding: 0; background-color: #f4f6f9; /* 更柔和的背景 */
padding: 0; overflow-y: scroll;
background-color: #f0f2f5; /* 背景色移到这里,铺满全屏 */
overflow-y: scroll; /* 始终显示滚动条轨道(防止跳动)或者设为 auto */
} }
</style> </style>
<style scoped> <style scoped>
/* 基础布局 */ /* 容器调整 */
.container { .container {
max-width: 1200px; max-width: 1200px; margin: 0 auto; padding: 20px 30px; box-sizing: border-box;
margin: 0 auto; font-family: 'Segoe UI', system-ui, sans-serif; color: #2c3e50;
padding: 30px 20px;
box-sizing: border-box;
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
color: #2c3e50;
/* 移除 min-height: 100vh 和背景色,让它自然伸缩,背景由 body 接管 */
/* 这样内容少时高度不够 100vh 就不会触发滚动条 */
} }
.header { text-align: center; margin-bottom: 30px; } /* --- 顶部标题栏 --- */
h1 { .header-bar {
font-size: 2rem; display: flex; justify-content: space-between; align-items: center;
margin-bottom: 8px; margin-bottom: 25px; padding-bottom: 15px; border-bottom: 1px solid #e1e4e8;
margin-top: 0; /* 关键防止margin合并导致的滚动条 */
color: #2c3e50;
letter-spacing: -0.5px;
} }
.subtitle { color: #7f8c8d; font-size: 1rem; margin-top: 0; /* 同样防止margin合并 */ } .brand { display: flex; align-items: center; gap: 10px; }
.brand .icon { font-size: 1.8rem; }
h1 { font-size: 1.5rem; margin: 0; color: #2c3e50; font-weight: 700; letter-spacing: -0.5px; }
.version-tag { font-size: 0.85rem; color: #95a5a6; font-weight: 500; }
.actions { display: flex; flex-direction: column; align-items: center; margin-bottom: 35px; gap: 15px; } /* --- 工具栏布局 --- */
.secondary-actions { display: flex; gap: 15px; } .toolbar {
display: flex; justify-content: space-between; align-items: center;
.scan-btn { margin-bottom: 30px; background: white; padding: 10px 15px;
background: linear-gradient(135deg, #42b983 0%, #2ecc71 100%); border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.04);
color: white; border: none; padding: 14px 40px; font-size: 1.1rem; border-radius: 50px; cursor: pointer;
box-shadow: 0 4px 15px rgba(46, 204, 113, 0.25); transition: all 0.2s ease; font-weight: 600; display: flex; align-items: center; justify-content: center; min-width: 200px;
} }
.scan-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(46, 204, 113, 0.35); filter: brightness(1.05); }
.scan-btn:active:not(:disabled) { transform: translateY(1px); }
.scan-btn:disabled { background: #bdc3c7; cursor: not-allowed; box-shadow: none; opacity: 0.8; }
.secondary-btn { .left-actions { display: flex; align-items: center; }
background: white; border: 1px solid #dcdfe6; color: #606266; padding: 8px 20px; font-size: 0.9rem; border-radius: 20px; .right-actions { display: flex; gap: 10px; }
cursor: pointer; transition: all 0.2s; display: flex; align-items: center; gap: 5px;
/* 主按钮 (绿色强调) */
.primary-btn {
background: #2ecc71; color: white; border: none;
padding: 10px 24px; font-size: 1rem; border-radius: 8px;
cursor: pointer; font-weight: 600; display: flex; align-items: center; gap: 8px;
transition: background 0.2s, transform 0.1s;
} }
.secondary-btn:hover:not(:disabled) { color: #409eff; border-color: #c6e2ff; background-color: #ecf5ff; } .primary-btn:hover:not(:disabled) { background: #27ae60; transform: translateY(-1px); }
.secondary-btn:disabled { color: #c0c4cc; cursor: not-allowed; background-color: #f5f7fa; } .primary-btn:active:not(:disabled) { transform: translateY(1px); }
.primary-btn:disabled { background: #bdc3c7; cursor: not-allowed; }
.icon-spin { animation: spin 1s linear infinite; }
.error-box { margin-top: 5px; padding: 12px 20px; background-color: #ffeaea; color: #c0392b; border: 1px solid #ffcccc; border-radius: 8px; font-weight: 500; font-size: 0.95rem; } /* 辅助按钮 (白色简约) */
.icon-btn {
background: transparent; border: 1px solid transparent; color: #555;
padding: 8px 16px; font-size: 0.95rem; border-radius: 8px;
cursor: pointer; transition: all 0.2s;
}
.icon-btn:hover:not(:disabled) { background: #f0f2f5; color: #2c3e50; }
.icon-btn:disabled { color: #ccc; cursor: not-allowed; }
.dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 25px; } /* --- Toast 通知 --- */
.fade-in { animation: fadeIn 0.4s ease-out; } .toast-notification {
@keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } } position: fixed; top: 25px; right: 25px; z-index: 9999;
background: white; border-left: 5px solid #2ecc71;
padding: 15px 20px; border-radius: 8px;
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
min-width: 280px; overflow: hidden;
}
.toast-content { display: flex; align-items: flex-start; gap: 12px; }
.check-icon { font-size: 1.2rem; }
.toast-text h4 { margin: 0 0 4px 0; font-size: 1rem; color: #2c3e50; }
.toast-text p { margin: 0; font-size: 0.85rem; color: #7f8c8d; }
/* 简单的倒计时进度条动画 */
.toast-progress {
position: absolute; bottom: 0; left: 0; height: 3px; background: #2ecc71;
width: 100%; animation: progress 3s linear forwards;
}
@keyframes progress { from { width: 100%; } to { width: 0%; } }
/* Toast 进出动画 */
.toast-slide-enter-active, .toast-slide-leave-active { transition: all 0.3s ease; }
.toast-slide-enter-from, .toast-slide-leave-to { transform: translateX(50px); opacity: 0; }
.inline-error {
background: #ffeaea; color: #c0392b; padding: 10px; border-radius: 6px; margin-bottom: 20px; font-size: 0.9rem; font-weight: 600;
}
/* --- 动画与通用 --- */
@keyframes spin { 100% { transform: rotate(360deg); } }
.dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 20px; }
.slide-up { animation: slideUp 0.4s ease-out forwards; }
@keyframes slideUp { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
/* 卡片样式 (微调更紧凑) */
.card { .card {
background: white; border-radius: 12px; padding: 20px; box-shadow: 0 5px 15px rgba(0,0,0,0.04); background: white; border-radius: 10px; padding: 20px;
border-top: 4px solid #42b983; transition: transform 0.2s, box-shadow 0.2s; box-shadow: 0 2px 10px rgba(0,0,0,0.03); border: 1px solid #eaecf0;
border-left: 1px solid #eee; border-right: 1px solid #eee; border-bottom: 1px solid #eee; border-top: 3px solid #2ecc71;
} }
.card:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(0,0,0,0.08); } .card:hover { box-shadow: 0 8px 20px rgba(0,0,0,0.06); transform: translateY(-2px); transition: all 0.2s; }
.card.danger { border-top-color: #ff4757; background: #fffbfb; } .card.danger { border-top-color: #ff4757; }
.card.success { border-top-color: #2ed573; } .card.summary { border-top-color: #3498db; grid-column: 1 / -1; }
.card.summary { border-top-color: #3742fa; grid-column: 1 / -1; }
.card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px; padding-bottom: 12px; border-bottom: 1px solid #f1f2f6; } .card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #f0f2f5; }
h2 { font-size: 1.2rem; margin: 0; font-weight: 600; } h2 { font-size: 1.1rem; margin: 0; font-weight: 600; }
.grid-container-summary { display: grid; grid-template-columns: 1fr 1.5fr; gap: 30px; align-items: start; } .grid-container-summary { display: grid; grid-template-columns: 1fr 1.5fr; gap: 25px; }
@media (max-width: 768px) { .grid-container-summary { grid-template-columns: 1fr; gap: 20px; } } @media (max-width: 768px) { .grid-container-summary { grid-template-columns: 1fr; } }
.basic-info, .usage-bars { display: flex; flex-direction: column; gap: 15px; } .basic-info, .usage-bars { display: flex; flex-direction: column; gap: 12px; }
.usage-bars { padding-top: 10px; } .usage-bars { padding-top: 5px; }
.usage-item { display: flex; flex-direction: column; gap: 8px; } .usage-item { display: flex; flex-direction: column; gap: 6px; }
.info-item { display: flex; flex-direction: column; } .info-item { display: flex; flex-direction: column; }
.label { font-size: 0.8rem; color: #95a5a6; margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; } .label { font-size: 0.75rem; color: #95a5a6; margin-bottom: 2px; text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; }
.value { font-size: 1.05rem; font-weight: 500; color: #2c3e50; } .value { font-size: 1rem; font-weight: 500; color: #2c3e50; }
.text-ellipsis { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .text-ellipsis { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.progress-label { display: flex; justify-content: space-between; font-size: 0.9rem; color: #636e72; } .progress-label { display: flex; justify-content: space-between; font-size: 0.85rem; color: #636e72; }
.progress-label strong { color: #2c3e50; font-size: 1rem; } .progress-bar-large { width: 100%; height: 16px; background: #f1f3f5; border-radius: 8px; overflow: hidden; }
.progress-bar-large { width: 100%; height: 20px; background: #e6e8ec; border-radius: 10px; overflow: hidden; box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); } .fill { height: 100%; transition: width 0.6s ease; }
.fill { height: 100%; transition: width 0.6s cubic-bezier(0.25, 0.8, 0.25, 1), background-color 0.6s ease; }
.list-container { display: flex; flex-direction: column; gap: 12px; } .list-container { display: flex; flex-direction: column; gap: 10px; }
.list-item { background: #fcfdfe; padding: 12px 15px; border-radius: 8px; border: 1px solid #ececec; } .list-item { background: #f8f9fa; padding: 10px 14px; border-radius: 6px; border: 1px solid #edf2f7; }
.list-item.error-item { background: #fff5f5; border-color: #ffcccc; } .list-item.error-item { background: #fff5f5; border-color: #fed7d7; }
.list-item.warning-item { background: #fffcf0; border-color: #ffeaa7; } .list-item.warning-item { background: #fffaf0; border-color: #fce588; }
.item-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; flex-wrap: wrap; gap: 5px; } .item-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
.description { margin: 0; color: #636e72; font-size: 0.9rem; line-height: 1.5; } .description { margin: 0; color: #636e72; font-size: 0.9rem; line-height: 1.4; }
.description.highlight { color: #d35400; font-weight: 500; } .description.highlight { color: #d35400; font-weight: 500; }
.description.raw-message { margin-top: 5px; font-size: 0.85rem; color: #7f8c8d; font-family: Consolas, monospace; background: #f0f3f4; padding: 5px 8px; border-radius: 4px; word-break: break-all; } .description.raw-message { margin-top: 6px; font-size: 0.8rem; color: #7f8c8d; font-family: Consolas, monospace; background: #fff; padding: 4px 8px; border-radius: 4px; border: 1px solid #eee; }
.status-text { font-weight: 600; font-size: 0.9rem; } .status-text { font-weight: 600; font-size: 0.9rem; }
.text-green { color: #27ae60; } .text-green { color: #27ae60; } .text-red { color: #c0392b; }
.text-red { color: #c0392b; }
.badge { font-size: 0.75rem; padding: 3px 10px; border-radius: 12px; color: white; font-weight: 600; letter-spacing: 0.5px; } .badge { font-size: 0.7rem; padding: 2px 8px; border-radius: 10px; color: white; font-weight: 600; }
.badge-green { background-color: #2ed573; } .badge-green { background-color: #2ed573; } .badge-red { background-color: #ff4757; } .badge-blue { background-color: #3498db; } .badge-gray { background-color: #95a5a6; }
.badge-red { background-color: #ff4757; } .code-tag { background: #ff4757; color: white; padding: 1px 5px; border-radius: 3px; font-size: 0.75rem; font-weight: bold; }
.badge-blue { background-color: #3742fa; }
.badge-gray { background-color: #95a5a6; }
.code-tag { background: #ff4757; color: white; padding: 2px 6px; border-radius: 4px; font-size: 0.8rem; font-weight: bold; }
.good-news { color: #27ae60; font-weight: 600; background: #eafaf1; padding: 15px; border-radius: 8px; border: 1px solid #d4efdf; text-align: center; } .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; }
.tip { margin-top: 15px; font-size: 0.85rem; color: #d35400; background: #fff3e0; padding: 12px; border-radius: 6px; border: 1px solid #ffe0b2; } .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: 15px; } .progress-wrapper { margin-bottom: 12px; }
.progress-bar { width: 100%; height: 10px; background: #e6e8ec; border-radius: 5px; overflow: hidden; } .progress-bar { width: 100%; height: 8px; background: #f1f3f5; border-radius: 4px; overflow: hidden; }
.event-id, .event-source, .event-time { font-size: 0.75rem; color: #7f8c8d; } .event-id { font-weight: bold; color: #2c3e50; background: #e2e8f0; padding: 1px 5px; border-radius: 3px; margin-right: 8px; font-size: 0.75rem; }
.event-id { font-weight: bold; color: #2c3e50; background: #dfe6e9; padding: 1px 5px; border-radius: 4px; margin-right: 8px; } .event-time { margin-left: auto; font-size: 0.75rem; color: #95a5a6; }
.event-time { margin-left: auto; }
.content-box { padding: 5px 0; } .content-box { padding: 5px 0; }
.main-text { font-weight: 500; color: #2c3e50; margin-bottom: 10px; } .main-text { font-weight: 500; color: #2c3e50; margin-bottom: 8px; }
/* 动画部分 */
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.slide-up { animation: slideUp 0.5s ease-out forwards; }
.success-box {
margin-top: 10px;
padding: 12px 20px;
background-color: #eafaf1;
color: #27ae60;
border: 1px solid #d4efdf;
border-radius: 8px;
font-weight: 600;
text-align: center;
animation: slideUp 0.3s ease-out;
}
</style> </style>