feat(ui): 记录页月份联动与多项 UI 美化/修复

- records: 底部列表与月份选择器联动,新增 [本月|最近] 分段控件;标题/空态随模式切换
- leaderboard: 领奖台布局(冠军居中+皇冠)、次数标注于底座且三档水平对齐;头像 cloud:// 批量预解析提速(云函数需重新部署)
- index/timer: 主色底图标改白色变体修复隐形;目标数字按微信字体缩放补偿真机遮挡
- settings: 配色方案横滑渐变色卡;头像上传前压缩至 200px
- theme: 新增 teal 主题;暗黑背景统一对齐
- 圆形头像双保险(image 自身 border-radius)修复月/年榜方图

注: cloudfunctions/leaderboard 改动须在开发者工具重新部署才生效。
This commit is contained in:
2026-07-22 09:36:22 +08:00
parent 70b87d424d
commit 6176f2c341
32 changed files with 924 additions and 197 deletions
+45 -3
View File
@@ -126,7 +126,8 @@ exports.main = async (event) => {
openid,
duration,
sessions,
nickname: profile.nickname || ''
nickname: profile.nickname || '',
avatarUrl: profile.avatarUrl || ''
})
}
}
@@ -152,9 +153,23 @@ exports.main = async (event) => {
nickname: item.nickname || '',
name: _displayName(item),
duration: item.duration,
sessions: item.sessions
sessions: item.sessions,
avatarUrl: item.avatarUrl || ''
}))
// Pre-resolve cloud:// avatar fileIDs into temporary HTTPS URLs so the
// client <image> doesn't pay a getTempFileURL round-trip at render time
// (that lazy round-trip is what made avatars lag 1-2s behind the text).
// Non-cloud values (wx qlogo links, empty strings, base64 data URIs)
// pass through untouched. If the resolve fails we keep the original fileID
// — the client can still load it, just without the speed-up.
const _avatarUrls = ranked.map(r => r.avatarUrl).concat(myEntry ? [myEntry.avatarUrl] : [])
const _resolved = await _resolveAvatars(_avatarUrls)
_resolved.forEach((url, i) => {
if (i < ranked.length) ranked[i].avatarUrl = url
else if (myEntry) myEntry.avatarUrl = url
})
return {
period,
ranked,
@@ -165,12 +180,39 @@ exports.main = async (event) => {
nickname: myEntry.nickname || '',
name: _displayName(myEntry),
duration: myEntry.duration,
sessions: myEntry.sessions
sessions: myEntry.sessions,
avatarUrl: myEntry.avatarUrl || ''
} : null,
updatedAt: now.toISOString()
}
}
/**
* Batch-resolve cloud:// avatar fileIDs into temporary HTTPS URLs.
* The client <image> would otherwise perform this getTempFileURL round-trip
* lazily at render time — the root of the 1-2s avatar delay. Only cloud://
* IDs are resolved; any other value passes through untouched. getTempFileURL
* accepts ≤50 fileIDs per call, so we page in batches of 50. On failure we
* return the original URLs so the client degrades to its normal cloud:// load.
*/
const _resolveAvatars = async (urls) => {
const cloudUrls = (urls || []).filter(u => typeof u === 'string' && u.startsWith('cloud://'))
if (cloudUrls.length === 0) return urls || []
const map = {}
for (let i = 0; i < cloudUrls.length; i += 50) {
const batch = cloudUrls.slice(i, i + 50)
try {
const res = await cloud.getTempFileURL({ fileList: batch })
;(res.fileList || []).forEach(f => {
if (f && f.fileID && f.tempFileURL) map[f.fileID] = f.tempFileURL
})
} catch (e) {
console.warn('[leaderboard] getTempFileURL batch failed:', e)
}
}
return (urls || []).map(u => (u && map[u]) ? map[u] : u)
}
function maskOpenid(openid) {
if (!openid || openid === 'unknown') return '未知用户'
if (openid.length <= 4) return '****' + openid