improve title ui
This commit is contained in:
384
src/App.vue
384
src/App.vue
@@ -1,34 +1,58 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1>🏥 电脑健康体检中心 Pro</h1>
|
||||
<p class="subtitle">静态排查仪表盘 - Powered by Rust & Tauri</p>
|
||||
<!-- 顶部标题栏 -->
|
||||
<header class="header-bar">
|
||||
<div class="brand">
|
||||
<span class="icon">🩺</span>
|
||||
<h1>静态排查仪表盘</h1>
|
||||
</div>
|
||||
<span class="version-tag">Powered by Rust & Tauri</span>
|
||||
</header>
|
||||
|
||||
<div class="actions">
|
||||
<!-- 主操作按钮 -->
|
||||
<button @click="startScan" :disabled="loading" class="scan-btn" :class="{ 'scanning': loading }">
|
||||
<span v-if="loading">🔄 正在深入排查系统底层...</span>
|
||||
<span v-else>🔍 开始全面体检</span>
|
||||
</button>
|
||||
|
||||
<!-- 辅助操作按钮区 -->
|
||||
<div class="secondary-actions">
|
||||
<button class="secondary-btn" @click="triggerImport" :disabled="loading">
|
||||
📂 导入报告
|
||||
</button>
|
||||
<button class="secondary-btn" @click="exportReport" :disabled="!isReportValid || loading">
|
||||
💾 导出报告
|
||||
<!-- 工具栏:左侧主操作,右侧辅助操作 -->
|
||||
<div class="toolbar">
|
||||
<div class="left-actions">
|
||||
<button @click="startScan" :disabled="loading" class="primary-btn" :class="{ 'scanning': loading }">
|
||||
<span v-if="loading" class="icon-spin">🔄</span>
|
||||
<span v-if="loading">正在排查...</span>
|
||||
<template v-else>
|
||||
<span class="btn-icon">🔍</span> 开始全面体检
|
||||
</template>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<input type="file" ref="fileInput" @change="handleFileImport" accept=".json" style="display: none" />
|
||||
|
||||
<div v-if="errorMsg" class="error-box">⚠️ {{ errorMsg }}</div>
|
||||
<!-- 完成提示 -->
|
||||
<div v-if="scanFinished" class="success-box">✅ 扫描已完成!所有项目检查完毕。</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" />
|
||||
|
||||
<!-- 错误提示 (保留在原本位置或顶部) -->
|
||||
<div v-if="errorMsg" class="inline-error">
|
||||
⚠️ {{ errorMsg }}
|
||||
</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">
|
||||
|
||||
@@ -44,11 +68,11 @@
|
||||
<span class="value text-ellipsis" :title="report.hardware.cpu_name">{{ report.hardware.cpu_name }}</span>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
@@ -183,72 +207,45 @@ import { ref, computed, onUnmounted } from 'vue';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
|
||||
// 初始化 report 为包含空字段的对象,避免 v-if 报错
|
||||
const emptyReport = () => ({
|
||||
hardware: null,
|
||||
storage: null,
|
||||
events: null,
|
||||
minidumps: null,
|
||||
drivers: null,
|
||||
battery: null
|
||||
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);
|
||||
const showToast = ref(false);
|
||||
const fileInput = ref(null);
|
||||
let unlistenFns = []; // 存储事件取消函数
|
||||
let unlistenFns = [];
|
||||
let toastTimer = null;
|
||||
|
||||
const hasStorageDanger = computed(() => {
|
||||
return report.value?.storage?.some(d => d.is_danger) || false;
|
||||
});
|
||||
const hasStorageDanger = computed(() => report.value?.storage?.some(d => d.is_danger) || false);
|
||||
const isReportValid = computed(() => report.value && (report.value.hardware || report.value.storage));
|
||||
|
||||
const isReportValid = computed(() => {
|
||||
// 只要有任意一项数据,就认为可以导出
|
||||
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 calculatePercent(used, total) { return (!total || total === 0) ? 0 : Math.round((used / total) * 100); }
|
||||
function getProgressStyle(used, total) {
|
||||
const percent = calculatePercent(used, total);
|
||||
let color = '#2ed573';
|
||||
if (percent >= 90) color = '#ff4757';
|
||||
else if (percent >= 75) color = '#ffa502';
|
||||
let color = percent >= 90 ? '#ff4757' : (percent >= 75 ? '#ffa502' : '#2ed573');
|
||||
return { width: `${percent}%`, background: color };
|
||||
}
|
||||
function getBatteryColor(percentage) {
|
||||
if (percentage < 50) return '#ff4757';
|
||||
if (percentage < 80) return '#ffa502';
|
||||
return '#2ed573';
|
||||
}
|
||||
function getBatteryColor(p) { return p < 50 ? '#ff4757' : (p < 80 ? '#ffa502' : '#2ed573'); }
|
||||
function formatTime(t) { return !t ? "Unknown Time" : t.replace('T', ' ').substring(0, 19); }
|
||||
|
||||
// --- 导出/导入 ---
|
||||
function exportReport() {
|
||||
if (!isReportValid.value) return;
|
||||
try {
|
||||
const dataStr = JSON.stringify(report.value, null, 2);
|
||||
const blob = new Blob([dataStr], { type: "application/json" });
|
||||
const blob = new Blob([JSON.stringify(report.value, null, 2)], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
const timestamp = new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-");
|
||||
link.download = `SystemDoctor_Report_${timestamp}.json`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (err) {
|
||||
errorMsg.value = "导出失败: " + err.message;
|
||||
}
|
||||
link.download = `SystemDoctor_Report_${new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-")}.json`;
|
||||
document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url);
|
||||
} catch (err) { errorMsg.value = "导出失败: " + err.message; }
|
||||
}
|
||||
|
||||
function triggerImport() { fileInput.value.click(); }
|
||||
|
||||
function handleFileImport(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
@@ -257,52 +254,36 @@ function handleFileImport(event) {
|
||||
try {
|
||||
const json = JSON.parse(e.target.result);
|
||||
if (json && (json.hardware || json.storage)) {
|
||||
report.value = json; // 导入时一次性覆盖
|
||||
scanFinished.value = false; // 导入的不显示“扫描完成”
|
||||
errorMsg.value = '';
|
||||
} else {
|
||||
errorMsg.value = "无效的报告文件。";
|
||||
}
|
||||
} catch (err) {
|
||||
errorMsg.value = "解析失败: " + err.message;
|
||||
}
|
||||
report.value = json; errorMsg.value = '';
|
||||
} else { errorMsg.value = "无效的报告文件。"; }
|
||||
} catch (err) { errorMsg.value = "解析失败: " + err.message; }
|
||||
};
|
||||
reader.readAsText(file);
|
||||
event.target.value = '';
|
||||
}
|
||||
|
||||
// --- 扫描逻辑 (流式) ---
|
||||
// --- 扫描逻辑 ---
|
||||
async function startScan() {
|
||||
// 1. 重置状态
|
||||
report.value = emptyReport();
|
||||
errorMsg.value = '';
|
||||
scanFinished.value = false;
|
||||
showToast.value = false; // 重置 Toast
|
||||
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 {
|
||||
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 l3 = await listen('report-drivers', (e) => report.value.drivers = 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 l7 = await listen('diagnosis-finished', () => {
|
||||
loading.value = false;
|
||||
scanFinished.value = true;
|
||||
triggerToast(); // 触发 Toast
|
||||
});
|
||||
|
||||
unlistenFns.push(l1, l2, l3, l4, l5, l6, l7);
|
||||
|
||||
// 4. 触发后端任务 (fire and forget)
|
||||
await invoke('run_diagnosis');
|
||||
|
||||
} catch (e) {
|
||||
console.error(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(() => {
|
||||
for (const fn of unlistenFns) fn();
|
||||
if (toastTimer) clearTimeout(toastTimer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- 全局样式:解决滚动条问题 -->
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f0f2f5; /* 背景色移到这里,铺满全屏 */
|
||||
overflow-y: scroll; /* 始终显示滚动条轨道(防止跳动)或者设为 auto */
|
||||
margin: 0; padding: 0; background-color: #f4f6f9; /* 更柔和的背景 */
|
||||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
/* 基础布局 */
|
||||
/* 容器调整 */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 30px 20px;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
color: #2c3e50;
|
||||
/* 移除 min-height: 100vh 和背景色,让它自然伸缩,背景由 body 接管 */
|
||||
/* 这样内容少时高度不够 100vh 就不会触发滚动条 */
|
||||
max-width: 1200px; margin: 0 auto; padding: 20px 30px; box-sizing: border-box;
|
||||
font-family: 'Segoe UI', system-ui, sans-serif; color: #2c3e50;
|
||||
}
|
||||
|
||||
.header { text-align: center; margin-bottom: 30px; }
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 0; /* 关键:防止margin合并导致的滚动条 */
|
||||
color: #2c3e50;
|
||||
letter-spacing: -0.5px;
|
||||
/* --- 顶部标题栏 --- */
|
||||
.header-bar {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-bottom: 25px; padding-bottom: 15px; border-bottom: 1px solid #e1e4e8;
|
||||
}
|
||||
.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; }
|
||||
|
||||
.scan-btn {
|
||||
background: linear-gradient(135deg, #42b983 0%, #2ecc71 100%);
|
||||
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;
|
||||
/* --- 工具栏布局 --- */
|
||||
.toolbar {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-bottom: 30px; background: white; padding: 10px 15px;
|
||||
border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
||||
}
|
||||
.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 {
|
||||
background: white; border: 1px solid #dcdfe6; color: #606266; padding: 8px 20px; font-size: 0.9rem; border-radius: 20px;
|
||||
cursor: pointer; transition: all 0.2s; display: flex; align-items: center; gap: 5px;
|
||||
.left-actions { display: flex; align-items: center; }
|
||||
.right-actions { display: flex; gap: 10px; }
|
||||
|
||||
/* 主按钮 (绿色强调) */
|
||||
.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; }
|
||||
.secondary-btn:disabled { color: #c0c4cc; cursor: not-allowed; background-color: #f5f7fa; }
|
||||
.primary-btn:hover:not(:disabled) { background: #27ae60; transform: translateY(-1px); }
|
||||
.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; }
|
||||
.fade-in { animation: fadeIn 0.4s ease-out; }
|
||||
@keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
|
||||
/* --- Toast 通知 --- */
|
||||
.toast-notification {
|
||||
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 {
|
||||
background: white; border-radius: 12px; padding: 20px; box-shadow: 0 5px 15px rgba(0,0,0,0.04);
|
||||
border-top: 4px solid #42b983; transition: transform 0.2s, box-shadow 0.2s;
|
||||
border-left: 1px solid #eee; border-right: 1px solid #eee; border-bottom: 1px solid #eee;
|
||||
background: white; border-radius: 10px; padding: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.03); border: 1px solid #eaecf0;
|
||||
border-top: 3px solid #2ecc71;
|
||||
}
|
||||
.card:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(0,0,0,0.08); }
|
||||
.card.danger { border-top-color: #ff4757; background: #fffbfb; }
|
||||
.card.success { border-top-color: #2ed573; }
|
||||
.card.summary { border-top-color: #3742fa; grid-column: 1 / -1; }
|
||||
.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; }
|
||||
.card.summary { border-top-color: #3498db; 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; }
|
||||
h2 { font-size: 1.2rem; margin: 0; font-weight: 600; }
|
||||
.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.1rem; margin: 0; font-weight: 600; }
|
||||
|
||||
.grid-container-summary { display: grid; grid-template-columns: 1fr 1.5fr; gap: 30px; align-items: start; }
|
||||
@media (max-width: 768px) { .grid-container-summary { grid-template-columns: 1fr; gap: 20px; } }
|
||||
.grid-container-summary { display: grid; grid-template-columns: 1fr 1.5fr; gap: 25px; }
|
||||
@media (max-width: 768px) { .grid-container-summary { grid-template-columns: 1fr; } }
|
||||
|
||||
.basic-info, .usage-bars { display: flex; flex-direction: column; gap: 15px; }
|
||||
.usage-bars { padding-top: 10px; }
|
||||
.usage-item { display: flex; flex-direction: column; gap: 8px; }
|
||||
.basic-info, .usage-bars { display: flex; flex-direction: column; gap: 12px; }
|
||||
.usage-bars { padding-top: 5px; }
|
||||
.usage-item { display: flex; flex-direction: column; gap: 6px; }
|
||||
|
||||
.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; }
|
||||
.value { font-size: 1.05rem; font-weight: 500; color: #2c3e50; }
|
||||
.label { font-size: 0.75rem; color: #95a5a6; margin-bottom: 2px; text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; }
|
||||
.value { font-size: 1rem; font-weight: 500; color: #2c3e50; }
|
||||
.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 strong { color: #2c3e50; font-size: 1rem; }
|
||||
.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 cubic-bezier(0.25, 0.8, 0.25, 1), background-color 0.6s ease; }
|
||||
.progress-label { display: flex; justify-content: space-between; font-size: 0.85rem; color: #636e72; }
|
||||
.progress-bar-large { width: 100%; height: 16px; background: #f1f3f5; border-radius: 8px; overflow: hidden; }
|
||||
.fill { height: 100%; transition: width 0.6s ease; }
|
||||
|
||||
.list-container { display: flex; flex-direction: column; gap: 12px; }
|
||||
.list-item { background: #fcfdfe; padding: 12px 15px; border-radius: 8px; border: 1px solid #ececec; }
|
||||
.list-item.error-item { background: #fff5f5; border-color: #ffcccc; }
|
||||
.list-item.warning-item { background: #fffcf0; border-color: #ffeaa7; }
|
||||
.list-container { display: flex; flex-direction: column; gap: 10px; }
|
||||
.list-item { background: #f8f9fa; padding: 10px 14px; border-radius: 6px; border: 1px solid #edf2f7; }
|
||||
.list-item.error-item { background: #fff5f5; border-color: #fed7d7; }
|
||||
.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; }
|
||||
.description { margin: 0; color: #636e72; font-size: 0.9rem; line-height: 1.5; }
|
||||
.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.4; }
|
||||
.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; }
|
||||
.text-green { color: #27ae60; }
|
||||
.text-red { color: #c0392b; }
|
||||
.text-green { color: #27ae60; } .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-green { background-color: #2ed573; }
|
||||
.badge-red { background-color: #ff4757; }
|
||||
.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; }
|
||||
.badge { font-size: 0.7rem; padding: 2px 8px; border-radius: 10px; color: white; font-weight: 600; }
|
||||
.badge-green { background-color: #2ed573; } .badge-red { background-color: #ff4757; } .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: 15px; border-radius: 8px; border: 1px solid #d4efdf; text-align: center; }
|
||||
.tip { margin-top: 15px; font-size: 0.85rem; color: #d35400; background: #fff3e0; padding: 12px; border-radius: 6px; border: 1px solid #ffe0b2; }
|
||||
.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: 12px; font-size: 0.85rem; color: #d35400; background: #fff3e0; padding: 10px; border-radius: 6px; border: 1px solid #ffe0b2; }
|
||||
|
||||
.progress-wrapper { margin-bottom: 15px; }
|
||||
.progress-bar { width: 100%; height: 10px; background: #e6e8ec; border-radius: 5px; overflow: hidden; }
|
||||
.progress-wrapper { margin-bottom: 12px; }
|
||||
.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: #dfe6e9; padding: 1px 5px; border-radius: 4px; margin-right: 8px; }
|
||||
.event-time { margin-left: auto; }
|
||||
.event-id { font-weight: bold; color: #2c3e50; background: #e2e8f0; padding: 1px 5px; border-radius: 3px; margin-right: 8px; font-size: 0.75rem; }
|
||||
.event-time { margin-left: auto; font-size: 0.75rem; color: #95a5a6; }
|
||||
.content-box { padding: 5px 0; }
|
||||
.main-text { font-weight: 500; color: #2c3e50; margin-bottom: 10px; }
|
||||
|
||||
/* 动画部分 */
|
||||
@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;
|
||||
}
|
||||
.main-text { font-weight: 500; color: #2c3e50; margin-bottom: 8px; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user