support fast clean options
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
84
src/App.vue
84
src/App.vue
@@ -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); }
|
||||
|
||||
Reference in New Issue
Block a user