From a255cf9be0eb2aa15296b3df00ab6d04ac5452fb Mon Sep 17 00:00:00 2001 From: Julian Freeman Date: Mon, 2 Mar 2026 19:42:20 -0400 Subject: [PATCH] fix many things --- src-tauri/src/cleaner.rs | 79 ++++++- src-tauri/src/lib.rs | 2 +- src/App.vue | 482 ++++++++++++++++++++++++++++----------- 3 files changed, 425 insertions(+), 138 deletions(-) diff --git a/src-tauri/src/cleaner.rs b/src-tauri/src/cleaner.rs index a4f1985..d6501cd 100644 --- a/src-tauri/src/cleaner.rs +++ b/src-tauri/src/cleaner.rs @@ -201,6 +201,81 @@ fn get_dir_stats(path: &Path, filter_days: Option) -> (u64, u32) { (size, count) } -pub async fn run_fast_clean(_is_simulation: bool) -> Result { - Ok("快速清理任务已成功模拟执行。".into()) +#[derive(Serialize)] +pub struct CleanResult { + pub total_freed: String, + pub success_count: u32, + pub fail_count: u32, +} + +pub async fn run_fast_clean(is_simulation: bool) -> Result { + if is_simulation { + return Ok(CleanResult { + total_freed: "0 B".into(), + success_count: 0, + fail_count: 0, + }); + } + + let mut success_count = 0; + let mut fail_count = 0; + let mut total_freed: u64 = 0; + + let mut target_paths = Vec::new(); + if let Ok(t) = std::env::var("TEMP") { target_paths.push(t); } + target_paths.push("C:\\Windows\\Temp".into()); + target_paths.push("C:\\Windows\\SoftwareDistribution\\Download".into()); + target_paths.push("C:\\Windows\\ServiceProfiles\\NetworkService\\AppData\\Local\\Microsoft\\Windows\\DeliveryOptimization".into()); + + for path_str in target_paths { + let path = Path::new(&path_str); + if path.exists() { + let (freed, s, f) = clean_directory_contents(path); + total_freed += freed; + success_count += s; + fail_count += f; + } + } + + Ok(CleanResult { + total_freed: format_size(total_freed), + success_count, + fail_count, + }) +} + +fn clean_directory_contents(path: &Path) -> (u64, u32, u32) { + let mut freed = 0; + let mut success = 0; + let mut fail = 0; + + if let Ok(entries) = fs::read_dir(path) { + for entry in entries.filter_map(|e| e.ok()) { + let entry_path = entry.path(); + let metadata = entry.metadata(); + let size = metadata.as_ref().map(|m| m.len()).unwrap_or(0); + + if entry_path.is_file() { + if fs::remove_file(&entry_path).is_ok() { + freed += size; + success += 1; + } else { + fail += 1; + } + } else if entry_path.is_dir() { + // 递归清理子目录 + let (f, s, fl) = clean_directory_contents(&entry_path); + freed += f; + success += s; + fail += fl; + // 尝试删除已清空的目录 + if fs::remove_dir(&entry_path).is_ok() { + success += 1; + } else { + fail += 1; + } + } + } + } + (freed, success, fail) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 87a72f0..fef41f2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -9,7 +9,7 @@ async fn start_fast_scan() -> cleaner::FastScanResult { } #[tauri::command] -async fn start_fast_clean(is_simulation: bool) -> Result { +async fn start_fast_clean(is_simulation: bool) -> Result { cleaner::run_fast_clean(is_simulation).await } diff --git a/src/App.vue b/src/App.vue index d8e7fc1..06be5e1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -11,6 +11,7 @@ const isCMenuOpen = ref(true); // --- 数据结构 --- interface ScanItem { name: string; path: string; size: number; count: number; } interface FastScanResult { items: ScanItem[]; total_size: string; total_count: number; } +interface CleanResult { total_freed: string; success_count: number; fail_count: number; } interface FileNode { name: string; path: string; is_dir: boolean; size: number; size_str: string; percent: number; has_children: boolean; @@ -20,13 +21,25 @@ interface FileNode { // --- 状态管理 --- const isScanning = ref(false); const isCleaning = ref(false); -const isSimulation = ref(false); const isCleanDone = ref(false); const isFullScanning = ref(false); const scanProgress = ref(0); const fastScanResult = ref(null); +const cleanResult = ref(null); const treeData = ref([]); -const cleanMessage = ref(""); + +// --- 弹窗状态 --- +const showModal = ref(false); +const modalTitle = ref(""); +const modalMessage = ref(""); +const modalType = ref<'info' | 'success' | 'error'>('info'); + +function showAlert(title: string, message: string, type: 'info' | 'success' | 'error' = 'info') { + modalTitle.value = title; + modalMessage.value = message; + modalType.value = type; + showModal.value = true; +} // 高级模式特有 const expandedAdvanced = ref(null); @@ -44,7 +57,7 @@ async function startFastScan() { scanProgress.value = 100; fastScanResult.value = result; } catch (err) { - alert("扫描失败,请尝试以管理员身份运行。"); + showAlert("扫描失败", "请尝试以管理员身份运行程序。", 'error'); } finally { clearInterval(interval); isScanning.value = false; @@ -55,12 +68,12 @@ async function startFastClean() { if (isCleaning.value) return; isCleaning.value = true; try { - const msg = await invoke("start_fast_clean", { isSimulation: isSimulation.value }); - cleanMessage.value = msg; + const res = await invoke("start_fast_clean", { isSimulation: false }); + cleanResult.value = res; isCleanDone.value = true; fastScanResult.value = null; } catch (err) { - alert("清理失败: " + err); + showAlert("清理失败", String(err), 'error'); } finally { isCleaning.value = false; } } @@ -69,14 +82,15 @@ async function runAdvancedTask(task: string) { advLoading.value[task] = true; try { let cmd = ""; - if (task === 'dism') cmd = "clean_system_components"; - else if (task === 'thumb') cmd = "clean_thumbnails"; - else if (task === 'hiber') cmd = "disable_hibernation"; + let title = ""; + if (task === 'dism') { cmd = "clean_system_components"; title = "系统组件清理"; } + else if (task === 'thumb') { cmd = "clean_thumbnails"; title = "缩略图清理"; } + else if (task === 'hiber') { cmd = "disable_hibernation"; title = "休眠文件优化"; } const res = await invoke(cmd); - alert(res); + showAlert(title, res, 'success'); } catch (err) { - alert("执行失败: " + err); + showAlert("任务失败", String(err), 'error'); } finally { advLoading.value[task] = false; } @@ -124,8 +138,8 @@ async function toggleNode(index: number) { function resetAll() { isCleanDone.value = false; fastScanResult.value = null; + cleanResult.value = null; treeData.value = []; - cleanMessage.value = ""; } function formatItemSize(bytes: number): string { @@ -240,28 +254,33 @@ function formatItemSize(bytes: number): string { -
- - 模拟清理 (不实际删除文件) -
- -
+
- + 🎉

清理完成

-

{{ cleanMessage }}

-
-

您的 C 盘现在更加清爽了!

+ +
+
+ {{ cleanResult.total_freed }} + 释放空间 +
+
+
+ {{ cleanResult.success_count }} + 成功清理 +
+
+
+ {{ cleanResult.fail_count }} + 跳过/失败 +
@@ -366,7 +385,7 @@ function formatItemSize(bytes: number): string { -
+
+ + +
@@ -435,22 +474,24 @@ function formatItemSize(bytes: number): string { :root { --primary-color: #007AFF; --primary-hover: #0063CC; - --bg-light: #F9FAFB; + --bg-light: #FBFBFD; --sidebar-bg: #FFFFFF; --text-main: #1D1D1F; --text-sec: #86868B; --border-color: #E5E5E7; - --card-shadow: 0 8px 24px rgba(0, 0, 0, 0.05); + --card-shadow: 0 12px 30px rgba(0, 0, 0, 0.04); + --btn-shadow: 0 4px 12px rgba(0, 122, 255, 0.25); } * { box-sizing: border-box; margin: 0; padding: 0; } body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif; color: var(--text-main); background-color: var(--bg-light); height: 100vh; - overflow: hidden; /* 锁定主体,由 .content 滚动 */ + overflow: hidden; + -webkit-font-smoothing: antialiased; } #app { height: 100%; } @@ -460,101 +501,105 @@ body { height: 100%; } -/* --- 侧边栏精确恢复 --- */ +/* --- 侧边栏优化 --- */ .sidebar { - width: 260px; + width: 250px; background-color: var(--sidebar-bg); border-right: 1px solid var(--border-color); display: flex; flex-direction: column; - padding: 24px 0; + padding: 32px 0; z-index: 10; } -.sidebar-header { padding: 0 24px 32px; } -.brand { font-size: 22px; font-weight: 800; color: var(--primary-color); letter-spacing: -0.5px; } +.sidebar-header { padding: 0 28px 36px; } +.brand { font-size: 20px; font-weight: 700; color: var(--text-main); letter-spacing: -0.3px; } .sidebar-nav { flex: 1; } .nav-item, .nav-item-header { - padding: 12px 24px; + padding: 10px 24px; + margin: 2px 12px; display: flex; align-items: center; cursor: pointer; - transition: all 0.2s; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); color: #424245; - font-size: 15px; + font-size: 14px; font-weight: 500; + border-radius: 8px; } .nav-item:hover, .nav-item-header:hover { background-color: #F5F5F7; - color: var(--primary-color); } .nav-item.active { background-color: #F0F7FF; color: var(--primary-color); font-weight: 600; - border-right: 3px solid var(--primary-color); } -.icon { margin-right: 12px; font-size: 18px; } +.icon { margin-right: 10px; font-size: 16px; width: 20px; text-align: center; } .arrow { margin-left: auto; transition: transform 0.3s; - font-size: 12px; + font-size: 10px; color: #C1C1C1; } .arrow.open { transform: rotate(180deg); } -.nav-sub-items { background-color: #FAFAFA; } +.nav-sub-items { margin-bottom: 8px; } .nav-sub-item { - padding: 10px 24px 10px 54px; + padding: 8px 24px 8px 54px; + margin: 1px 12px; cursor: pointer; - font-size: 14px; + font-size: 13px; color: #6E6E73; transition: all 0.2s; + border-radius: 6px; } -.nav-sub-item:hover { color: var(--primary-color); } +.nav-sub-item:hover { background-color: #F5F5F7; color: var(--text-main); } .nav-sub-item.active { color: var(--primary-color); font-weight: 600; background-color: #F0F7FF; } -.sidebar-footer { padding: 16px 24px; border-top: 1px solid var(--border-color); } -.version { font-size: 12px; color: var(--text-sec); } +.sidebar-footer { padding: 16px 28px; border-top: 1px solid var(--border-color); } +.version { font-size: 11px; color: var(--text-sec); font-weight: 500; } -/* --- 内容区滚动修复 --- */ +/* --- 内容区 --- */ .content { flex: 1; - padding: 40px; - overflow-y: auto; /* 关键:确保右侧滚动 */ + padding: 48px 60px; + overflow-y: auto; height: 100%; } -.page-container { max-width: 900px; margin: 0 auto; padding-bottom: 60px; } -.page-header { margin-bottom: 32px; } -.page-header h1 { font-size: 32px; font-weight: 800; margin-bottom: 8px; letter-spacing: -0.5px; } -.page-header p { color: var(--text-sec); font-size: 16px; } +.page-container { max-width: 800px; margin: 0 auto; padding-bottom: 60px; transition: max-width 0.4s ease; } +.page-container.full-width { max-width: 1400px; } +.page-header { margin-bottom: 40px; text-align: center; } +.page-header h1 { font-size: 28px; font-weight: 700; margin-bottom: 8px; color: var(--text-main); } +.page-header p { color: var(--text-sec); font-size: 15px; } -/* --- 按钮样式精确恢复 --- */ +/* --- 按钮样式重构 --- */ .btn-primary { background-color: var(--primary-color); color: white; border: none; - padding: 14px 32px; - border-radius: 12px; + padding: 14px 44px; + border-radius: 14px; font-size: 16px; font-weight: 600; cursor: pointer; - transition: all 0.2s; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: var(--btn-shadow); } -.btn-primary:hover { background-color: var(--primary-hover); transform: translateY(-1px); } +.btn-primary:hover { background-color: var(--primary-hover); transform: translateY(-1.5px); box-shadow: 0 6px 16px rgba(0, 122, 255, 0.35); } .btn-primary:active { transform: translateY(0); } -.btn-primary:disabled { background-color: #A5C7FF; cursor: not-allowed; transform: none; } +.btn-primary:disabled { background-color: #D1D1D6; box-shadow: none; cursor: not-allowed; transform: none; } .btn-secondary { background-color: white; @@ -567,54 +612,80 @@ body { cursor: pointer; transition: all 0.2s; } -.btn-secondary:hover { background-color: #F5F5F7; border-color: #D1D1D6; } +.btn-secondary:hover { background-color: #F5F5F7; } -.btn-action { - background-color: #F2F2F7; - color: var(--primary-color); - border: none; - padding: 8px 20px; - border-radius: 8px; - font-weight: 700; - cursor: pointer; - transition: all 0.2s; - min-width: 80px; -} -.btn-action:hover { background-color: var(--primary-color); color: #fff; } - -/* --- 快速清理特有 UI --- */ -.main-action { margin: 60px 0; display: flex; justify-content: center; } -.scan-circle-container { width: 220px; height: 220px; } -.scan-circle { - width: 100%; height: 100%; border-radius: 50%; border: 4px solid var(--border-color); - display: flex; align-items: center; justify-content: center; position: relative; - transition: all 0.5s; -} -.scan-circle.scanning { border-color: var(--primary-color); box-shadow: 0 0 40px rgba(0, 122, 255, 0.15); animation: pulse 2s infinite; } -.scan-inner { - width: 190px; height: 190px; border-radius: 50%; background: white; - display: flex; align-items: center; justify-content: center; - cursor: pointer; box-shadow: var(--card-shadow); transition: transform 0.2s; -} -.scan-inner:hover { transform: scale(1.05); } -.scan-btn-text { font-size: 22px; font-weight: 700; color: var(--primary-color); } -.scan-percent { font-size: 36px; font-weight: 800; color: var(--primary-color); } +/* --- 扫描结果卡片 (重点优化) --- */ +.main-action { margin: 40px 0; display: flex; justify-content: center; } .result-card { - background: white; border-radius: 24px; padding: 40px; width: 100%; - box-shadow: var(--card-shadow); text-align: center; border: 1px solid rgba(0,0,0,0.03); + background: white; + border-radius: 28px; + padding: 48px; + width: 100%; + box-shadow: var(--card-shadow); + text-align: center; + border: 1px solid rgba(0,0,0,0.02); } -.result-stats { display: flex; justify-content: center; align-items: center; margin-bottom: 32px; } -.stat-value { display: block; font-size: 36px; font-weight: 800; color: var(--primary-color); } -.stat-divider { width: 1px; height: 50px; background-color: var(--border-color); margin: 0 40px; } -.simulation-toggle { display: flex; align-items: center; justify-content: center; margin-bottom: 24px; gap: 12px; } -.switch { position: relative; display: inline-block; width: 44px; height: 24px; } -.switch input { opacity: 0; width: 0; height: 0; } -.slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #E5E5E7; transition: .4s; border-radius: 24px; } -.slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } -input:checked + .slider { background-color: var(--primary-color); } -input:checked + .slider:before { transform: translateX(20px); } +.result-header { margin-bottom: 32px; } +.result-icon { font-size: 32px; display: block; margin-bottom: 12px; } +.result-header h2 { font-size: 24px; font-weight: 700; color: var(--text-main); } + +.result-stats { + display: flex; + justify-content: center; + align-items: baseline; + margin-bottom: 40px; +} + +.stat-item { flex: 1; } +.stat-value { + display: block; + font-size: 48px; + font-weight: 800; + color: var(--primary-color); + letter-spacing: -1px; + line-height: 1.1; + margin-bottom: 8px; +} +.stat-label { font-size: 15px; color: var(--text-sec); font-weight: 500; } + +.stat-divider { + width: 1px; + height: 60px; + background-color: #F2F2F7; + margin: 0 40px; + align-self: center; +} + +.main-btn { width: 220px; } + +/* --- 扫描中 UI --- */ +.scan-circle-container { width: 240px; height: 240px; margin: 0 auto; } +.scan-circle { + width: 100%; height: 100%; border-radius: 50%; border: 2px solid #F2F2F7; + display: flex; align-items: center; justify-content: center; position: relative; +} +.scan-circle.scanning { border-color: transparent; } +.scan-circle.scanning::before { + content: ""; + position: absolute; + top: -2px; left: -2px; right: -2px; bottom: -2px; + border-radius: 50%; + border: 4px solid var(--primary-color); + border-top-color: transparent; + animation: spin 1s linear infinite; +} +.scan-inner { + width: 200px; height: 200px; border-radius: 50%; background: white; + display: flex; align-items: center; justify-content: center; + cursor: pointer; box-shadow: 0 10px 25px rgba(0,0,0,0.05); transition: transform 0.2s ease; +} +.scan-inner:hover { transform: scale(1.03); } +.scan-btn-text { font-size: 20px; font-weight: 700; color: var(--primary-color); } +.scan-percent { font-size: 40px; font-weight: 800; color: var(--primary-color); letter-spacing: -1px; } + +.stat-value.highlight-gray { color: #8E8E93; } .done-card { border: 2px solid #E8F5E9; } .result-icon.success { color: #34C759; font-size: 54px; margin-bottom: 16px; display: block; } @@ -622,48 +693,189 @@ input:checked + .slider:before { transform: translateX(20px); } /* --- 高级模式卡片 --- */ .adv-card-list { display: flex; flex-direction: column; gap: 20px; } -.adv-card { background: #fff; border-radius: 16px; box-shadow: var(--card-shadow); border: 1px solid rgba(0,0,0,0.03); overflow: hidden; transition: all 0.3s; } -.adv-card-main { padding: 24px; display: flex; align-items: center; justify-content: space-between; cursor: pointer; } +.adv-card { + background: #fff; + border-radius: 20px; + box-shadow: var(--card-shadow); + border: 1px solid rgba(0,0,0,0.02); + overflow: hidden; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} +.adv-card:hover { transform: translateY(-2px); box-shadow: 0 15px 35px rgba(0,0,0,0.06); } + +.adv-card-main { + padding: 24px 32px; + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; +} .adv-card-main:hover { background: #FAFAFA; } -.adv-card-info { display: flex; align-items: center; gap: 20px; } + +.adv-card-info { display: flex; align-items: center; gap: 24px; } .adv-card-icon { font-size: 32px; } -.adv-card-text h3 { font-size: 18px; margin-bottom: 4px; font-weight: 700; } +.adv-card-text h3 { font-size: 18px; margin-bottom: 4px; font-weight: 700; color: var(--text-main); } .adv-card-text p { color: var(--text-sec); font-size: 14px; } -.adv-card-detail { padding: 0 24px 24px 76px; border-top: 1px solid #F2F2F7; background: #FAFAFA; } -.detail-content { padding-top: 20px; } -.detail-content h4 { font-size: 14px; margin-bottom: 8px; color: var(--text-main); font-weight: 700; } + +.btn-action { + background-color: #F2F2F7; + color: var(--primary-color); + border: none; + padding: 10px 24px; + border-radius: 10px; + font-weight: 700; + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; + min-width: 90px; +} +.btn-action:hover { background-color: var(--primary-color); color: #fff; transform: scale(1.05); } +.btn-action:disabled { background-color: #E5E5E7; color: #A1A1A1; cursor: not-allowed; transform: none; } + +.adv-card-detail { + padding: 0 32px 32px 88px; + border-top: 1px solid #F5F5F7; + background: #FCFCFD; +} +.detail-content { padding-top: 24px; } +.detail-content h4 { font-size: 14px; margin-bottom: 10px; color: var(--text-main); font-weight: 700; } .detail-content p { font-size: 14px; color: var(--text-sec); line-height: 1.6; margin-bottom: 16px; } -.warning-title { color: #FF9500 !important; margin-top: 16px; } -.detail-content ul { padding-left: 20px; color: var(--text-sec); font-size: 13px; } -.detail-content li { margin-bottom: 6px; } +.warning-title { color: #FF9500 !important; margin-top: 20px; } +.detail-content ul { padding-left: 18px; color: var(--text-sec); font-size: 13px; } +.detail-content li { margin-bottom: 8px; line-height: 1.4; } /* --- 磁盘树样式 --- */ -.tree-table-container { background: #fff; border-radius: 16px; overflow: hidden; margin-top: 24px; min-height: 400px; box-shadow: var(--card-shadow); border: 1px solid rgba(0,0,0,0.03); } -.tree-header { display: flex; background: #F5F5F7; padding: 14px 20px; font-size: 13px; font-weight: 700; color: var(--text-sec); border-bottom: 1px solid var(--border-color); } -.tree-body { max-height: 600px; overflow-y: auto; } -.tree-row { display: flex; align-items: center; padding: 12px 20px; border-bottom: 1px solid #F2F2F7; font-size: 14px; transition: background 0.1s; } -.tree-row:hover { background: #F9FAFB; } -.col-name { flex: 2; display: flex; align-items: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.col-size { width: 110px; text-align: right; } -.col-graph { width: 200px; display: flex; align-items: center; gap: 12px; padding-left: 30px; } -.mini-bar-bg { flex: 1; height: 6px; background: #F2F2F7; border-radius: 3px; overflow: hidden; } -.mini-bar-fill { height: 100%; background: linear-gradient(90deg, #007AFF, #5856D6); border-radius: 3px; } -.percent-text { font-size: 11px; color: var(--text-sec); width: 35px; font-weight: 600; } -.node-toggle { width: 24px; cursor: pointer; color: #C1C1C1; display: inline-block; text-align: center; } -.node-icon { width: 24px; font-size: 14px; } +.advanced-actions { display: flex; justify-content: center; margin-bottom: 32px; } +.tree-table-container { + background: #fff; + border-radius: 24px; + overflow: hidden; + margin-top: 32px; + min-height: 400px; + box-shadow: var(--card-shadow); + border: 1px solid rgba(0,0,0,0.02); +} +.tree-header { + display: flex; + background: #F9FAFB; + padding: 16px 24px; + font-size: 12px; + font-weight: 700; + color: var(--text-sec); + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 1px solid var(--border-color); +} +.tree-body { max-height: 550px; overflow-y: auto; } +.tree-row { + display: flex; + align-items: center; + padding: 14px 24px; + border-bottom: 1px solid #F5F5F7; + font-size: 14px; + transition: background 0.15s ease; +} +.tree-row:hover { background: #F9F9FB; } +.tree-row.is-file { color: #424245; } + +.col-name { + flex: 2; + display: flex; + align-items: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-weight: 500; +} +.col-size { width: 100px; text-align: right; font-weight: 600; color: var(--text-main); } +.col-graph { width: 180px; display: flex; align-items: center; gap: 12px; padding-left: 32px; } + +.mini-bar-bg { flex: 1; height: 6px; background: #F0F0F2; border-radius: 3px; overflow: hidden; } +.mini-bar-fill { + height: 100%; + background: linear-gradient(90deg, #007AFF, #5856D6); + border-radius: 3px; + transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} +.percent-text { font-size: 11px; color: var(--text-sec); width: 32px; font-weight: 600; text-align: right; } +.node-toggle { width: 24px; cursor: pointer; color: #C1C1C1; display: inline-block; text-align: center; font-size: 10px; transition: color 0.2s; } +.node-toggle:hover { color: var(--primary-color); } +.node-icon { width: 24px; font-size: 14px; opacity: 0.7; } /* --- 通用状态 --- */ -.scanning-loader, .scanning-overlay { padding: 80px 40px; text-align: center; color: var(--text-sec); } -.spinner { width: 40px; height: 40px; border: 4px solid #f3f3f3; border-top: 4px solid var(--primary-color); border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 20px; } +.scanning-loader, .scanning-overlay { padding: 100px 40px; text-align: center; color: var(--text-sec); } +.spinner { + width: 44px; height: 44px; + border: 3px solid #F2F2F7; + border-top: 3px solid var(--primary-color); + border-radius: 50%; + animation: spin 0.8s linear infinite; + margin: 0 auto 24px; +} -.detail-list { background: white; border-radius: 16px; padding: 24px; box-shadow: var(--card-shadow); margin-top: 32px; border: 1px solid rgba(0,0,0,0.02); } -.detail-list h3 { font-size: 17px; margin-bottom: 16px; font-weight: 700; } -.detail-item { display: flex; justify-content: space-between; padding: 14px 0; border-bottom: 1px solid #F2F2F7; font-size: 14px; color: #424245; } +.detail-list { + background: white; + border-radius: 20px; + padding: 32px; + box-shadow: var(--card-shadow); + margin-top: 40px; + border: 1px solid rgba(0,0,0,0.02); +} +.detail-list h3 { font-size: 18px; margin-bottom: 20px; font-weight: 700; color: var(--text-main); } +.detail-item { + display: flex; + justify-content: space-between; + padding: 16px 0; + border-bottom: 1px solid #F5F5F7; + font-size: 14px; + color: #424245; + transition: transform 0.2s ease; +} +.detail-item:hover { transform: translateX(4px); } +.detail-item:last-child { border-bottom: none; } .item-size { font-weight: 600; color: var(--primary-color); } -.placeholder-page { padding-top: 100px; text-align: center; color: var(--text-sec); } -.empty-icon { font-size: 64px; display: block; margin-bottom: 24px; } +.placeholder-page { padding-top: 120px; text-align: center; color: var(--text-sec); } +.empty-icon { font-size: 64px; display: block; margin-bottom: 24px; opacity: 0.5; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes pulse { 0% { box-shadow: 0 0 0 0 rgba(0, 122, 255, 0.3); } 70% { box-shadow: 0 0 0 25px rgba(0, 122, 255, 0); } 100% { box-shadow: 0 0 0 0 rgba(0, 122, 255, 0); } } + +/* --- 自定义弹窗样式 --- */ +.modal-overlay { + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + background-color: rgba(0, 0, 0, 0.2); + backdrop-filter: blur(8px); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + animation: fadeIn 0.2s ease; +} + +.modal-card { + background: white; + width: 400px; + border-radius: 24px; + padding: 32px; + box-shadow: 0 20px 60px rgba(0,0,0,0.15); + text-align: center; + animation: modalIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.modal-header { margin-bottom: 20px; } +.modal-icon { font-size: 40px; display: block; margin-bottom: 12px; } +.modal-header h3 { font-size: 20px; font-weight: 700; color: var(--text-main); } + +.modal-card.success .modal-header h3 { color: #34C759; } +.modal-card.error .modal-header h3 { color: #FF3B30; } + +.modal-body { margin-bottom: 32px; } +.modal-body p { color: var(--text-sec); font-size: 15px; line-height: 1.6; } + +.modal-footer { display: flex; justify-content: center; } + +@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } +@keyframes modalIn { from { opacity: 0; transform: scale(0.9) translateY(20px); } to { opacity: 1; transform: scale(1) translateY(0); } } \ No newline at end of file