This commit is contained in:
Julian Freeman
2026-03-01 19:57:21 -04:00
parent a170e2a4bd
commit 6f1d625c00
5 changed files with 382 additions and 103 deletions

View File

@@ -1,7 +1,3 @@
# Tauri + Vue + TypeScript
# Windows 清理工具
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
用 Gemini CLI 生成。

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tauri + Vue + Typescript App</title>
<title>Windows 清理工具</title>
</head>
<body>

View File

@@ -1,8 +1,8 @@
[package]
name = "win-cleaner"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
description = "A Windows Cleaner"
authors = ["Julian"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -12,9 +12,9 @@
"app": {
"windows": [
{
"title": "win-cleaner",
"width": 800,
"height": 600
"title": "Windows 清理工具",
"width": 1400,
"height": 900
}
],
"security": {

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref } from "vue";
import { invoke } from "@tauri-apps/api/core";
import pkg from "../package.json";
// --- 导航状态 ---
type Tab = 'clean-c-fast' | 'clean-c-advanced' | 'clean-c-deep' | 'clean-browser' | 'clean-memory';
@@ -27,7 +28,7 @@ const fastScanResult = ref<FastScanResult | null>(null);
const treeData = ref<FileNode[]>([]);
const cleanMessage = ref("");
// 高级模式展开状态
// 高级模式特有
const expandedAdvanced = ref<string | null>(null);
const advLoading = ref<Record<string, boolean>>({});
@@ -58,6 +59,8 @@ async function startFastClean() {
cleanMessage.value = msg;
isCleanDone.value = true;
fastScanResult.value = null;
} catch (err) {
alert("清理失败: " + err);
} finally { isCleaning.value = false; }
}
@@ -79,7 +82,7 @@ async function runAdvancedTask(task: string) {
}
}
// --- 深度分析 (原高级模式) 逻辑 ---
// --- 深度分析 (TreeSize) 逻辑 ---
async function startFullDiskScan() {
isFullScanning.value = true;
treeData.value = [];
@@ -112,6 +115,8 @@ async function toggleNode(index: number) {
const mapped = children.map(c => ({ ...c, level: node.level + 1, isOpen: false, isLoading: false }));
treeData.value.splice(index + 1, 0, ...mapped);
node.isOpen = true;
} catch (err) {
console.error(err);
} finally { node.isLoading = false; }
}
}
@@ -134,70 +139,153 @@ function formatItemSize(bytes: number): string {
<template>
<div class="app-container">
<!-- 侧边栏 -->
<aside class="sidebar">
<div class="sidebar-header"><h2 class="brand">WinCleaner</h2></div>
<div class="sidebar-header">
<h2 class="brand">Windows 清理工具</h2>
</div>
<nav class="sidebar-nav">
<!-- 清理 C 盘组 -->
<div class="nav-group">
<div class="nav-item-header" @click="isCMenuOpen = !isCMenuOpen">
<span class="icon">💾</span><span class="label">清理 C </span>
<span class="icon">💾</span>
<span class="label">清理 C </span>
<span class="arrow" :class="{ open: isCMenuOpen }"></span>
</div>
<div class="nav-sub-items" v-show="isCMenuOpen">
<div class="nav-sub-item" :class="{ active: activeTab === 'clean-c-fast' }" @click="activeTab = 'clean-c-fast'">快速模式</div>
<div class="nav-sub-item" :class="{ active: activeTab === 'clean-c-advanced' }" @click="activeTab = 'clean-c-advanced'">高级模式</div>
<div class="nav-sub-item" :class="{ active: activeTab === 'clean-c-deep' }" @click="activeTab = 'clean-c-deep'">深度分析</div>
<div
class="nav-sub-item"
:class="{ active: activeTab === 'clean-c-fast' }"
@click="activeTab = 'clean-c-fast'"
>
快速模式
</div>
<div
class="nav-sub-item"
:class="{ active: activeTab === 'clean-c-advanced' }"
@click="activeTab = 'clean-c-advanced'"
>
高级模式
</div>
<div
class="nav-sub-item"
:class="{ active: activeTab === 'clean-c-deep' }"
@click="activeTab = 'clean-c-deep'"
>
深度分析
</div>
</div>
</div>
<div class="nav-item" :class="{ active: activeTab === 'clean-browser' }" @click="activeTab = 'clean-browser'">
<span class="icon">🌐</span><span class="label">清理浏览器</span>
<!-- 其它项 -->
<div
class="nav-item"
:class="{ active: activeTab === 'clean-browser' }"
@click="activeTab = 'clean-browser'"
>
<span class="icon">🌐</span>
<span class="label">清理浏览器</span>
</div>
<div class="nav-item" :class="{ active: activeTab === 'clean-memory' }" @click="activeTab = 'clean-memory'">
<span class="icon">🚀</span><span class="label">清理内存</span>
<div
class="nav-item"
:class="{ active: activeTab === 'clean-memory' }"
@click="activeTab = 'clean-memory'"
>
<span class="icon">🚀</span>
<span class="label">清理内存</span>
</div>
</nav>
<div class="sidebar-footer"><span class="version">v1.0.0</span></div>
<div class="sidebar-footer">
<span class="version">v{{ pkg.version }}</span>
</div>
</aside>
<!-- 内容区 -->
<main class="content">
<!-- 1. 快速清理 -->
<!-- 1. 快速清理页面 -->
<section v-if="activeTab === 'clean-c-fast'" class="page-container">
<div class="page-header"><h1>快速清理系统盘</h1><p>一键释放 C 盘空间不影响系统安全</p></div>
<div class="page-header">
<h1>快速清理系统盘</h1>
<p>一键释放 C 盘空间不影响系统运行</p>
</div>
<div class="main-action">
<!-- 扫描前/ -->
<div class="scan-circle-container" v-if="!fastScanResult && !isCleanDone">
<div class="scan-circle" :class="{ scanning: isScanning }">
<div class="scan-inner" @click="!isScanning && startFastScan()"><span v-if="!isScanning" class="scan-btn-text">开始扫描</span><span v-else class="scan-percent">{{ scanProgress }}%</span></div>
<div class="scan-inner" @click="!isScanning && startFastScan()">
<span v-if="!isScanning" class="scan-btn-text">开始扫描</span>
<span v-else class="scan-percent">{{ scanProgress }}%</span>
</div>
</div>
</div>
<!-- 扫描完成 -->
<div class="result-card" v-else-if="fastScanResult && !isCleanDone">
<div class="result-header"><span class="result-icon">📋</span><h2>扫描完成</h2></div>
<div class="result-stats">
<div class="stat-item"><span class="stat-value">{{ fastScanResult.total_size }}</span><span class="stat-label">预计释放</span></div>
<div class="stat-divider"></div>
<div class="stat-item"><span class="stat-value">{{ fastScanResult.total_count }}</span><span class="stat-label">文件数量</span></div>
<div class="result-header">
<span class="result-icon">📋</span>
<h2>扫描完成</h2>
</div>
<div class="result-stats">
<div class="stat-item">
<span class="stat-value">{{ fastScanResult.total_size }}</span>
<span class="stat-label">预计释放</span>
</div>
<div class="stat-divider"></div>
<div class="stat-item">
<span class="stat-value">{{ fastScanResult.total_count }}</span>
<span class="stat-label">文件数量</span>
</div>
</div>
<div class="simulation-toggle">
<label class="switch"><input type="checkbox" v-model="isSimulation"><span class="slider round"></span></label>
<label class="switch">
<input type="checkbox" v-model="isSimulation">
<span class="slider round"></span>
</label>
<span class="toggle-label">模拟清理 (不实际删除文件)</span>
</div>
<button class="btn-primary main-btn" @click="startFastClean" :disabled="isCleaning">{{ isCleaning ? '正在清理...' : (isSimulation ? '开始模拟清理' : '立即清理') }}</button>
<button class="btn-primary main-btn" @click="startFastClean" :disabled="isCleaning">
{{ isCleaning ? '正在清理...' : (isSimulation ? '开始模拟清理' : '立即清理') }}
</button>
</div>
<!-- 清理完成报告 -->
<div class="result-card done-card" v-else-if="isCleanDone">
<div class="result-header"><span class="result-icon success"></span><h2>清理完成</h2><p class="clean-summary">{{ cleanMessage }}</p></div>
<div class="result-header">
<span class="result-icon success"></span>
<h2>清理完成</h2>
<p class="clean-summary">{{ cleanMessage }}</p>
</div>
<div class="done-info">
<p>您的 C 盘现在更加清爽了</p>
</div>
<button class="btn-secondary" @click="resetAll">返回首页</button>
</div>
</div>
<div class="detail-list" v-if="(isScanning || fastScanResult) && !isCleanDone">
<h3>清理项详情</h3>
<div class="detail-item" v-for="item in fastScanResult?.items || []" :key="item.path"><span>{{ item.name }}</span><span class="item-size">{{ formatItemSize(item.size) }}</span></div>
<div class="detail-item" v-for="item in fastScanResult?.items || []" :key="item.path">
<span>{{ item.name }}</span>
<span class="item-size">{{ formatItemSize(item.size) }}</span>
</div>
<div v-if="isScanning" class="scanning-placeholder">正在深度扫描文件系统...</div>
</div>
</section>
<!-- 2. 高级模式 (新增) -->
<!-- 2. 高级模式页面 -->
<section v-else-if="activeTab === 'clean-c-advanced'" class="page-container">
<div class="page-header"><h1>高级清理工具</h1><p>执行深层系统优化释放更多被占用的磁盘空间</p></div>
<div class="page-header">
<h1>高级清理工具</h1>
<p>执行深层系统优化释放更多被占用的磁盘空间</p>
</div>
<div class="adv-card-list">
<!-- 1: DISM -->
<!-- 系统组件 -->
<div class="adv-card" :class="{ expanded: expandedAdvanced === 'dism' }">
<div class="adv-card-main" @click="expandedAdvanced = expandedAdvanced === 'dism' ? null : 'dism'">
<div class="adv-card-info">
@@ -225,7 +313,7 @@ function formatItemSize(bytes: number): string {
</div>
</div>
<!-- 2: 缩略图 -->
<!-- 缩略图 -->
<div class="adv-card" :class="{ expanded: expandedAdvanced === 'thumb' }">
<div class="adv-card-main" @click="expandedAdvanced = expandedAdvanced === 'thumb' ? null : 'thumb'">
<div class="adv-card-info">
@@ -250,7 +338,7 @@ function formatItemSize(bytes: number): string {
</div>
</div>
<!-- 3: 休眠文件 -->
<!-- 休眠 -->
<div class="adv-card" :class="{ expanded: expandedAdvanced === 'hiber' }">
<div class="adv-card-main" @click="expandedAdvanced = expandedAdvanced === 'hiber' ? null : 'hiber'">
<div class="adv-card-info">
@@ -277,21 +365,53 @@ function formatItemSize(bytes: number): string {
</div>
</section>
<!-- 3. 深度分析 (原高级模式) -->
<!-- 3. 深度分析页面 -->
<section v-else-if="activeTab === 'clean-c-deep'" class="page-container advanced-page">
<div class="page-header"><h1>磁盘树深度分析</h1><p>层级化查看 C 盘占用锁定空间大户</p></div>
<div class="advanced-actions"><button class="btn-primary" @click="startFullDiskScan" :disabled="isFullScanning">{{ isFullScanning ? '正在建立索引...' : '开始深度分析' }}</button></div>
<div class="page-header">
<h1>查找大目录</h1>
<p>层级化查看 C 盘占用锁定空间大户</p>
</div>
<div class="advanced-actions">
<button class="btn-primary" @click="startFullDiskScan" :disabled="isFullScanning">
{{ isFullScanning ? '正在建立全盘索引...' : '开始深度分析' }}
</button>
</div>
<div class="tree-table-container shadow-card" v-if="treeData.length > 0 || isFullScanning">
<div v-if="isFullScanning" class="scanning-overlay"><div class="spinner"></div><p>正在分析数百万个文件...</p></div>
<div v-if="isFullScanning" class="scanning-overlay">
<div class="spinner"></div>
<p>正在分析数百万个文件请稍候...</p>
</div>
<div v-else>
<div class="tree-header"><span class="col-name">名称</span><span class="col-size">大小</span><span class="col-graph">占比</span></div>
<div class="tree-header">
<span class="col-name">文件/文件夹名称</span>
<span class="col-size">大小</span>
<span class="col-graph">相对于父目录占比</span>
</div>
<div class="tree-body">
<div v-for="(node, index) in treeData" :key="node.path" class="tree-row" :class="{ 'is-file': !node.is_dir }" :style="{ paddingLeft: (node.level * 20 + 16) + 'px' }">
<div
v-for="(node, index) in treeData"
:key="node.path"
class="tree-row"
:class="{ 'is-file': !node.is_dir }"
:style="{ paddingLeft: (node.level * 20 + 16) + 'px' }"
>
<div class="col-name" @click="toggleNode(index)">
<span v-if="node.is_dir" class="node-toggle">{{ node.isLoading ? '⌛' : (node.isOpen ? '▼' : '▶') }}</span><span v-else class="node-icon">📄</span><span class="node-text">{{ node.name }}</span>
<span v-if="node.is_dir" class="node-toggle">
{{ node.isLoading ? '' : (node.isOpen ? '' : '') }}
</span>
<span v-else class="node-icon">📄</span>
<span class="node-text">{{ node.name }}</span>
</div>
<div class="col-size">{{ node.size_str }}</div>
<div class="col-graph"><div class="mini-bar-bg"><div class="mini-bar-fill" :style="{ width: node.percent + '%' }"></div></div><span class="percent-text">{{ Math.round(node.percent) }}%</span></div>
<div class="col-graph">
<div class="mini-bar-bg">
<div class="mini-bar-fill" :style="{ width: node.percent + '%' }"></div>
</div>
<span class="percent-text">{{ Math.round(node.percent) }}%</span>
</div>
</div>
</div>
</div>
@@ -299,34 +419,206 @@ function formatItemSize(bytes: number): string {
</section>
<!-- 4. 其他占位 -->
<section v-else class="placeholder-page"><div class="empty-state"><h1>功能开发中</h1><p>敬请期待</p></div></section>
<section v-else class="placeholder-page">
<div class="empty-state">
<span class="empty-icon">🛠</span>
<h1>功能开发中</h1>
<p>此模块正在逐步完善敬请期待</p>
</div>
</section>
</main>
</div>
</template>
<style>
/* --- 全局样式复用 --- */
:root { --primary-color: #007AFF; --primary-hover: #0063CC; --bg-light: #F9FAFB; --sidebar-bg: #FFFFFF; --text-main: #1D1D1F; --text-sec: #86868B; --border-color: #E5E5E7; --card-shadow: 0 8px 24px rgba(0, 0, 0, 0.05); }
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; color: var(--text-main); background-color: var(--bg-light); height: 100vh; overflow: hidden; }
.app-container { display: flex; height: 100%; }
.sidebar { width: 260px; background: var(--sidebar-bg); border-right: 1px solid var(--border-color); display: flex; flex-direction: column; padding: 24px 0; }
.sidebar-header { padding: 0 24px 32px; } .brand { font-size: 22px; font-weight: 800; color: var(--primary-color); }
.sidebar-nav { flex: 1; }
.nav-item, .nav-item-header { padding: 12px 24px; display: flex; align-items: center; cursor: pointer; color: #424245; font-size: 15px; font-weight: 500; }
.nav-item.active { background: #F0F7FF; color: var(--primary-color); font-weight: 600; border-right: 3px solid var(--primary-color); }
.nav-sub-item { padding: 10px 24px 10px 54px; cursor: pointer; font-size: 14px; color: #6E6E73; }
.nav-sub-item.active { color: var(--primary-color); font-weight: 600; background: #F0F7FF; }
.arrow { margin-left: auto; transition: transform 0.3s; font-size: 12px; } .arrow.open { transform: rotate(180deg); }
.content { flex: 1; padding: 40px; overflow-y: auto; }
.page-container { max-width: 900px; margin: 0 auto; } .page-header { margin-bottom: 32px; } .page-header h1 { font-size: 32px; font-weight: 800; margin-bottom: 8px; }
/* --- 全局基础样式修复 --- */
:root {
--primary-color: #007AFF;
--primary-hover: #0063CC;
--bg-light: #F9FAFB;
--sidebar-bg: #FFFFFF;
--text-main: #1D1D1F;
--text-sec: #86868B;
--border-color: #E5E5E7;
--card-shadow: 0 8px 24px rgba(0, 0, 0, 0.05);
}
/* --- 按钮样式 --- */
.btn-primary { background: var(--primary-color); color: #fff; border: none; padding: 14px 32px; border-radius: 12px; font-size: 16px; font-weight: 600; cursor: pointer; }
.btn-secondary { background: #fff; border: 1px solid var(--border-color); padding: 12px 32px; border-radius: 12px; font-size: 15px; cursor: pointer; }
.btn-action { background: #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: var(--primary-color); color: #fff; }
.btn-action:disabled { opacity: 0.5; cursor: wait; }
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
color: var(--text-main);
background-color: var(--bg-light);
height: 100vh;
overflow: hidden; /* 锁定主体,由 .content 滚动 */
}
#app { height: 100%; }
.app-container {
display: flex;
height: 100%;
}
/* --- 侧边栏精确恢复 --- */
.sidebar {
width: 260px;
background-color: var(--sidebar-bg);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
padding: 24px 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-nav { flex: 1; }
.nav-item, .nav-item-header {
padding: 12px 24px;
display: flex;
align-items: center;
cursor: pointer;
transition: all 0.2s;
color: #424245;
font-size: 15px;
font-weight: 500;
}
.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; }
.arrow {
margin-left: auto;
transition: transform 0.3s;
font-size: 12px;
color: #C1C1C1;
}
.arrow.open { transform: rotate(180deg); }
.nav-sub-items { background-color: #FAFAFA; }
.nav-sub-item {
padding: 10px 24px 10px 54px;
cursor: pointer;
font-size: 14px;
color: #6E6E73;
transition: all 0.2s;
}
.nav-sub-item:hover { color: var(--primary-color); }
.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); }
/* --- 内容区滚动修复 --- */
.content {
flex: 1;
padding: 40px;
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; }
/* --- 按钮样式精确恢复 --- */
.btn-primary {
background-color: var(--primary-color);
color: white;
border: none;
padding: 14px 32px;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary:hover { background-color: var(--primary-hover); transform: translateY(-1px); }
.btn-primary:active { transform: translateY(0); }
.btn-primary:disabled { background-color: #A5C7FF; cursor: not-allowed; transform: none; }
.btn-secondary {
background-color: white;
color: var(--text-main);
border: 1px solid var(--border-color);
padding: 12px 32px;
border-radius: 12px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-secondary:hover { background-color: #F5F5F7; border-color: #D1D1D6; }
.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); }
.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);
}
.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); }
.done-card { border: 2px solid #E8F5E9; }
.result-icon.success { color: #34C759; font-size: 54px; margin-bottom: 16px; display: block; }
.clean-summary { font-size: 20px; color: var(--text-main); font-weight: 700; margin-top: 12px; }
/* --- 高级模式卡片 --- */
.adv-card-list { display: flex; flex-direction: column; gap: 20px; }
@@ -335,51 +627,42 @@ body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; color: var(--
.adv-card-main:hover { background: #FAFAFA; }
.adv-card-info { display: flex; align-items: center; gap: 20px; }
.adv-card-icon { font-size: 32px; }
.adv-card-text h3 { font-size: 18px; margin-bottom: 4px; }
.adv-card-text h3 { font-size: 18px; margin-bottom: 4px; font-weight: 700; }
.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); }
.detail-content h4 { font-size: 14px; margin-bottom: 8px; 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; }
/* --- 原有 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; }
.scan-circle.scanning { border-color: var(--primary-color); 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); }
.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); }
.result-card { background: white; border-radius: 24px; padding: 40px; width: 100%; box-shadow: var(--card-shadow); text-align: center; }
.result-stats { display: flex; justify-content: center; align-items: center; margin-bottom: 32px; }
.stat-value { font-size: 36px; font-weight: 800; color: var(--primary-color); display: block; }
.stat-divider { width: 1px; height: 50px; background: var(--border-color); margin: 0 40px; }
.simulation-toggle { display: flex; align-items: center; justify-content: center; margin-bottom: 24px; gap: 12px; }
.switch { position: relative; 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: #E5E5E7; transition: .4s; border-radius: 24px; }
.slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background: white; transition: .4s; border-radius: 50%; }
input:checked + .slider { background: var(--primary-color); }
input:checked + .slider:before { transform: translateX(20px); }
/* --- 磁盘树样式 --- */
.tree-table-container { background: #fff; border-radius: 16px; overflow: hidden; margin-top: 24px; min-height: 400px; box-shadow: var(--card-shadow); }
.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-row { display: flex; align-items: center; padding: 12px 20px; border-bottom: 1px solid #F2F2F7; font-size: 14px; }
.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; overflow: hidden; }
.col-size { width: 110px; text-align: right; font-family: monospace; font-weight: 600; }
.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); }
.percent-text { font-size: 11px; width: 35px; }
.node-toggle { width: 24px; cursor: pointer; color: #C1C1C1; }
.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; }
.detail-list { background: white; border-radius: 16px; padding: 24px; box-shadow: var(--card-shadow); margin-top: 32px; }
.detail-item { display: flex; justify-content: space-between; padding: 14px 0; border-bottom: 1px solid #F2F2F7; font-size: 14px; }
/* --- 通用状态 --- */
.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; }
.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; }
.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; }
@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); } }