feat:修改样式
This commit is contained in:
parent
b0357d39a4
commit
81ee22d2ee
@ -1,27 +1,58 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="cache-cleanup">
|
<view class="cache-cleanup">
|
||||||
<!-- 顶部 WeChat 风格 -->
|
<!-- Hero 卡片:核心占用展示 -->
|
||||||
<view class="hero">
|
<view class="hero-card">
|
||||||
|
<!-- iOS 受沙盒限制,无法读 .app bundle 大小;提示用户到系统设置查看真实值 -->
|
||||||
|
<!-- #ifdef APP-PLUS -->
|
||||||
|
<view v-if="isIos" class="hero-tip"
|
||||||
|
>准确值请查看 iPhone「设置-通用-iPhone 储存空间」</view
|
||||||
|
>
|
||||||
|
<!-- #endif -->
|
||||||
|
|
||||||
<text class="hero-label">Topfans 已用空间</text>
|
<text class="hero-label">Topfans 已用空间</text>
|
||||||
<text class="hero-value">{{ formatSize(info.appUsedBytes) }}</text>
|
<text class="hero-value">{{ formatSize(info.appUsedBytes) }}</text>
|
||||||
<text class="hero-percent"
|
|
||||||
>占据配额 {{ info.usagePercent.toFixed(1) }}% 存储空间</text
|
<!-- 嵌套进度条:Topfans 已用空间(蓝)→ 内存缓存空间(紫,叠在蓝内)→ 总内存空间剩余(绿) -->
|
||||||
>
|
|
||||||
<view class="progress-bar">
|
<view class="progress-bar">
|
||||||
|
<!-- 绿段:剩余配额(贴在右侧) -->
|
||||||
<view
|
<view
|
||||||
class="progress-fill"
|
class="seg-available"
|
||||||
:style="{ width: Math.min(100, info.usagePercent) + '%' }"
|
:style="{ width: availablePct + '%' }"
|
||||||
|
></view>
|
||||||
|
<!-- 蓝段:完整 Topfans 已用(从左) -->
|
||||||
|
<view
|
||||||
|
class="seg-app"
|
||||||
|
:style="{ width: appPct + '%' }"
|
||||||
|
></view>
|
||||||
|
<!-- 紫段:缓存(从左、叠在蓝段内;不超过蓝段右边界) -->
|
||||||
|
<view
|
||||||
|
class="seg-cache"
|
||||||
|
:style="{ width: cachePct + '%' }"
|
||||||
></view>
|
></view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="chips">
|
<view class="chips">
|
||||||
<view class="chip">Topfans 已用空间</view>
|
<view class="chip">
|
||||||
<view class="chip">内存缓存空间</view>
|
<view class="chip-dot dot-app"></view>
|
||||||
<view class="chip">总内存空间</view>
|
<text>Topfans 已用空间</text>
|
||||||
|
</view>
|
||||||
|
<view class="chip">
|
||||||
|
<view class="chip-dot dot-cache"></view>
|
||||||
|
<text>内存缓存空间</text>
|
||||||
|
</view>
|
||||||
|
<view class="chip">
|
||||||
|
<view class="chip-dot dot-available"></view>
|
||||||
|
<text>总内存空间</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<text class="hero-percent"
|
||||||
|
>占手机 {{ info.usagePercent.toFixed(1) }}% 存储空间</text
|
||||||
|
>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 缓存分类 -->
|
<!-- 缓存分类卡片 -->
|
||||||
<view class="section">
|
<view class="section-card">
|
||||||
<text class="section-title">缓存分类</text>
|
<text class="section-title">缓存分类</text>
|
||||||
<!-- 全局空状态:失败时不显示(避免冲突显示) -->
|
<!-- 全局空状态:失败时不显示(避免冲突显示) -->
|
||||||
<view
|
<view
|
||||||
@ -32,12 +63,14 @@
|
|||||||
"
|
"
|
||||||
class="empty"
|
class="empty"
|
||||||
>
|
>
|
||||||
<text>✓ 当前无缓存可清理</text>
|
<view class="empty-icon">✓</view>
|
||||||
|
<text class="empty-text">当前无缓存可清理</text>
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
v-for="cat in info.categories"
|
v-for="(cat, index) in info.categories"
|
||||||
:key="cat.id"
|
:key="cat.id"
|
||||||
class="row"
|
class="row"
|
||||||
|
:class="{ 'row-last': index === info.categories.length - 1 }"
|
||||||
@click="goDetail(cat.id)"
|
@click="goDetail(cat.id)"
|
||||||
>
|
>
|
||||||
<view class="row-main">
|
<view class="row-main">
|
||||||
@ -54,10 +87,10 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- "其他" section -->
|
<!-- "其他" 卡片 -->
|
||||||
<view class="section others-section">
|
<view class="section-card others-section">
|
||||||
<text class="section-title">其他</text>
|
<text class="section-title">其他</text>
|
||||||
<view class="row others-row">
|
<view class="row others-row row-last">
|
||||||
<view class="row-main">
|
<view class="row-main">
|
||||||
<text class="row-label">其他</text>
|
<text class="row-label">其他</text>
|
||||||
<text class="row-desc"
|
<text class="row-desc"
|
||||||
@ -80,7 +113,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import { onPullDownRefresh, onShow } from "@dcloudio/uni-app";
|
import { onPullDownRefresh, onShow } from "@dcloudio/uni-app";
|
||||||
import { getCacheInfo, formatSize } from "@/utils/cacheManager";
|
import { getCacheInfo, formatSize } from "@/utils/cacheManager";
|
||||||
|
|
||||||
@ -95,6 +128,38 @@ const info = ref({
|
|||||||
});
|
});
|
||||||
const loadFailed = ref(false);
|
const loadFailed = ref(false);
|
||||||
|
|
||||||
|
// ── 进度条三段宽度(基于 quotaTotalBytes = 100%)──
|
||||||
|
// 三段相加 ≤ 100%,不出现负数;任一字段缺失时安全降级为 0
|
||||||
|
// 语义:蓝 = Topfans 全部已用;紫 = 蓝内的缓存(必须 ≤ 蓝);绿 = 蓝右侧剩余
|
||||||
|
const appPct = computed(() => {
|
||||||
|
const total = info.value.quotaTotalBytes;
|
||||||
|
if (!total) return 0;
|
||||||
|
return Math.min(100, Math.max(0, (info.value.appUsedBytes / total) * 100));
|
||||||
|
});
|
||||||
|
const cachePct = computed(() => {
|
||||||
|
const total = info.value.quotaTotalBytes;
|
||||||
|
if (!total) return 0;
|
||||||
|
// 缓存必须落在 Topfans 已用区间内(cache 是 used 的子集)
|
||||||
|
const cache = Math.min(info.value.totalBytes, info.value.appUsedBytes);
|
||||||
|
return Math.min(appPct.value, Math.max(0, (cache / total) * 100));
|
||||||
|
});
|
||||||
|
const availablePct = computed(() => {
|
||||||
|
const total = info.value.quotaTotalBytes;
|
||||||
|
if (!total) return 0;
|
||||||
|
return Math.max(0, 100 - appPct.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── 平台判断(仅 APP-PLUS 编译时计算)──
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
const isIos = (() => {
|
||||||
|
try {
|
||||||
|
return uni.getSystemInfoSync().platform === "ios";
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
// #endif
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
loadFailed.value = false;
|
loadFailed.value = false;
|
||||||
try {
|
try {
|
||||||
@ -130,69 +195,154 @@ onPullDownRefresh(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* ── 容器 ── */
|
||||||
.cache-cleanup {
|
.cache-cleanup {
|
||||||
padding-bottom: 40rpx;
|
padding: 24rpx 24rpx 40rpx;
|
||||||
|
background: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.hero {
|
|
||||||
|
/* ── Hero 卡片 ── */
|
||||||
|
.hero-card {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
padding: 40rpx 32rpx 32rpx;
|
padding: 40rpx 32rpx 32rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
.hero-label {
|
.hero-label {
|
||||||
font-size: 26rpx;
|
font-size: 28rpx;
|
||||||
color: #666;
|
color: #8c8c8c;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
.hero-value {
|
.hero-value {
|
||||||
font-size: 72rpx;
|
font-size: 88rpx;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
color: #1890ff;
|
color: #1890ff;
|
||||||
|
letter-spacing: -1rpx;
|
||||||
display: block;
|
display: block;
|
||||||
margin: 12rpx 0;
|
margin: 8rpx 0 24rpx;
|
||||||
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
.hero-percent {
|
.hero-percent {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #8c8c8c;
|
||||||
|
display: block;
|
||||||
|
margin-top: 24rpx;
|
||||||
}
|
}
|
||||||
.progress-bar {
|
.hero-tip {
|
||||||
height: 8rpx;
|
|
||||||
background: #f0f0f0;
|
|
||||||
border-radius: 4rpx;
|
|
||||||
margin: 16rpx 0 24rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.progress-fill {
|
|
||||||
height: 100%;
|
|
||||||
background: #1890ff;
|
|
||||||
}
|
|
||||||
.chips {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16rpx;
|
align-items: center;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #faad14;
|
||||||
|
background: #fffbe6;
|
||||||
|
border-left: 4rpx solid #faad14;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
.chip {
|
|
||||||
flex: 1;
|
/* ── 进度条 ── */
|
||||||
padding: 12rpx;
|
.progress-bar {
|
||||||
|
position: relative;
|
||||||
|
height: 16rpx;
|
||||||
background: #f5f7fa;
|
background: #f5f7fa;
|
||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
font-size: 22rpx;
|
overflow: hidden;
|
||||||
color: #666;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
.section {
|
/* 三段都用 absolute 定位(蓝、紫从左贴齐;绿从右贴齐),
|
||||||
background: #fff;
|
紫叠在蓝之上 → 视觉上"缓存占用 Topfans 已用的一部分" */
|
||||||
|
.seg-app,
|
||||||
|
.seg-cache,
|
||||||
|
.seg-available {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
box-shadow: inset 0 0 0 1rpx rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
.seg-app {
|
||||||
|
left: 0;
|
||||||
|
background: #1890ff;
|
||||||
|
}
|
||||||
|
.seg-cache {
|
||||||
|
left: 0;
|
||||||
|
background: #722ed1;
|
||||||
|
}
|
||||||
|
.seg-available {
|
||||||
|
right: 0;
|
||||||
|
background: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 图例 chips ── */
|
||||||
|
.chips {
|
||||||
|
display: flex;
|
||||||
|
gap: 12rpx;
|
||||||
margin-top: 20rpx;
|
margin-top: 20rpx;
|
||||||
padding: 0 32rpx;
|
}
|
||||||
|
.chip {
|
||||||
|
/* 不再 flex: 1,按内容自适应宽度;white-space 防中文被挤压换行 */
|
||||||
|
padding: 12rpx 16rpx;
|
||||||
|
background: #fafbfc;
|
||||||
|
border: 1rpx solid #f0f0f0;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #595959;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.chip-dot {
|
||||||
|
width: 8rpx;
|
||||||
|
height: 8rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.dot-app {
|
||||||
|
background: #1890ff;
|
||||||
|
}
|
||||||
|
.dot-cache {
|
||||||
|
background: #722ed1;
|
||||||
|
}
|
||||||
|
.dot-available {
|
||||||
|
background: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 分组卡片(缓存分类、其他) ── */
|
||||||
|
.section-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.03);
|
||||||
}
|
}
|
||||||
.section-title {
|
.section-title {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #8c8c8c;
|
||||||
padding: 20rpx 0;
|
font-weight: 500;
|
||||||
|
padding: 24rpx 0 16rpx;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Row ── */
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 28rpx 0;
|
padding: 32rpx 0;
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
.row:active {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
.row-last {
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
.row-main {
|
.row-main {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -204,7 +354,8 @@ onPullDownRefresh(async () => {
|
|||||||
}
|
}
|
||||||
.row-label {
|
.row-label {
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
color: #333;
|
font-weight: 500;
|
||||||
|
color: #262626;
|
||||||
}
|
}
|
||||||
.row-warn {
|
.row-warn {
|
||||||
font-size: 22rpx;
|
font-size: 22rpx;
|
||||||
@ -218,16 +369,18 @@ onPullDownRefresh(async () => {
|
|||||||
}
|
}
|
||||||
.row-sub .size {
|
.row-sub .size {
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #999;
|
color: #8c8c8c;
|
||||||
}
|
}
|
||||||
.row-sub .chevron {
|
.row-sub .chevron {
|
||||||
font-size: 32rpx;
|
font-size: 28rpx;
|
||||||
color: #ccc;
|
color: #bfbfbf;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.row-desc {
|
.row-desc {
|
||||||
font-size: 22rpx;
|
font-size: 22rpx;
|
||||||
color: #999;
|
color: #8c8c8c;
|
||||||
margin-top: 6rpx;
|
margin-top: 6rpx;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
.others-row {
|
.others-row {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@ -236,22 +389,49 @@ onPullDownRefresh(async () => {
|
|||||||
padding-top: 4rpx;
|
padding-top: 4rpx;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
color: #8c8c8c;
|
||||||
|
font-size: 26rpx;
|
||||||
}
|
}
|
||||||
.error {
|
.error {
|
||||||
color: #ff4d4f;
|
color: #ff4d4f;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── 空状态 ── */
|
||||||
.empty {
|
.empty {
|
||||||
text-align: center;
|
display: flex;
|
||||||
padding: 60rpx 0;
|
flex-direction: column;
|
||||||
color: #999;
|
align-items: center;
|
||||||
|
padding: 80rpx 0;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
.empty-icon {
|
||||||
|
width: 96rpx;
|
||||||
|
height: 96rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f6ffed;
|
||||||
|
color: #52c41a;
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.empty-text {
|
||||||
|
color: #8c8c8c;
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── 加载失败按钮 ── */
|
||||||
.load-failed {
|
.load-failed {
|
||||||
|
margin: 0 24rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 40rpx 0;
|
padding: 24rpx 0;
|
||||||
|
background: #e6f7ff;
|
||||||
color: #1890ff;
|
color: #1890ff;
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 16rpx;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import othersHandler from './handlers/othersHandler'
|
|||||||
import preloadHandler from './handlers/preloadHandler'
|
import preloadHandler from './handlers/preloadHandler'
|
||||||
import guideHandler from './handlers/guideHandler'
|
import guideHandler from './handlers/guideHandler'
|
||||||
import draftHandler from './handlers/draftHandler'
|
import draftHandler from './handlers/draftHandler'
|
||||||
|
import { getSandboxTotalSize } from './ioPath'
|
||||||
|
|
||||||
// ── 黑名单(§3.4.1)──
|
// ── 黑名单(§3.4.1)──
|
||||||
// 全等匹配
|
// 全等匹配
|
||||||
@ -100,7 +101,6 @@ export async function getCacheInfo() {
|
|||||||
console.warn('[cacheManager] getStorageInfoSync failed:', e.message)
|
console.warn('[cacheManager] getStorageInfoSync failed:', e.message)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { getSandboxTotalSize } = await import('./ioPath')
|
|
||||||
sandboxBytes = await getSandboxTotalSize()
|
sandboxBytes = await getSandboxTotalSize()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[cacheManager] getSandboxTotalSize failed:', e.message)
|
console.warn('[cacheManager] getSandboxTotalSize failed:', e.message)
|
||||||
|
|||||||
@ -343,13 +343,14 @@ async function getAndroidApkSize() {
|
|||||||
if (typeof plus === 'undefined' || !plus.android) return 0
|
if (typeof plus === 'undefined' || !plus.android) return 0
|
||||||
const main = plus.android.runtimeMainActivity()
|
const main = plus.android.runtimeMainActivity()
|
||||||
if (!main) return 0
|
if (!main) return 0
|
||||||
const Context = plus.android.importClass('android.content.Context')
|
// 走 Context.getApplicationInfo()(App.vue#setPermissions 验证过可工作)
|
||||||
const FLAG_PKG_DATA = Context.PM_FLAGS || 0 // 不一定存在
|
// 不走 PackageManager.getApplicationInfo(pkg, 0):
|
||||||
const pm = main.getPackageManager()
|
// plus.android proxy 对 PackageManager 的 getApplicationInfo 重载方法暴露不稳定,
|
||||||
const pkg = main.getPackageName()
|
// 会报 "pm.getApplicationInfo is not a function"。
|
||||||
// getPackageSizeInfo 是异步回调,这里改用 sourceDir 同步读
|
// Context.getApplicationInfo() 返回当前应用的 ApplicationInfo,正是我们想要的。
|
||||||
const appInfo = pm.getApplicationInfo(pkg, 0)
|
const appInfo = main.getApplicationInfo()
|
||||||
const sourceDir = appInfo.sourceDir
|
if (!appInfo) return 0
|
||||||
|
const sourceDir = plus.android.invoke(appInfo, 'getSourceDir') || appInfo.plusGetAttribute?.('sourceDir')
|
||||||
if (!sourceDir) return 0
|
if (!sourceDir) return 0
|
||||||
const File = plus.android.importClass('java.io.File')
|
const File = plus.android.importClass('java.io.File')
|
||||||
const f = new File(sourceDir)
|
const f = new File(sourceDir)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user