fix:存储空间页加载状态优化(进度条实时计算 + 详情页加载动画)
- 列表页进度条三段改为数据就绪(deviceTotalBytes>0)后才渲染,修复加载中 CSS min-width:2% 强制显示假蓝段的问题;占用百分比同步 gate,不再显示 0.00% - 详情页加载动画与列表页一致:首屏转圈、数字位 loading 显示 spinner、加载中禁用清理按钮 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7dc9aef5be
commit
a6859a67d3
@ -6,21 +6,25 @@
|
||||
<text v-if="category.description" class="header-desc">{{ category.description }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 加载占位 -->
|
||||
<!-- 加载占位(与列表页 cache-cleanup.vue 一致的 spinner 动画) -->
|
||||
<view v-if="loading && !category.label" class="loading">
|
||||
<text>加载中...</text>
|
||||
<view class="value-spinner spinner-hero"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 简单模板(preload / progress / sandbox-tmp / others) -->
|
||||
<view v-else-if="breakdown === null" class="simple-body">
|
||||
<view class="big-stat">
|
||||
<text class="big-value">{{ formatSize(category.sizeBytes) }}</text>
|
||||
<text class="big-count">共 {{ category.keyCount }} 项缓存</text>
|
||||
<!-- 与列表页一致:loading 时数字位置显示 spinner(刷新/重新扫描时生效) -->
|
||||
<view v-if="loading" class="value-spinner spinner-hero"></view>
|
||||
<text v-else class="big-value">{{ formatSize(category.sizeBytes) }}</text>
|
||||
<view v-if="loading" class="value-spinner spinner-sm"></view>
|
||||
<text v-else class="big-count">共 {{ category.keyCount }} 项缓存</text>
|
||||
</view>
|
||||
<view class="action-wrap">
|
||||
<button
|
||||
class="clean-btn"
|
||||
:disabled="category.sizeBytes <= 0"
|
||||
:disabled="loading || category.sizeBytes <= 0"
|
||||
@click="confirmSimple"
|
||||
>
|
||||
清 理 缓 存
|
||||
@ -45,13 +49,17 @@
|
||||
<text class="group-icon">{{ g.isCurrent ? '👤' : (g.global ? '🌐' : '👥') }}</text>
|
||||
<text class="group-name">{{ g.displayUid }}</text>
|
||||
</view>
|
||||
<text class="group-size">{{ formatSize(g.sizeBytes) }} · {{ g.keyCount }} 项</text>
|
||||
<view class="group-size">
|
||||
<!-- 与列表页一致:loading 时行内数字显示 spinner -->
|
||||
<view v-if="loading" class="value-spinner spinner-sm"></view>
|
||||
<text v-else>{{ formatSize(g.sizeBytes) }} · {{ g.keyCount }} 项</text>
|
||||
</view>
|
||||
<text v-if="g.warning" class="group-warn">⚠</text>
|
||||
</view>
|
||||
<button
|
||||
class="clean-btn small"
|
||||
:class="{ 'strong': !g.isCurrent && !g.global }"
|
||||
:disabled="!g.canClean"
|
||||
:disabled="loading || !g.canClean"
|
||||
@click="confirmGroup(g)"
|
||||
>
|
||||
{{ g.canClean ? '清 理' : disabledLabel(g.disabledReason) }}
|
||||
@ -117,17 +125,17 @@ async function load() {
|
||||
// - getCategoryBreakdown 1min TTL(M2 新增),二次进入 < 50ms
|
||||
// - 首次进入:handler 4s + breakdown walk 4s = 最坏 8s
|
||||
// - 撞超时各自走降级路径(合成分组 / 保留 category)
|
||||
// 收紧超时 8s → 4s(M3):配合 HANDLER_TIMEOUT_MS 4s 一致
|
||||
// 2026-08-03 用户反馈超时,放宽 4s → 8s(liberate 下扫描变慢)
|
||||
let info = null
|
||||
let br = null
|
||||
try {
|
||||
info = await Promise.race([
|
||||
getCacheInfo(),
|
||||
new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 4000)),
|
||||
new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 8000)),
|
||||
])
|
||||
br = await Promise.race([
|
||||
getCategoryBreakdown(id.value),
|
||||
new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 4000)),
|
||||
new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 8000)),
|
||||
])
|
||||
const meta = (info.categories || []).find((c) => c.id === id.value)
|
||||
if (meta) {
|
||||
@ -337,6 +345,43 @@ function disabledLabel(reason) {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.loading-text {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* ── 加载 spinner(与列表页 cache-cleanup.vue 一致的动画) ── */
|
||||
.value-spinner {
|
||||
display: inline-block;
|
||||
border-style: solid;
|
||||
border-color: #e6e6e6;
|
||||
border-top-color: #1890ff;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
animation: value-spin 0.8s linear infinite;
|
||||
-webkit-animation: value-spin 0.8s linear infinite;
|
||||
}
|
||||
.spinner-hero {
|
||||
/* big-value 数字位:占位高度 ≈ 72rpx 字号(与列表页一致的 margin 防布局抖动) */
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-width: 6rpx;
|
||||
margin: 8rpx 0 24rpx;
|
||||
}
|
||||
.spinner-sm {
|
||||
/* big-count / group-size:内联在 24-26rpx 文字旁 */
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
border-width: 2rpx;
|
||||
}
|
||||
@keyframes value-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
@-webkit-keyframes value-spin {
|
||||
to { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 简单模板 */
|
||||
.simple-body {
|
||||
|
||||
@ -16,22 +16,28 @@
|
||||
|
||||
<!-- 嵌套进度条:Topfans 已用空间(蓝)→ 其他 app 使用空间(橙)→ 剩余可用空间(绿) -->
|
||||
<view class="progress-bar">
|
||||
<!-- 绿段:剩余可用空间(贴在右侧) -->
|
||||
<view
|
||||
class="seg-available"
|
||||
:style="{ width: availablePct + '%' }"
|
||||
></view>
|
||||
<!-- 蓝段:Topfans 已用空间(从左) -->
|
||||
<view
|
||||
class="seg-topfans"
|
||||
:style="{ width: topfansPct + '%' }"
|
||||
></view>
|
||||
<!-- 橙段:其他 app 使用空间(从蓝段"视觉右边界"开始:用视觉宽度而非真实百分比,
|
||||
避免 0.04%–2% 区间被橙段覆盖导致蓝段消失) -->
|
||||
<view
|
||||
class="seg-other"
|
||||
:style="{ left: topfansVisualPct + '%', width: otherPct + '%' }"
|
||||
></view>
|
||||
<!-- ★ 2026-08-03 三段只在真实数据就绪后渲染:
|
||||
加载中 info 为全 0,但 .seg-topfans 的 CSS min-width:2% 会把蓝段强制显示成 2%,
|
||||
造成"数据未加载出来进度条先有值"。用 hasDeviceData 统一 gate,
|
||||
加载中/加载失败时进度条只显示灰色轨道,加载完再按计算值渲染 -->
|
||||
<template v-if="hasDeviceData">
|
||||
<!-- 绿段:剩余可用空间(贴在右侧) -->
|
||||
<view
|
||||
class="seg-available"
|
||||
:style="{ width: availablePct + '%' }"
|
||||
></view>
|
||||
<!-- 蓝段:Topfans 已用空间(从左) -->
|
||||
<view
|
||||
class="seg-topfans"
|
||||
:style="{ width: topfansPct + '%' }"
|
||||
></view>
|
||||
<!-- 橙段:其他 app 使用空间(从蓝段"视觉右边界"开始:用视觉宽度而非真实百分比,
|
||||
避免 0.04%–2% 区间被橙段覆盖导致蓝段消失) -->
|
||||
<view
|
||||
class="seg-other"
|
||||
:style="{ left: topfansVisualPct + '%', width: otherPct + '%' }"
|
||||
></view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<view class="chips">
|
||||
@ -64,7 +70,9 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<text class="hero-percent"
|
||||
<!-- 与进度条同一 class 的 bug:加载中 deviceUsagePercent=0 会显示"占设备 0.00%",
|
||||
同样用 hasDeviceData gate,数据就绪前不渲染 -->
|
||||
<text v-if="hasDeviceData" class="hero-percent"
|
||||
>占设备 {{ info.deviceUsagePercent.toFixed(2) }}% 存储空间</text
|
||||
>
|
||||
</view>
|
||||
@ -176,6 +184,15 @@ const availablePct = computed(() => {
|
||||
return Math.max(0, 100 - topfansVisualPct.value - otherPct.value);
|
||||
});
|
||||
|
||||
// ★ 2026-08-03 修复"数据未加载进度条先有值":
|
||||
// loading 中 info 被重置为 INITIAL_INFO(deviceTotalBytes=0),此时 topfansPct=0,
|
||||
// 但 .seg-topfans 的 CSS min-width:2% 仍会把蓝段强制渲染成可见的 2%,
|
||||
// 进度条看起来像固定值。用 hasDeviceData 统一 gate 进度条三段 + 占用百分比,
|
||||
// 真实数据就绪前只显示灰色轨道,加载完再按实际计算值渲染。
|
||||
const hasDeviceData = computed(
|
||||
() => !loading.value && info.value.deviceTotalBytes > 0
|
||||
);
|
||||
|
||||
// ── 平台判断(仅 APP-PLUS 编译时计算)──
|
||||
// #ifdef APP-PLUS
|
||||
const isIos = (() => {
|
||||
@ -202,7 +219,8 @@ async function load(force = false) {
|
||||
const result = await Promise.race([
|
||||
getCacheInfo(force),
|
||||
new Promise((_, rej) =>
|
||||
setTimeout(() => rej(new Error("timeout")), 8000),
|
||||
// ★ 2026-08-03 用户反馈超时,放宽 8s → 20s(liberate 下 getSandboxTotalSize 递归扫描变慢)
|
||||
setTimeout(() => rej(new Error("timeout")), 20000),
|
||||
),
|
||||
]);
|
||||
info.value = result;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user