feat(ui): 排行榜与首页打卡卡片多项优化

排行榜性能:
- 客户端乐观缓存:进页面/切周期秒显旧数据,后台静默刷新,消除 2-3s 空白
- 云函数实例缓存 TTL 60s -> 5min,降低全表扫描频率

排行榜 UI:
- 空状态重设计:奖杯光环插画(径向光晕 + 闪光漂浮)
- 领奖台 slot 撑满对齐列表、金银铜底座配色、角标与昵称间距修复、底线对齐

首页打卡卡片:
- 火焰光晕脉动 + 72rpx 大数字 + 分段进度格子
- 计划 >10 天时进度退化为里程碑连续条

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-27 17:28:53 +08:00
parent 4b5f195a18
commit e2becc24aa
9 changed files with 329 additions and 121 deletions
+32 -5
View File
@@ -17,6 +17,7 @@ Page({
rankedList: [],
myEntry: null,
loading: false,
refreshing: false,
empty: false,
icons: iconsMod.build()
},
@@ -58,7 +59,24 @@ Page({
this._fetchSeq = (this._fetchSeq || 0) + 1
const seq = this._fetchSeq
const period = this.data.activePeriod
this.setData({ loading: true, empty: false })
// 乐观缓存:命中该 period 的近期缓存就先秒显旧数据,后台再静默拉新。
// 命中时不进 loading 骨架屏(避免旧数据被空白覆盖),用 refreshing 标记
// 后台刷新状态;未命中才显示骨架屏。云函数全表扫描冷启动可达 2-3s,
// 这层客户端缓存把"重复进入 / 切 period"的等待几乎降到无感。
const cached = storage.getLeaderboardCache(period)
if (cached && cached.rankedList) {
this.setData({
rankedList: cached.rankedList,
myEntry: cached.myEntry,
loading: false,
empty: cached.empty,
refreshing: true
})
} else {
this.setData({ loading: true, empty: false, refreshing: false })
}
wx.cloud.callFunction({
name: 'leaderboard',
data: {
@@ -76,7 +94,12 @@ Page({
// them into thinking the cloud is broken.
console.warn('[leaderboard] cloud function failed, using local fallback:', err)
if (seq !== this._fetchSeq) { if (cb) cb(); return }
this._applyLocal()
// 已有缓存秒显时后台刷新失败:保持旧数据,不打断用户;仅无缓存才走本地兜底。
if (cached && cached.rankedList) {
this.setData({ refreshing: false })
} else {
this._applyLocal()
}
if (cb) cb()
})
},
@@ -91,12 +114,16 @@ Page({
durationText: util.formatDuration(result.myEntry.duration),
isMe: true
} : null
const empty = list.length === 0 && !myEntry
this.setData({
rankedList: list,
myEntry,
loading: false,
empty: list.length === 0 && !myEntry
refreshing: false,
empty
})
// 写入乐观缓存:下次进页面 / 切 period 秒显
storage.setLeaderboardCache(this.data.activePeriod, { rankedList: list, myEntry, empty })
},
/** Local fallback: use local storage when cloud function isn't deployed */
@@ -122,9 +149,9 @@ Page({
rank: 1, openid: 'local', name: localName, nickname: profile.nickname || '',
duration, durationText: util.formatDuration(duration), sessions
}
this.setData({ rankedList: [entry], myEntry: { ...entry, isMe: true }, loading: false, empty: false })
this.setData({ rankedList: [entry], myEntry: { ...entry, isMe: true }, loading: false, refreshing: false, empty: false })
} else {
this.setData({ loading: false, empty: true })
this.setData({ loading: false, refreshing: false, empty: true })
}
},