@@ -1,18 +1,22 @@
< script setup lang = "ts" >
import { ref } from "vue" ;
import { invoke } from "@tauri-apps/api/core" ;
import { listen } from "@tauri-apps/api/event" ;
import { openUrl } from "@tauri-apps/plugin-opener" ;
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 isCMenuOpen = ref ( true ) ;
const isBrowserMenuOpen = ref ( false ) ;
// --- 数据结构 ---
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 BrowserProfile { name : string ; path _name : string ; cache _size : number ; cache _size _str : string ; enabled : boolean ; }
interface BrowserScanResult { profiles : BrowserProfile [ ] ; total _size : string ; }
interface FileNode {
name : string ; path : string ; is _dir : boolean ; size : number ; size _str : string ;
percent : number ; has _children : boolean ;
@@ -20,21 +24,42 @@ interface FileNode {
}
// --- 状态管理 ---
const isScanning = ref ( false ) ;
const isCleaning = ref ( false ) ;
const isCleanDone = ref ( false ) ;
const fastState = ref ( {
isScanning : 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 s canProgress = ref ( 0 ) ;
const fastScanResult = ref < FastScanResult | null > ( null ) ;
const cleanResult = ref < CleanResult | null > ( null ) ;
const fullS canProgress = ref ( { fileCount : 0 , currentPath : "" } ) ;
const treeData = ref < FileNode [ ] > ( [ ] ) ;
// --- 动态汇总计算 ---
import { computed } from "vue" ;
const selectedStats = computed ( ( ) => {
if ( ! fastScanResult . value ) return { sizeStr : "0 B" , count : 0 , h asSelection : false } ;
const enabledItems = fastScanResult . value . items . filter ( i => i . enabled ) ;
const s = f astState . value ;
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 totalCount = enabledItems . reduce ( ( acc , i ) => acc + i . count , 0 ) ;
return {
@@ -44,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 modalTitle = ref ( "" ) ;
@@ -112,26 +151,28 @@ const advLoading = ref<Record<string, boolean>>({});
// --- 快速模式逻辑 ---
async function startFastScan ( ) {
isScanning . value = tr ue;
isCleanDone . value = fals e;
scanProgress . valu e = 0 ;
fastScanResult . value = null ;
const interval = setInterval ( ( ) => { if ( scanProgress . value < 95 ) scanProgress . value += Math . floor ( Math . random ( ) * 5 ) ; } , 100 ) ;
const s = fastState . val ue;
s . isScanning = tru e;
s. isDon e = false ;
s . progress = 0 ;
s . scanResult = null ;
const interval = setInterval ( ( ) => { if ( s . progress < 95 ) s . progress += Math . floor ( Math . random ( ) * 5 ) ; } , 100 ) ;
try {
const result = await invoke < FastScanResult > ( "start_fast_scan" ) ;
scanP rogress . value = 100 ;
fastS canResult. value = result ;
s. p rogress = 100 ;
s . s canResult = result ;
} catch ( err ) {
showAlert ( "扫描失败" , "请尝试以管理员身份运行程序。" , 'error' ) ;
} finally {
clearInterval ( interval ) ;
isScanning . value = false ;
s . isScanning = false ;
}
}
async function startFastClean ( ) {
if ( isCleaning . value || ! fastScanResult . value ) return ;
const selectedPaths = fastScanResult . value . items
const s = fastState . value ;
if ( s . isCleaning || ! s . scanResult ) return ;
const selectedPaths = s . scanResult . items
. filter ( item => item . enabled )
. map ( item => item . path ) ;
@@ -140,15 +181,15 @@ async function startFastClean() {
return ;
}
isCleaning . value = true ;
s . isCleaning = true ;
try {
const res = await invoke < CleanResult > ( "start_fast_clean" , { selectedPaths } ) ;
cleanResult . value = res ;
isCleanDone . valu e = true ;
fastS canResult. value = null ;
s . cleanResult = res ;
s . isDon e = true ;
s . s canResult = null ;
} catch ( err ) {
showAlert ( "清理失败" , String ( err ) , 'error' ) ;
} finally { isCleaning . value = false ; }
} finally { s . isCleaning = false ; }
}
// --- 高级模式逻辑 ---
@@ -170,10 +211,68 @@ 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 } ) ;
// 对 profiles 进行排序:按 cache_size 从大到小
const sortedProfiles = res . profiles
. map ( p => ( { ... p , enabled : true } ) )
. sort ( ( a , b ) => b . cache _size - a . cache _size ) ;
s . scanResult = {
... res ,
profiles : sortedProfiles
} ;
} 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) 逻辑 ---
async function startFullDiskScan ( ) {
isFullScanning . value = true ;
treeData . value = [ ] ;
fullScanProgress . value = { fileCount : 0 , currentPath : "" } ;
// 监听进度
const unlisten = await listen < { file _count : number , current _path : string } > ( "scan-progress" , ( event ) => {
fullScanProgress . value . fileCount = event . payload . file _count ;
fullScanProgress . value . currentPath = event . payload . current _path ;
} ) ;
try {
await invoke ( "start_full_disk_scan" ) ;
const rootChildren = await invoke < FileNode [ ] > ( "get_tree_children" , { path : "C:\\" } ) ;
@@ -182,6 +281,7 @@ async function startFullDiskScan() {
alert ( "扫描失败,请确保以管理员身份运行。" ) ;
} finally {
isFullScanning . value = false ;
unlisten ( ) ;
}
}
@@ -209,11 +309,14 @@ async function toggleNode(index: number) {
}
}
function resetAll ( ) {
isCleanDone . value = false ;
fastScanResult . value = null ;
cleanResult . value = null ;
treeD ata . value = [ ] ;
function resetPageState ( ) {
if ( activeTab . value === 'clean-c-fast' ) {
fastState . value = { isScanning : false , isCleaning : false , isDone : false , progress : 0 , scanResult : null , cleanResult : null } ;
} else if ( activeTab . value === 'clean-browser-chrome' ) {
chromeSt ate . 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 {
@@ -275,15 +378,32 @@ function splitSize(sizeStr: string | number) {
< / div >
< / div >
<!-- 其它项 -- >
< div
class = "nav-item"
: class = "{ active: activeTab === 'clean-browser' }"
@click ="activeTab = 'clean-browser'"
>
<!-- 清理浏览器组 -- >
< div class = "nav-group" >
< div class = "nav-item-header" @click ="isBrowserMenuOpen = !isBrowserMenuOpen" >
< span class = "icon" > 🌐 < / span >
< span class = "label" > 清理浏览器 < / span >
< span class = "arrow" : class = "{ open: isBrowserMenuOpen }" > ▾ < / span >
< / 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
class = "nav-item"
: class = "{ active: activeTab === 'clean-memory' }"
@@ -312,17 +432,17 @@ function splitSize(sizeStr: string | number) {
< div class = "main-action" >
<!-- 扫描前 / 中 -- >
< div class = "scan-circle-container" v-if = "!fastScanResult && !isClean Done" >
< 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" > { { scanP rogress } } % < / span >
< div class = "scan-circle-container" v-if = "!fastState.s canResult && !fastState.is Done" >
< div class = "scan-circle" : class = "{ scanning: fastState. isScanning }" >
< div class = "scan-inner" @click ="!fastState. isScanning && startFastScan()" >
< span v-if = "!fastState. isScanning" class="scan-btn-text" > 开始扫描 < / span >
< span v-else class = "scan-percent" > { { fastState . p rogress } } % < / span >
< / div >
< / div >
< / div >
<!-- 扫描完成 -- >
< div class = "result-card" v-else-if = "fastScanResult && !isClean Done" >
< div class = "result-card" v-else-if = "fastState.s canResult && !fastState.is Done" >
< div class = "result-header" >
< span class = "result-icon" > 📋 < / span >
< h2 > 扫描完成 < / h2 >
@@ -345,14 +465,14 @@ function splitSize(sizeStr: string | number) {
< button
class = "btn-primary main-btn"
@click ="startFastClean"
: disabled = "isCleaning || !selectedStats.hasSelection"
: disabled = "fastState. isCleaning || !selectedStats.hasSelection"
>
{ { isCleaning ? '正在清理...' : '立即清理' } }
{ { fastState . isCleaning ? '正在清理...' : '立即清理' } }
< / button >
< / 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" >
< span class = "result-icon success" > 🎉 < / span >
< h2 > 清理完成 < / h2 >
@@ -361,31 +481,31 @@ function splitSize(sizeStr: string | number) {
< div class = "result-stats" >
< div class = "stat-item" >
< span class = "stat-value" >
{ { splitSize ( cleanResult . total _freed ) . value } }
< span class = "unit" > { { splitSize ( cleanResult . total _freed ) . unit } } < / span >
{ { splitSize ( fastState . cleanResult. total _freed ) . value } }
< span class = "unit" > { { splitSize ( fastState . 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" > { { cleanResult . success _count } } < / span >
< span class = "stat-value" > { { fastState . cleanResult. success _count } } < / span >
< span class = "stat-label" > 成功清理 < / span >
< / div >
< div class = "stat-divider" > < / div >
< 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 >
< / div >
< / div >
< button class = "btn-secondary" @click ="resetAll " > 返回首页 < / button >
< button class = "btn-secondary" @click ="resetPageState " > 返回 < / button >
< / div >
< / div >
< div class = "detail-list" v-if = "(isScanning || fastScanResult) && !isClean Done" >
< div class = "detail-list" v-if = "(fastState. isScanning || fastState.s canResult) && !fastState.is Done" >
< h3 > 清理项详情 < / h3 >
< div
class = "detail-item"
v-for = "item in fastScanResult?.items || []"
v-for = "item in fastState.s canResult?.items || []"
:key = "item.path"
@click ="item.enabled = !item.enabled"
: class = "{ disabled: !item.enabled }"
@@ -399,7 +519,7 @@ function splitSize(sizeStr: string | number) {
< / div >
< span class = "item-size" > { { formatItemSize ( item . size ) } } < / span >
< / div >
< div v-if = "isScanning" class="scanning-placeholder" > 正在深度扫描文件系统... < / div >
< div v-if = "fastState. isScanning" class="scanning-placeholder" > 正在深度扫描文件系统... < / div >
< / div >
< / section >
@@ -481,7 +601,7 @@ function splitSize(sizeStr: string | number) {
< div class = "adv-card-detail" v-show = "expandedAdvanced === 'hiber'" >
< div class = "detail-content" >
< h4 > 详细信息 : < / h4 >
< p > 休眠文件 ( hiberfil . sys ) 占用大量 C 盘空间 。 对于使用 SSD 且不常用休眠功能的用户 , 关闭它可以释放巨额空间 。 < / p >
< p > 休眠文件 ( hiberfil . sys ) 占用大量 C 盘空间 。 对于使用 SSD且不常用休眠功能的用户 , 关闭它可以释放巨额空间 。 < / p >
< h4 class = "warning-title" > 注意事项 : < / h4 >
< ul >
< li > 关闭后将无法使用 “ 休眠 ” 功能及 “ 快速启动 ” 技术 。 < / li >
@@ -493,6 +613,155 @@ function splitSize(sizeStr: string | number) {
< / div >
< / 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. 深度分析页面 - - >
< section v-else-if = "activeTab === 'clean-c-deep'" class="page-container full-width" >
< div class = "page-header" >
@@ -502,7 +771,7 @@ function splitSize(sizeStr: string | number) {
< / div >
< div class = "header-actions" >
< button class = "btn-primary btn-sm" @click ="startFullDiskScan" :disabled = "isFullScanning" >
{ { isFullScanning ? '正在扫描...' : '开始深度分析 ' } }
{ { isFullScanning ? '正在扫描...' : '开始扫描 ' } }
< / button >
< / div >
< / div >
@@ -510,7 +779,15 @@ function splitSize(sizeStr: string | number) {
< 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 class = "scanning-status" >
< p class = "scanning-main-text" > 正在扫描全盘文件 ... < / p >
< div class = "scanning-stats-row" >
< span class = "stat-badge" > 已扫描 : { { fullScanProgress . fileCount . toLocaleString ( ) } } 个文件 < / span >
< / div >
< p class = "scanning-current-path" v-if = "fullScanProgress.currentPath" >
当前 : {{ fullScanProgress.currentPath }}
< / p >
< / div >
< / div >
< div v-else class = "tree-content-wrapper" >
@@ -1013,6 +1290,12 @@ body {
/* --- 通用状态 --- */
. scanning - loader , . scanning - overlay { padding : 100 px 40 px ; text - align : center ; color : var ( -- text - sec ) ; }
. scanning - status { margin - top : 16 px ; }
. scanning - main - text { font - size : 16 px ; font - weight : 600 ; color : var ( -- text - main ) ; margin - bottom : 12 px ; }
. scanning - stats - row { margin - bottom : 16 px ; }
. stat - badge { background : # EBF4FF ; color : var ( -- primary - color ) ; padding : 6 px 16 px ; border - radius : 20 px ; font - size : 13 px ; font - weight : 700 ; }
. scanning - current - path { font - size : 12 px ; color : var ( -- text - sec ) ; max - width : 500 px ; margin : 0 auto ; white - space : nowrap ; overflow : hidden ; text - overflow : ellipsis ; font - family : ui - monospace , SFMono - Regular , Menlo , Monaco , Consolas , monospace ; opacity : 0.8 ; }
. spinner {
width : 44 px ; height : 44 px ;
border : 3 px solid # F2F2F7 ;