add clean browsers
This commit is contained in:
@@ -365,3 +365,118 @@ fn clean_directory_contents(path: &Path, filter_days: Option<u64>) -> (u64, u32,
|
|||||||
}
|
}
|
||||||
(freed, success, fail)
|
(freed, success, fail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 浏览器清理逻辑 ---
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
pub struct BrowserProfile {
|
||||||
|
pub name: String,
|
||||||
|
pub path_name: String,
|
||||||
|
pub cache_size: u64,
|
||||||
|
pub cache_size_str: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct BrowserScanResult {
|
||||||
|
pub profiles: Vec<BrowserProfile>,
|
||||||
|
pub total_size: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum BrowserType {
|
||||||
|
Chrome,
|
||||||
|
Edge,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BrowserType {
|
||||||
|
fn get_user_data_path(&self) -> Result<std::path::PathBuf, String> {
|
||||||
|
let local_app_data = std::env::var("LOCALAPPDATA").map_err(|_| "无法获取 LocalAppData 路径")?;
|
||||||
|
let base = std::path::Path::new(&local_app_data);
|
||||||
|
match self {
|
||||||
|
BrowserType::Chrome => Ok(base.join("Google\\Chrome\\User Data")),
|
||||||
|
BrowserType::Edge => Ok(base.join("Microsoft\\Edge\\User Data")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_browser_scan(browser: BrowserType) -> Result<BrowserScanResult, String> {
|
||||||
|
let user_data_path = browser.get_user_data_path()?;
|
||||||
|
let local_state_path = user_data_path.join("Local State");
|
||||||
|
|
||||||
|
let mut profiles = Vec::new();
|
||||||
|
let mut total_bytes = 0;
|
||||||
|
|
||||||
|
if local_state_path.exists() {
|
||||||
|
let content = fs::read_to_string(local_state_path).map_err(|e| e.to_string())?;
|
||||||
|
let v: serde_json::Value = serde_json::from_str(&content).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
if let Some(info_cache) = v.get("profile").and_then(|p| p.get("info_cache")).and_then(|i| i.as_object()) {
|
||||||
|
for (dir_name, info) in info_cache {
|
||||||
|
let profile_display_name = info.get("name").and_then(|n| n.as_str()).unwrap_or(dir_name);
|
||||||
|
let profile_path = user_data_path.join(dir_name);
|
||||||
|
|
||||||
|
if profile_path.exists() {
|
||||||
|
let mut size = 0;
|
||||||
|
// 扫描常见的缓存目录
|
||||||
|
let cache_dirs = ["Cache", "Code Cache", "GPUCache", "Media Cache"];
|
||||||
|
for sub in cache_dirs {
|
||||||
|
let target = profile_path.join(sub);
|
||||||
|
if target.exists() {
|
||||||
|
size += get_dir_size_simple(&target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
total_bytes += size;
|
||||||
|
profiles.push(BrowserProfile {
|
||||||
|
name: profile_display_name.to_string(),
|
||||||
|
path_name: dir_name.clone(),
|
||||||
|
cache_size: size,
|
||||||
|
cache_size_str: format_size(size),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(BrowserScanResult {
|
||||||
|
profiles,
|
||||||
|
total_size: format_size(total_bytes),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_dir_size_simple(path: &std::path::Path) -> u64 {
|
||||||
|
walkdir::WalkDir::new(path)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.filter(|e| e.file_type().is_file())
|
||||||
|
.map(|e| e.metadata().map(|m| m.len()).unwrap_or(0))
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run_browser_clean(browser: BrowserType, profile_paths: Vec<String>) -> Result<CleanResult, String> {
|
||||||
|
let user_data_path = browser.get_user_data_path()?;
|
||||||
|
let mut total_freed = 0;
|
||||||
|
let mut success_count = 0;
|
||||||
|
let mut fail_count = 0;
|
||||||
|
|
||||||
|
for profile_dir in profile_paths {
|
||||||
|
let profile_path = user_data_path.join(&profile_dir);
|
||||||
|
if profile_path.exists() {
|
||||||
|
let cache_dirs = ["Cache", "Code Cache", "GPUCache", "Media Cache"];
|
||||||
|
for sub in cache_dirs {
|
||||||
|
let target = profile_path.join(sub);
|
||||||
|
if target.exists() {
|
||||||
|
let (f, s, fl) = clean_directory_contents(&target, None);
|
||||||
|
total_freed += f;
|
||||||
|
success_count += s;
|
||||||
|
fail_count += fl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(CleanResult {
|
||||||
|
total_freed: format_size(total_freed),
|
||||||
|
success_count,
|
||||||
|
fail_count,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,6 +46,20 @@ async fn disable_hibernation() -> Result<String, String> {
|
|||||||
cleaner::disable_hibernation().await
|
cleaner::disable_hibernation().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 浏览器清理命令 ---
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn start_browser_scan(browser: String) -> Result<cleaner::BrowserScanResult, String> {
|
||||||
|
let b_type = if browser == "chrome" { cleaner::BrowserType::Chrome } else { cleaner::BrowserType::Edge };
|
||||||
|
cleaner::run_browser_scan(b_type).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn start_browser_clean(browser: String, profiles: Vec<String>) -> Result<cleaner::CleanResult, String> {
|
||||||
|
let b_type = if browser == "chrome" { cleaner::BrowserType::Chrome } else { cleaner::BrowserType::Edge };
|
||||||
|
cleaner::run_browser_clean(b_type, profiles).await
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
@@ -62,7 +76,9 @@ pub fn run() {
|
|||||||
open_in_explorer,
|
open_in_explorer,
|
||||||
clean_system_components,
|
clean_system_components,
|
||||||
clean_thumbnails,
|
clean_thumbnails,
|
||||||
disable_hibernation
|
disable_hibernation,
|
||||||
|
start_browser_scan,
|
||||||
|
start_browser_clean
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
361
src/App.vue
361
src/App.vue
@@ -1,19 +1,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onUnmounted } from "vue";
|
import { ref } from "vue";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||||
import pkg from "../package.json";
|
import pkg from "../package.json";
|
||||||
|
|
||||||
// --- 导航状态 ---
|
// --- 导航状态 ---
|
||||||
type Tab = 'clean-c-fast' | 'clean-c-advanced' | 'clean-c-deep' | 'clean-browser' | 'clean-memory';
|
type Tab = 'clean-c-fast' | 'clean-c-advanced' | 'clean-c-deep' | 'clean-browser-chrome' | 'clean-browser-edge' | 'clean-memory';
|
||||||
const activeTab = ref<Tab>('clean-c-fast');
|
const activeTab = ref<Tab>('clean-c-fast');
|
||||||
const isCMenuOpen = ref(true);
|
const isCMenuOpen = ref(true);
|
||||||
|
const isBrowserMenuOpen = ref(false);
|
||||||
|
|
||||||
// --- 数据结构 ---
|
// --- 数据结构 ---
|
||||||
interface ScanItem { name: string; path: string; size: number; count: number; enabled: boolean; }
|
interface ScanItem { name: string; path: string; size: number; count: number; enabled: boolean; }
|
||||||
interface FastScanResult { items: ScanItem[]; total_size: string; total_count: number; }
|
interface FastScanResult { items: ScanItem[]; total_size: string; total_count: number; }
|
||||||
interface CleanResult { total_freed: string; success_count: number; fail_count: number; }
|
interface CleanResult { total_freed: string; success_count: number; fail_count: number; }
|
||||||
|
interface BrowserProfile { name: string; path_name: string; cache_size: number; cache_size_str: string; enabled: boolean; }
|
||||||
|
interface BrowserScanResult { profiles: BrowserProfile[]; total_size: string; }
|
||||||
interface FileNode {
|
interface FileNode {
|
||||||
name: string; path: string; is_dir: boolean; size: number; size_str: string;
|
name: string; path: string; is_dir: boolean; size: number; size_str: string;
|
||||||
percent: number; has_children: boolean;
|
percent: number; has_children: boolean;
|
||||||
@@ -21,22 +24,42 @@ interface FileNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- 状态管理 ---
|
// --- 状态管理 ---
|
||||||
const isScanning = ref(false);
|
const fastState = ref({
|
||||||
const isCleaning = ref(false);
|
isScanning: false,
|
||||||
const isCleanDone = ref(false);
|
isCleaning: false,
|
||||||
|
isDone: false,
|
||||||
|
progress: 0,
|
||||||
|
scanResult: null as FastScanResult | null,
|
||||||
|
cleanResult: null as CleanResult | null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const chromeState = ref({
|
||||||
|
isScanning: false,
|
||||||
|
isCleaning: false,
|
||||||
|
isDone: false,
|
||||||
|
scanResult: null as BrowserScanResult | null,
|
||||||
|
cleanResult: null as CleanResult | null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const edgeState = ref({
|
||||||
|
isScanning: false,
|
||||||
|
isCleaning: false,
|
||||||
|
isDone: false,
|
||||||
|
scanResult: null as BrowserScanResult | null,
|
||||||
|
cleanResult: null as CleanResult | null,
|
||||||
|
});
|
||||||
|
|
||||||
const isFullScanning = ref(false);
|
const isFullScanning = ref(false);
|
||||||
const scanProgress = ref(0);
|
|
||||||
const fullScanProgress = ref({ fileCount: 0, currentPath: "" });
|
const fullScanProgress = ref({ fileCount: 0, currentPath: "" });
|
||||||
const fastScanResult = ref<FastScanResult | null>(null);
|
|
||||||
const cleanResult = ref<CleanResult | null>(null);
|
|
||||||
const treeData = ref<FileNode[]>([]);
|
const treeData = ref<FileNode[]>([]);
|
||||||
|
|
||||||
// --- 动态汇总计算 ---
|
// --- 动态汇总计算 ---
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
const selectedStats = computed(() => {
|
const selectedStats = computed(() => {
|
||||||
if (!fastScanResult.value) return { sizeStr: "0 B", count: 0, hasSelection: false };
|
const s = fastState.value;
|
||||||
const enabledItems = fastScanResult.value.items.filter(i => i.enabled);
|
if (!s.scanResult) return { sizeStr: "0 B", count: 0, hasSelection: false };
|
||||||
|
const enabledItems = s.scanResult.items.filter(i => i.enabled);
|
||||||
const totalBytes = enabledItems.reduce((acc, i) => acc + i.size, 0);
|
const totalBytes = enabledItems.reduce((acc, i) => acc + i.size, 0);
|
||||||
const totalCount = enabledItems.reduce((acc, i) => acc + i.count, 0);
|
const totalCount = enabledItems.reduce((acc, i) => acc + i.count, 0);
|
||||||
return {
|
return {
|
||||||
@@ -46,6 +69,20 @@ const selectedStats = computed(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getBrowserSelectedStats(state: any) {
|
||||||
|
if (!state.scanResult) return { sizeStr: "0 B", count: 0, hasSelection: false };
|
||||||
|
const enabledProfiles = state.scanResult.profiles.filter((p: any) => p.enabled);
|
||||||
|
const totalBytes = enabledProfiles.reduce((acc: number, p: any) => acc + p.cache_size, 0);
|
||||||
|
return {
|
||||||
|
sizeStr: formatItemSize(totalBytes),
|
||||||
|
count: enabledProfiles.length,
|
||||||
|
hasSelection: enabledProfiles.length > 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const chromeSelectedStats = computed(() => getBrowserSelectedStats(chromeState.value));
|
||||||
|
const edgeSelectedStats = computed(() => getBrowserSelectedStats(edgeState.value));
|
||||||
|
|
||||||
// --- 弹窗状态 ---
|
// --- 弹窗状态 ---
|
||||||
const showModal = ref(false);
|
const showModal = ref(false);
|
||||||
const modalTitle = ref("");
|
const modalTitle = ref("");
|
||||||
@@ -114,26 +151,28 @@ const advLoading = ref<Record<string, boolean>>({});
|
|||||||
|
|
||||||
// --- 快速模式逻辑 ---
|
// --- 快速模式逻辑 ---
|
||||||
async function startFastScan() {
|
async function startFastScan() {
|
||||||
isScanning.value = true;
|
const s = fastState.value;
|
||||||
isCleanDone.value = false;
|
s.isScanning = true;
|
||||||
scanProgress.value = 0;
|
s.isDone = false;
|
||||||
fastScanResult.value = null;
|
s.progress = 0;
|
||||||
const interval = setInterval(() => { if (scanProgress.value < 95) scanProgress.value += Math.floor(Math.random() * 5); }, 100);
|
s.scanResult = null;
|
||||||
|
const interval = setInterval(() => { if (s.progress < 95) s.progress += Math.floor(Math.random() * 5); }, 100);
|
||||||
try {
|
try {
|
||||||
const result = await invoke<FastScanResult>("start_fast_scan");
|
const result = await invoke<FastScanResult>("start_fast_scan");
|
||||||
scanProgress.value = 100;
|
s.progress = 100;
|
||||||
fastScanResult.value = result;
|
s.scanResult = result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showAlert("扫描失败", "请尝试以管理员身份运行程序。", 'error');
|
showAlert("扫描失败", "请尝试以管理员身份运行程序。", 'error');
|
||||||
} finally {
|
} finally {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
isScanning.value = false;
|
s.isScanning = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startFastClean() {
|
async function startFastClean() {
|
||||||
if (isCleaning.value || !fastScanResult.value) return;
|
const s = fastState.value;
|
||||||
const selectedPaths = fastScanResult.value.items
|
if (s.isCleaning || !s.scanResult) return;
|
||||||
|
const selectedPaths = s.scanResult.items
|
||||||
.filter(item => item.enabled)
|
.filter(item => item.enabled)
|
||||||
.map(item => item.path);
|
.map(item => item.path);
|
||||||
|
|
||||||
@@ -142,15 +181,15 @@ async function startFastClean() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isCleaning.value = true;
|
s.isCleaning = true;
|
||||||
try {
|
try {
|
||||||
const res = await invoke<CleanResult>("start_fast_clean", { selectedPaths });
|
const res = await invoke<CleanResult>("start_fast_clean", { selectedPaths });
|
||||||
cleanResult.value = res;
|
s.cleanResult = res;
|
||||||
isCleanDone.value = true;
|
s.isDone = true;
|
||||||
fastScanResult.value = null;
|
s.scanResult = null;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showAlert("清理失败", String(err), 'error');
|
showAlert("清理失败", String(err), 'error');
|
||||||
} finally { isCleaning.value = false; }
|
} finally { s.isCleaning = false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 高级模式逻辑 ---
|
// --- 高级模式逻辑 ---
|
||||||
@@ -172,6 +211,51 @@ async function runAdvancedTask(task: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 浏览器清理逻辑 ---
|
||||||
|
async function startBrowserScan(browser: 'chrome' | 'edge') {
|
||||||
|
const s = browser === 'chrome' ? chromeState.value : edgeState.value;
|
||||||
|
s.isScanning = true;
|
||||||
|
s.isDone = false;
|
||||||
|
s.scanResult = null;
|
||||||
|
s.cleanResult = null;
|
||||||
|
try {
|
||||||
|
const res = await invoke<BrowserScanResult>("start_browser_scan", { browser });
|
||||||
|
s.scanResult = {
|
||||||
|
...res,
|
||||||
|
profiles: res.profiles.map(p => ({ ...p, enabled: true }))
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
showAlert("扫描失败", String(err), 'error');
|
||||||
|
} finally {
|
||||||
|
s.isScanning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startBrowserClean(browser: 'chrome' | 'edge') {
|
||||||
|
const s = browser === 'chrome' ? chromeState.value : edgeState.value;
|
||||||
|
if (!s.scanResult || s.isCleaning) return;
|
||||||
|
const selectedProfiles = s.scanResult.profiles
|
||||||
|
.filter(p => p.enabled)
|
||||||
|
.map(p => p.path_name);
|
||||||
|
|
||||||
|
if (selectedProfiles.length === 0) {
|
||||||
|
showAlert("未选择", "请选择至少一个用户资料进行清理。", 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s.isCleaning = true;
|
||||||
|
try {
|
||||||
|
const res = await invoke<CleanResult>("start_browser_clean", { browser, profiles: selectedProfiles });
|
||||||
|
s.cleanResult = res;
|
||||||
|
s.isDone = true;
|
||||||
|
s.scanResult = null;
|
||||||
|
} catch (err) {
|
||||||
|
showAlert("清理失败", String(err), 'error');
|
||||||
|
} finally {
|
||||||
|
s.isCleaning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- 深度分析 (TreeSize) 逻辑 ---
|
// --- 深度分析 (TreeSize) 逻辑 ---
|
||||||
async function startFullDiskScan() {
|
async function startFullDiskScan() {
|
||||||
isFullScanning.value = true;
|
isFullScanning.value = true;
|
||||||
@@ -220,11 +304,14 @@ async function toggleNode(index: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetAll() {
|
function resetPageState() {
|
||||||
isCleanDone.value = false;
|
if (activeTab.value === 'clean-c-fast') {
|
||||||
fastScanResult.value = null;
|
fastState.value = { isScanning: false, isCleaning: false, isDone: false, progress: 0, scanResult: null, cleanResult: null };
|
||||||
cleanResult.value = null;
|
} else if (activeTab.value === 'clean-browser-chrome') {
|
||||||
treeData.value = [];
|
chromeState.value = { isScanning: false, isCleaning: false, isDone: false, scanResult: null, cleanResult: null };
|
||||||
|
} else if (activeTab.value === 'clean-browser-edge') {
|
||||||
|
edgeState.value = { isScanning: false, isCleaning: false, isDone: false, scanResult: null, cleanResult: null };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatItemSize(bytes: number): string {
|
function formatItemSize(bytes: number): string {
|
||||||
@@ -286,15 +373,32 @@ function splitSize(sizeStr: string | number) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 其它项 -->
|
<!-- 清理浏览器组 -->
|
||||||
<div
|
<div class="nav-group">
|
||||||
class="nav-item"
|
<div class="nav-item-header" @click="isBrowserMenuOpen = !isBrowserMenuOpen">
|
||||||
:class="{ active: activeTab === 'clean-browser' }"
|
|
||||||
@click="activeTab = 'clean-browser'"
|
|
||||||
>
|
|
||||||
<span class="icon">🌐</span>
|
<span class="icon">🌐</span>
|
||||||
<span class="label">清理浏览器</span>
|
<span class="label">清理浏览器</span>
|
||||||
|
<span class="arrow" :class="{ open: isBrowserMenuOpen }">▾</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="nav-sub-items" v-show="isBrowserMenuOpen">
|
||||||
|
<div
|
||||||
|
class="nav-sub-item"
|
||||||
|
:class="{ active: activeTab === 'clean-browser-chrome' }"
|
||||||
|
@click="activeTab = 'clean-browser-chrome'"
|
||||||
|
>
|
||||||
|
谷歌浏览器
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="nav-sub-item"
|
||||||
|
:class="{ active: activeTab === 'clean-browser-edge' }"
|
||||||
|
@click="activeTab = 'clean-browser-edge'"
|
||||||
|
>
|
||||||
|
微软浏览器
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 其它项 -->
|
||||||
<div
|
<div
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
:class="{ active: activeTab === 'clean-memory' }"
|
:class="{ active: activeTab === 'clean-memory' }"
|
||||||
@@ -323,17 +427,17 @@ function splitSize(sizeStr: string | number) {
|
|||||||
|
|
||||||
<div class="main-action">
|
<div class="main-action">
|
||||||
<!-- 扫描前/中 -->
|
<!-- 扫描前/中 -->
|
||||||
<div class="scan-circle-container" v-if="!fastScanResult && !isCleanDone">
|
<div class="scan-circle-container" v-if="!fastState.scanResult && !fastState.isDone">
|
||||||
<div class="scan-circle" :class="{ scanning: isScanning }">
|
<div class="scan-circle" :class="{ scanning: fastState.isScanning }">
|
||||||
<div class="scan-inner" @click="!isScanning && startFastScan()">
|
<div class="scan-inner" @click="!fastState.isScanning && startFastScan()">
|
||||||
<span v-if="!isScanning" class="scan-btn-text">开始扫描</span>
|
<span v-if="!fastState.isScanning" class="scan-btn-text">开始扫描</span>
|
||||||
<span v-else class="scan-percent">{{ scanProgress }}%</span>
|
<span v-else class="scan-percent">{{ fastState.progress }}%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 扫描完成 -->
|
<!-- 扫描完成 -->
|
||||||
<div class="result-card" v-else-if="fastScanResult && !isCleanDone">
|
<div class="result-card" v-else-if="fastState.scanResult && !fastState.isDone">
|
||||||
<div class="result-header">
|
<div class="result-header">
|
||||||
<span class="result-icon">📋</span>
|
<span class="result-icon">📋</span>
|
||||||
<h2>扫描完成</h2>
|
<h2>扫描完成</h2>
|
||||||
@@ -356,14 +460,14 @@ function splitSize(sizeStr: string | number) {
|
|||||||
<button
|
<button
|
||||||
class="btn-primary main-btn"
|
class="btn-primary main-btn"
|
||||||
@click="startFastClean"
|
@click="startFastClean"
|
||||||
:disabled="isCleaning || !selectedStats.hasSelection"
|
:disabled="fastState.isCleaning || !selectedStats.hasSelection"
|
||||||
>
|
>
|
||||||
{{ isCleaning ? '正在清理...' : '立即清理' }}
|
{{ fastState.isCleaning ? '正在清理...' : '立即清理' }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 清理完成报告 -->
|
<!-- 清理完成报告 -->
|
||||||
<div class="result-card done-card" v-else-if="isCleanDone && cleanResult">
|
<div class="result-card done-card" v-else-if="fastState.isDone && fastState.cleanResult">
|
||||||
<div class="result-header">
|
<div class="result-header">
|
||||||
<span class="result-icon success">🎉</span>
|
<span class="result-icon success">🎉</span>
|
||||||
<h2>清理完成</h2>
|
<h2>清理完成</h2>
|
||||||
@@ -372,31 +476,31 @@ function splitSize(sizeStr: string | number) {
|
|||||||
<div class="result-stats">
|
<div class="result-stats">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-value">
|
<span class="stat-value">
|
||||||
{{ splitSize(cleanResult.total_freed).value }}
|
{{ splitSize(fastState.cleanResult.total_freed).value }}
|
||||||
<span class="unit">{{ splitSize(cleanResult.total_freed).unit }}</span>
|
<span class="unit">{{ splitSize(fastState.cleanResult.total_freed).unit }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="stat-label">释放空间</span>
|
<span class="stat-label">释放空间</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-divider"></div>
|
<div class="stat-divider"></div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-value">{{ cleanResult.success_count }}</span>
|
<span class="stat-value">{{ fastState.cleanResult.success_count }}</span>
|
||||||
<span class="stat-label">成功清理</span>
|
<span class="stat-label">成功清理</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-divider"></div>
|
<div class="stat-divider"></div>
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-value highlight-gray">{{ cleanResult.fail_count }}</span>
|
<span class="stat-value highlight-gray">{{ fastState.cleanResult.fail_count }}</span>
|
||||||
<span class="stat-label">跳过/失败</span>
|
<span class="stat-label">跳过/失败</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-secondary" @click="resetAll">返回首页</button>
|
<button class="btn-secondary" @click="resetPageState">返回</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="detail-list" v-if="(isScanning || fastScanResult) && !isCleanDone">
|
<div class="detail-list" v-if="(fastState.isScanning || fastState.scanResult) && !fastState.isDone">
|
||||||
<h3>清理项详情</h3>
|
<h3>清理项详情</h3>
|
||||||
<div
|
<div
|
||||||
class="detail-item"
|
class="detail-item"
|
||||||
v-for="item in fastScanResult?.items || []"
|
v-for="item in fastState.scanResult?.items || []"
|
||||||
:key="item.path"
|
:key="item.path"
|
||||||
@click="item.enabled = !item.enabled"
|
@click="item.enabled = !item.enabled"
|
||||||
:class="{ disabled: !item.enabled }"
|
:class="{ disabled: !item.enabled }"
|
||||||
@@ -410,7 +514,7 @@ function splitSize(sizeStr: string | number) {
|
|||||||
</div>
|
</div>
|
||||||
<span class="item-size">{{ formatItemSize(item.size) }}</span>
|
<span class="item-size">{{ formatItemSize(item.size) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isScanning" class="scanning-placeholder">正在深度扫描文件系统...</div>
|
<div v-if="fastState.isScanning" class="scanning-placeholder">正在深度扫描文件系统...</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -492,7 +596,7 @@ function splitSize(sizeStr: string | number) {
|
|||||||
<div class="adv-card-detail" v-show="expandedAdvanced === 'hiber'">
|
<div class="adv-card-detail" v-show="expandedAdvanced === 'hiber'">
|
||||||
<div class="detail-content">
|
<div class="detail-content">
|
||||||
<h4>详细信息:</h4>
|
<h4>详细信息:</h4>
|
||||||
<p>休眠文件(hiberfil.sys)占用大量 C 盘空间。对于使用 SSD 且不常用休眠功能的用户,关闭它可以释放巨额空间。</p>
|
<p>休眠文件(hiberfil.sys)占用大量 C 盘空间。对于使用 SSD且不常用休眠功能的用户,关闭它可以释放巨额空间。</p>
|
||||||
<h4 class="warning-title">注意事项:</h4>
|
<h4 class="warning-title">注意事项:</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>关闭后将无法使用“休眠”功能及“快速启动”技术。</li>
|
<li>关闭后将无法使用“休眠”功能及“快速启动”技术。</li>
|
||||||
@@ -504,6 +608,155 @@ function splitSize(sizeStr: string | number) {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- 2.5 浏览器清理页面 -->
|
||||||
|
<section v-else-if="activeTab === 'clean-browser-chrome' || activeTab === 'clean-browser-edge'" class="page-container">
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="header-info">
|
||||||
|
<h1>清理{{ activeTab === 'clean-browser-chrome' ? '谷歌浏览器' : '微软浏览器' }}</h1>
|
||||||
|
<p>安全清理浏览器缓存、临时文件,释放磁盘空间。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-action">
|
||||||
|
<!-- 谷歌浏览器视图 -->
|
||||||
|
<template v-if="activeTab === 'clean-browser-chrome'">
|
||||||
|
<!-- 扫描前 -->
|
||||||
|
<div class="scan-circle-container" v-if="!chromeState.scanResult && !chromeState.isDone">
|
||||||
|
<div class="scan-circle" :class="{ scanning: chromeState.isScanning }">
|
||||||
|
<div class="scan-inner" @click="!chromeState.isScanning && startBrowserScan('chrome')">
|
||||||
|
<span v-if="!chromeState.isScanning" class="scan-btn-text">开始扫描</span>
|
||||||
|
<span v-else class="spinner"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 扫描完成 -->
|
||||||
|
<div class="result-card" v-else-if="chromeState.scanResult && !chromeState.isDone">
|
||||||
|
<div class="result-header">
|
||||||
|
<span class="result-icon">🌐</span>
|
||||||
|
<h2>扫描完成</h2>
|
||||||
|
</div>
|
||||||
|
<div class="result-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-value">
|
||||||
|
{{ splitSize(chromeSelectedStats.sizeStr).value }}
|
||||||
|
<span class="unit">{{ splitSize(chromeSelectedStats.sizeStr).unit }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="stat-label">预计释放</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-divider"></div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-value">{{ chromeSelectedStats.count }}</span>
|
||||||
|
<span class="stat-label">用户资料数量</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn-primary main-btn" @click="startBrowserClean('chrome')" :disabled="chromeState.isCleaning || !chromeSelectedStats.hasSelection">
|
||||||
|
{{ chromeState.isCleaning ? '正在清理...' : '立即清理' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- 清理完成 -->
|
||||||
|
<div class="result-card done-card" v-else-if="chromeState.isDone && chromeState.cleanResult">
|
||||||
|
<div class="result-header">
|
||||||
|
<span class="result-icon success">🎉</span>
|
||||||
|
<h2>清理完成</h2>
|
||||||
|
</div>
|
||||||
|
<div class="result-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-value">
|
||||||
|
{{ splitSize(chromeState.cleanResult.total_freed).value }}
|
||||||
|
<span class="unit">{{ splitSize(chromeState.cleanResult.total_freed).unit }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="stat-label">释放空间</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-divider"></div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-value">{{ chromeState.cleanResult.success_count }}</span>
|
||||||
|
<span class="stat-label">成功清理</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn-secondary" @click="resetPageState">返回</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 微软浏览器视图 -->
|
||||||
|
<template v-else-if="activeTab === 'clean-browser-edge'">
|
||||||
|
<div class="scan-circle-container" v-if="!edgeState.scanResult && !edgeState.isDone">
|
||||||
|
<div class="scan-circle" :class="{ scanning: edgeState.isScanning }">
|
||||||
|
<div class="scan-inner" @click="!edgeState.isScanning && startBrowserScan('edge')">
|
||||||
|
<span v-if="!edgeState.isScanning" class="scan-btn-text">开始扫描</span>
|
||||||
|
<span v-else class="spinner"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="result-card" v-else-if="edgeState.scanResult && !edgeState.isDone">
|
||||||
|
<div class="result-header">
|
||||||
|
<span class="result-icon">🌐</span>
|
||||||
|
<h2>扫描完成</h2>
|
||||||
|
</div>
|
||||||
|
<div class="result-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-value">
|
||||||
|
{{ splitSize(edgeSelectedStats.sizeStr).value }}
|
||||||
|
<span class="unit">{{ splitSize(edgeSelectedStats.sizeStr).unit }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="stat-label">预计释放</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-divider"></div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-value">{{ edgeSelectedStats.count }}</span>
|
||||||
|
<span class="stat-label">用户资料数量</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn-primary main-btn" @click="startBrowserClean('edge')" :disabled="edgeState.isCleaning || !edgeSelectedStats.hasSelection">
|
||||||
|
{{ edgeState.isCleaning ? '正在清理...' : '立即清理' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="result-card done-card" v-else-if="edgeState.isDone && edgeState.cleanResult">
|
||||||
|
<div class="result-header">
|
||||||
|
<span class="result-icon success">🎉</span>
|
||||||
|
<h2>清理完成</h2>
|
||||||
|
</div>
|
||||||
|
<div class="result-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-value">
|
||||||
|
{{ splitSize(edgeState.cleanResult.total_freed).value }}
|
||||||
|
<span class="unit">{{ splitSize(edgeState.cleanResult.total_freed).unit }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="stat-label">释放空间</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-divider"></div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-value">{{ edgeState.cleanResult.success_count }}</span>
|
||||||
|
<span class="stat-label">成功清理</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn-secondary" @click="resetPageState">返回</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 详情列表 -->
|
||||||
|
<div class="detail-list" v-if="activeTab === 'clean-browser-chrome' ? (chromeState.isScanning || chromeState.scanResult) && !chromeState.isDone : (edgeState.isScanning || edgeState.scanResult) && !edgeState.isDone">
|
||||||
|
<h3>用户资料列表</h3>
|
||||||
|
<div
|
||||||
|
class="detail-item"
|
||||||
|
v-for="profile in (activeTab === 'clean-browser-chrome' ? chromeState.scanResult?.profiles : edgeState.scanResult?.profiles) || []"
|
||||||
|
:key="profile.path_name"
|
||||||
|
@click="profile.enabled = !profile.enabled"
|
||||||
|
:class="{ disabled: !profile.enabled }"
|
||||||
|
>
|
||||||
|
<div class="item-info">
|
||||||
|
<label class="checkbox-container" @click.stop>
|
||||||
|
<input type="checkbox" v-model="profile.enabled">
|
||||||
|
<span class="checkmark"></span>
|
||||||
|
</label>
|
||||||
|
<span>{{ profile.name }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="item-size">{{ profile.cache_size_str }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="activeTab === 'clean-browser-chrome' ? chromeState.isScanning : edgeState.isScanning" class="scanning-placeholder">正在定位并分析浏览器用户资料...</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- 3. 深度分析页面 -->
|
<!-- 3. 深度分析页面 -->
|
||||||
<section v-else-if="activeTab === 'clean-c-deep'" class="page-container full-width">
|
<section v-else-if="activeTab === 'clean-c-deep'" class="page-container full-width">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
|
|||||||
Reference in New Issue
Block a user