support fast clean options

This commit is contained in:
Julian Freeman
2026-03-03 13:35:44 -04:00
parent dbec37cd3d
commit 4cbf6379ba
3 changed files with 106 additions and 41 deletions

View File

@@ -174,11 +174,12 @@ pub struct CleaningConfig {
pub name: String,
pub path: String,
pub filter_days: Option<u64>,
pub default_enabled: bool,
}
impl CleaningConfig {
fn new(name: &str, path: &str, filter_days: Option<u64>) -> Self {
Self { name: name.into(), path: path.into(), filter_days }
fn new(name: &str, path: &str, filter_days: Option<u64>, default_enabled: bool) -> Self {
Self { name: name.into(), path: path.into(), filter_days, default_enabled }
}
}
@@ -188,33 +189,32 @@ fn get_fast_cleaning_configs() -> Vec<CleaningConfig> {
// 1. 用户临时文件
if let Ok(t) = std::env::var("TEMP") {
configs.push(CleaningConfig::new("用户临时文件", &t, None));
configs.push(CleaningConfig::new("用户临时文件", &t, None, true));
}
// 2. 系统临时文件
configs.push(CleaningConfig::new("系统临时文件", "C:\\Windows\\Temp", None));
configs.push(CleaningConfig::new("系统临时文件", "C:\\Windows\\Temp", None, true));
// 3. Windows 更新残留 (通常建议清理 10 天前的)
configs.push(CleaningConfig::new("Windows 更新残留", "C:\\Windows\\SoftwareDistribution\\Download", Some(10)));
configs.push(CleaningConfig::new("Windows 更新残留", "C:\\Windows\\SoftwareDistribution\\Download", Some(10), true));
// 4. 传递优化缓存
// configs.push(CleaningConfig::new(
// "传递优化缓存",
// "C:\\Windows\\ServiceProfiles\\NetworkService\\AppData\\Local\\Microsoft\\Windows\\DeliveryOptimization",
// None
// ));
// 以后要添加新目录,只需在此处追加一行:
// configs.push(CleaningConfig::new("新目录名称", "C:\\路径", None));
// 4. 内核转储文件
configs.push(CleaningConfig::new("内核转储文件", "C:\\Windows\\LiveKernelReports", None, false));
configs
}
#[derive(Serialize, Clone)]
pub struct ScanItem { pub name: String, pub path: String, pub size: u64, pub count: u32 }
pub struct ScanItem {
pub name: String,
pub path: String,
pub size: u64,
pub count: u32,
pub enabled: bool,
}
#[derive(Serialize)]
pub struct FastScanResult { pub items: Vec<ScanItem>, pub total_size: String, pub total_count: u32 }
pub struct FastScanResult { pub items: Vec<ScanItem>, total_size: String, total_count: u32 }
pub async fn run_fast_scan() -> FastScanResult {
let configs = get_fast_cleaning_configs();
@@ -228,7 +228,8 @@ pub async fn run_fast_scan() -> FastScanResult {
name: config.name,
path: config.path,
size,
count
count,
enabled: config.default_enabled,
});
total_bytes += size;
total_count += count;
@@ -274,27 +275,21 @@ pub struct CleanResult {
pub fail_count: u32,
}
pub async fn run_fast_clean(is_simulation: bool) -> Result<CleanResult, String> {
if is_simulation {
return Ok(CleanResult {
total_freed: "0 B".into(),
success_count: 0,
fail_count: 0,
});
}
pub async fn run_fast_clean(selected_paths: Vec<String>) -> Result<CleanResult, String> {
let configs = get_fast_cleaning_configs();
let mut success_count = 0;
let mut fail_count = 0;
let mut total_freed: u64 = 0;
for config in configs {
let path = Path::new(&config.path);
if path.exists() {
let (freed, s, f) = clean_directory_contents(path, config.filter_days);
total_freed += freed;
success_count += s;
fail_count += f;
if selected_paths.contains(&config.path) {
let path = Path::new(&config.path);
if path.exists() {
let (freed, s, f) = clean_directory_contents(path, config.filter_days);
total_freed += freed;
success_count += s;
fail_count += f;
}
}
}

View File

@@ -9,8 +9,8 @@ async fn start_fast_scan() -> cleaner::FastScanResult {
}
#[tauri::command]
async fn start_fast_clean(is_simulation: bool) -> Result<cleaner::CleanResult, String> {
cleaner::run_fast_clean(is_simulation).await
async fn start_fast_clean(selected_paths: Vec<String>) -> Result<cleaner::CleanResult, String> {
cleaner::run_fast_clean(selected_paths).await
}
#[tauri::command]

View File

@@ -10,7 +10,7 @@ const activeTab = ref<Tab>('clean-c-fast');
const isCMenuOpen = ref(true);
// --- 数据结构 ---
interface ScanItem { name: string; path: string; size: number; count: number; }
interface ScanItem { name: string; path: string; size: number; count: number; enabled: boolean; }
interface FastScanResult { items: ScanItem[]; total_size: string; total_count: number; }
interface CleanResult { total_freed: string; success_count: number; fail_count: number; }
interface FileNode {
@@ -115,10 +115,19 @@ async function startFastScan() {
}
async function startFastClean() {
if (isCleaning.value) return;
if (isCleaning.value || !fastScanResult.value) return;
const selectedPaths = fastScanResult.value.items
.filter(item => item.enabled)
.map(item => item.path);
if (selectedPaths.length === 0) {
showAlert("未选择任何项", "请至少勾选一个需要清理的项目。", 'info');
return;
}
isCleaning.value = true;
try {
const res = await invoke<CleanResult>("start_fast_clean", { isSimulation: false });
const res = await invoke<CleanResult>("start_fast_clean", { selectedPaths });
cleanResult.value = res;
isCleanDone.value = true;
fastScanResult.value = null;
@@ -353,8 +362,20 @@ function splitSize(sizeStr: string | number) {
<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>
<div
class="detail-item"
v-for="item in fastScanResult?.items || []"
:key="item.path"
@click="item.enabled = !item.enabled"
:class="{ disabled: !item.enabled }"
>
<div class="item-info">
<label class="checkbox-container" @click.stop>
<input type="checkbox" v-model="item.enabled">
<span class="checkmark"></span>
</label>
<span>{{ item.name }}</span>
</div>
<span class="item-size">{{ formatItemSize(item.size) }}</span>
</div>
<div v-if="isScanning" class="scanning-placeholder">正在深度扫描文件系统...</div>
@@ -972,14 +993,63 @@ body {
.detail-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid #F5F5F7;
font-size: 14px;
color: #424245;
transition: transform 0.2s ease;
transition: all 0.2s ease;
cursor: pointer;
}
.detail-item:hover { transform: translateX(4px); }
.detail-item:hover { background-color: #FAFAFB; padding-left: 8px; padding-right: 8px; border-radius: 8px; }
.detail-item.disabled { opacity: 0.5; }
.detail-item:last-child { border-bottom: none; }
.item-info { display: flex; align-items: center; gap: 12px; }
/* --- 自定义复选框 --- */
.checkbox-container {
display: block;
position: relative;
width: 20px;
height: 20px;
cursor: pointer;
user-select: none;
}
.checkbox-container input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0; width: 0;
}
.checkmark {
position: absolute;
top: 0; left: 0;
height: 20px; width: 20px;
background-color: #F2F2F7;
border-radius: 6px;
transition: all 0.2s;
border: 1px solid #E5E5E7;
}
.checkbox-container:hover input ~ .checkmark { background-color: #E5E5E7; }
.checkbox-container input:checked ~ .checkmark { background-color: var(--primary-color); border-color: var(--primary-color); }
.checkmark:after {
content: "";
position: absolute;
display: none;
left: 6px; top: 1px;
width: 5px; height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.checkbox-container input:checked ~ .checkmark:after { display: block; }
.item-size { font-weight: 600; color: var(--primary-color); }
.placeholder-page { padding-top: 120px; text-align: center; color: var(--text-sec); }