diff --git a/src-tauri/src/cleaner.rs b/src-tauri/src/cleaner.rs index 193e944..a4f1985 100644 --- a/src-tauri/src/cleaner.rs +++ b/src-tauri/src/cleaner.rs @@ -4,11 +4,13 @@ use std::time::{SystemTime, Duration}; use serde::Serialize; use std::collections::HashMap; use std::sync::Mutex; +use std::process::Command; +use std::os::windows::process::CommandExt; -// 存储全盘扫描后的结果,以便前端按需查询 +// 存储全盘扫描后的结果 pub struct DiskState { pub dir_sizes: Mutex>, - pub file_info: Mutex>, // 路径 -> (文件总大小, 文件数量) + pub file_info: Mutex>, } #[derive(Serialize, Clone)] @@ -18,8 +20,8 @@ pub struct FileTreeNode { pub is_dir: bool, pub size: u64, pub size_str: String, - pub percent: f32, // 占父目录的百分比 - pub file_count: u32, // 仅对 "X个文件" 节点有效 + pub percent: f32, + pub file_count: u32, pub has_children: bool, } @@ -33,6 +35,69 @@ pub fn format_size(size: u64) -> String { else { format!("{} B", size) } } +// --- 高级清理功能实现 --- + +// 1. 系统组件清理 (DISM) +pub async fn run_dism_cleanup() -> Result { + const CREATE_NO_WINDOW: u32 = 0x08000000; + let output = Command::new("dism.exe") + .args(&["/online", "/Cleanup-Image", "/StartComponentCleanup"]) + .creation_flags(CREATE_NO_WINDOW) + .output() + .map_err(|e| e.to_string())?; + + if output.status.success() { + Ok("系统组件清理完成。".into()) + } else { + Err(String::from_utf8_lossy(&output.stderr).to_string()) + } +} + +// 2. 清理缩略图缓存 +pub async fn clean_thumbnails() -> Result { + let local_app_data = std::env::var("LOCALAPPDATA").map_err(|_| "无法获取 LocalAppData 路径")?; + let thumb_path = Path::new(&local_app_data).join("Microsoft\\Windows\\Explorer"); + + let mut count = 0; + if thumb_path.exists() { + if let Ok(entries) = fs::read_dir(thumb_path) { + for entry in entries.filter_map(|e| e.ok()) { + let name = entry.file_name().to_string_lossy().to_lowercase(); + if name.starts_with("thumbcache_") && name.ends_with(".db") { + // 缩略图文件通常被 Explorer 占用,这里尝试删除,失败也继续 + if fs::remove_file(entry.path()).is_ok() { + count += 1; + } + } + } + } + } + + if count > 0 { + Ok(format!("成功清理 {} 个缩略图缓存文件。", count)) + } else { + Ok("未发现可清理的缩略图缓存,或文件正被系统占用。".into()) + } +} + +// 3. 关闭休眠文件 +pub async fn disable_hibernation() -> Result { + const CREATE_NO_WINDOW: u32 = 0x08000000; + let output = Command::new("powercfg.exe") + .args(&["-h", "off"]) + .creation_flags(CREATE_NO_WINDOW) + .output() + .map_err(|e| e.to_string())?; + + if output.status.success() { + Ok("休眠模式已关闭,hiberfil.sys 已移除。".into()) + } else { + Err("执行失败,请确保以管理员身份运行。".into()) + } +} + +// --- 原有逻辑保持 (磁盘树等) --- + pub async fn run_full_scan(root_path: String, state: &DiskState) { use jwalk::WalkDir; let mut dir_sizes = HashMap::new(); @@ -42,41 +107,30 @@ pub async fn run_full_scan(root_path: String, state: &DiskState) { for entry in WalkDir::new(root).skip_hidden(false).into_iter().filter_map(|e| e.ok()) { if entry.file_type.is_file() { let size = entry.metadata().map(|m| m.len()).unwrap_or(0); - - // 统计文件信息(归属于直接父目录) if let Some(parent) = entry.parent_path().to_str() { let info = file_info.entry(parent.to_string()).or_insert((0, 0)); - info.0 += size; - info.1 += 1; + info.0 += size; info.1 += 1; } - - // 递归向上累加目录大小 let mut current_path = entry.parent_path().to_path_buf(); while current_path.starts_with(root) { let path_str = current_path.to_string_lossy().to_string(); *dir_sizes.entry(path_str).or_insert(0) += size; if current_path == root { break; } - if let Some(parent) = current_path.parent() { - current_path = parent.to_path_buf(); - } else { break; } + if let Some(parent) = current_path.parent() { current_path = parent.to_path_buf(); } else { break; } } } } - let mut state_dirs = state.dir_sizes.lock().unwrap(); let mut state_files = state.file_info.lock().unwrap(); - *state_dirs = dir_sizes; - *state_files = file_info; + *state_dirs = dir_sizes; *state_files = file_info; } pub fn get_children(parent_path: String, state: &DiskState) -> Vec { let dir_sizes = state.dir_sizes.lock().unwrap(); let file_info = state.file_info.lock().unwrap(); let mut results = Vec::new(); - let parent_size = *dir_sizes.get(&parent_path).unwrap_or(&1); - // 1. 获取子文件夹 if let Ok(entries) = fs::read_dir(Path::new(&parent_path)) { for entry in entries.filter_map(|e| e.ok()) { if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) { @@ -84,40 +138,29 @@ pub fn get_children(parent_path: String, state: &DiskState) -> Vec if let Some(&size) = dir_sizes.get(&path_str) { results.push(FileTreeNode { name: entry.file_name().to_string_lossy().to_string(), - path: path_str, - is_dir: true, - size, - size_str: format_size(size), + path: path_str, is_dir: true, size, size_str: format_size(size), percent: (size as f64 / parent_size as f64 * 100.0) as f32, - file_count: 0, - has_children: true, // 简化处理,假设都有子项 + file_count: 0, has_children: true, }); } } } } - - // 2. 获取该目录下的合并文件项 if let Some(&(size, count)) = file_info.get(&parent_path) { if count > 0 { results.push(FileTreeNode { name: format!("[{} 个文件]", count), path: format!("{}\\__files__", parent_path), - is_dir: false, - size, - size_str: format_size(size), + is_dir: false, size, size_str: format_size(size), percent: (size as f64 / parent_size as f64 * 100.0) as f32, - file_count: count, - has_children: false, + file_count: count, has_children: false, }); } } - results.sort_by(|a, b| b.size.cmp(&a.size)); results } -// 快速扫描逻辑 (保持不变) #[derive(Serialize, Clone)] pub struct ScanItem { pub name: String, pub path: String, pub size: u64, pub count: u32 } #[derive(Serialize)] @@ -127,19 +170,15 @@ pub async fn run_fast_scan() -> FastScanResult { let mut items = Vec::new(); let mut total_bytes = 0; let mut total_count = 0; - let mut add_item = |name: &str, path: &str, filter: Option| { let (size, count) = get_dir_stats(Path::new(path), filter); items.push(ScanItem { name: name.into(), path: path.into(), size, count }); - total_bytes += size; - total_count += count; + total_bytes += size; total_count += count; }; - if let Ok(t) = std::env::var("TEMP") { add_item("用户临时文件", &t, None); } add_item("系统临时文件", "C:\\Windows\\Temp", None); add_item("Windows 更新残留", "C:\\Windows\\SoftwareDistribution\\Download", Some(10)); add_item("传递优化缓存", "C:\\Windows\\ServiceProfiles\\NetworkService\\AppData\\Local\\Microsoft\\Windows\\DeliveryOptimization", None); - FastScanResult { items, total_size: format_size(total_bytes), total_count } } @@ -162,7 +201,6 @@ 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()) +pub async fn run_fast_clean(_is_simulation: bool) -> Result { + Ok("快速清理任务已成功模拟执行。".into()) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ce33f16..87a72f0 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -24,6 +24,23 @@ async fn get_tree_children(path: String, state: State<'_, cleaner::DiskState>) - Ok(cleaner::get_children(path, &state)) } +// --- 高级清理命令 --- + +#[tauri::command] +async fn clean_system_components() -> Result { + cleaner::run_dism_cleanup().await +} + +#[tauri::command] +async fn clean_thumbnails() -> Result { + cleaner::clean_thumbnails().await +} + +#[tauri::command] +async fn disable_hibernation() -> Result { + cleaner::disable_hibernation().await +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() @@ -36,7 +53,10 @@ pub fn run() { start_fast_scan, start_fast_clean, start_full_disk_scan, - get_tree_children + get_tree_children, + clean_system_components, + clean_thumbnails, + disable_hibernation ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/App.vue b/src/App.vue index 7df5185..618a0d2 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3,7 +3,7 @@ import { ref } from "vue"; import { invoke } from "@tauri-apps/api/core"; // --- 导航状态 --- -type Tab = 'clean-c-fast' | 'clean-c-advanced' | 'clean-browser' | 'clean-memory'; +type Tab = 'clean-c-fast' | 'clean-c-advanced' | 'clean-c-deep' | 'clean-browser' | 'clean-memory'; const activeTab = ref('clean-c-fast'); const isCMenuOpen = ref(true); @@ -27,6 +27,10 @@ const fastScanResult = ref(null); const treeData = ref([]); const cleanMessage = ref(""); +// 高级模式展开状态 +const expandedAdvanced = ref(null); +const advLoading = ref>({}); + // --- 快速模式逻辑 --- async function startFastScan() { isScanning.value = true; @@ -57,7 +61,25 @@ async function startFastClean() { } finally { isCleaning.value = false; } } -// --- 高级模式 (TreeSize) 逻辑 --- +// --- 高级模式逻辑 --- +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"; + + const res = await invoke(cmd); + alert(res); + } catch (err) { + alert("执行失败: " + err); + } finally { + advLoading.value[task] = false; + } +} + +// --- 深度分析 (原高级模式) 逻辑 --- async function startFullDiskScan() { isFullScanning.value = true; treeData.value = []; @@ -112,7 +134,6 @@ function formatItemSize(bytes: number): string {