feat: 排行榜虚拟领奖台 + 勋章对齐与尺寸统一
- 领奖台数据不足 3 名时显示虚拟空位(虚线轮廓+虚位以待) - 领奖台勋章与昵称对齐改为等高盒结构性修复(移除 margin 盲推) - 全 app 成就勋章图标尺寸统一为 30rpx(前三名/列表/设置页/记录页一致) - 勋章 SVG 按包围盒精确居中(utils/icons.js) - 设置页/记录页勋章展示配套调整
This commit is contained in:
@@ -65,6 +65,37 @@ Page({
|
||||
this.fetchRank()
|
||||
},
|
||||
|
||||
/**
|
||||
* totalDays → 最新解锁勋章(trainingDayBadges 按天数递增,取 days <= totalDays 最后一项)。
|
||||
* 返回 badge 定义(含 days/name/icon),无解锁或异常返回 null。
|
||||
* 云函数路径:item.totalDays 是云端算的(全榜通用,别人的行也能显示);
|
||||
* 本地兜底:用 storage 本地 totalDays 算自己那条。
|
||||
*/
|
||||
_badgeOf(totalDays) {
|
||||
try {
|
||||
const days = Math.max(0, Number(totalDays) || 0)
|
||||
const badges = config.trainingDayBadges || []
|
||||
let latest = null
|
||||
for (const b of badges) {
|
||||
if (days >= b.days) latest = b
|
||||
}
|
||||
return latest
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
/** 点击勋章:toast 展示勋章名与解锁天数门槛 */
|
||||
onBadgeTap(e) {
|
||||
const badge = e.currentTarget.dataset
|
||||
if (!badge || !badge.name) return
|
||||
wx.showToast({
|
||||
title: badge.name + ' · 累计 ' + badge.days + ' 天解锁',
|
||||
icon: 'none',
|
||||
duration: 1800
|
||||
})
|
||||
},
|
||||
|
||||
fetchRank(cb, force) {
|
||||
// 序列号:快速切换周期时丢弃旧响应,避免旧 period 的数据覆盖新 period
|
||||
this._fetchSeq = (this._fetchSeq || 0) + 1
|
||||
@@ -131,6 +162,17 @@ Page({
|
||||
} else {
|
||||
base.subText = `${item.sessions}次`
|
||||
}
|
||||
// 勋章:云函数下发每个用户的 totalDays,映射最新解锁勋章(全榜可见)。
|
||||
// 浅底行用主题色版 iconFill;旧快照无 totalDays 时 badge 为 null,不渲染。
|
||||
// 注意:badgeIcon 必须赋图标 data URI(从 icons 映射查),不能赋字符串 key,
|
||||
// 否则 WXML src 会当成路径加载报 500。
|
||||
const badge = this._badgeOf(item.totalDays)
|
||||
if (badge) {
|
||||
const icons = this.data.icons || iconsMod.build()
|
||||
base.badgeIcon = icons[badge.icon + 'Fill']
|
||||
base.badgeName = badge.name
|
||||
base.badgeDays = badge.days
|
||||
}
|
||||
return base
|
||||
})
|
||||
const myEntry = result.myEntry ? (() => {
|
||||
@@ -147,6 +189,16 @@ Page({
|
||||
} else {
|
||||
base.subText = `${result.myEntry.sessions}次`
|
||||
}
|
||||
// my-bar 是主题渐变底,需要白版图标;badgeIcon(主题色)给 is-me 行用。
|
||||
// 同样必须赋 data URI,不能赋字符串 key。
|
||||
const badge = this._badgeOf(result.myEntry.totalDays)
|
||||
if (badge) {
|
||||
const icons = this.data.icons || iconsMod.build()
|
||||
base.badgeIcon = icons[badge.icon + 'Fill']
|
||||
base.badgeIconWhite = icons[badge.icon + 'White']
|
||||
base.badgeName = badge.name
|
||||
base.badgeDays = badge.days
|
||||
}
|
||||
return base
|
||||
})() : null
|
||||
const empty = list.length === 0 && !myEntry
|
||||
@@ -168,6 +220,17 @@ Page({
|
||||
const profile = storage.getProfile()
|
||||
const localName = profile.nickname || '我'
|
||||
|
||||
// 本地兜底勋章:从本地记录算去重训练日(与云函数 totalDays 口径一致),
|
||||
// 映射最新解锁勋章。云函数不可用时自己的行/my-bar 仍能显示勋章。
|
||||
// 注意:badgeIcon 必须是 data URI(从 icons 映射查),不能是字符串 key。
|
||||
const trainedDays = new Set()
|
||||
allRecords.forEach(r => { if (r && r.date) trainedDays.add(util.dateOnly(r.date)) })
|
||||
const localBadge = this._badgeOf(trainedDays.size)
|
||||
const _icons = this.data.icons || iconsMod.build()
|
||||
const localBadgeFields = localBadge
|
||||
? { badgeIcon: _icons[localBadge.icon + 'Fill'], badgeIconWhite: _icons[localBadge.icon + 'White'], badgeName: localBadge.name, badgeDays: localBadge.days }
|
||||
: {}
|
||||
|
||||
// 耐力榜本地兜底:取本地全部记录里单次最久 + 其日期
|
||||
if (period === 'endurance') {
|
||||
let bestDuration = 0
|
||||
@@ -181,7 +244,11 @@ Page({
|
||||
const entry = {
|
||||
rank: 1, openid: 'local', name: localName, nickname: profile.nickname || '',
|
||||
duration: bestDuration, durationText: util.formatDuration(bestDuration),
|
||||
bestDate, subText: this._formatBestDate(bestDate), subDate: date, subTime: time
|
||||
bestDate, subText: this._formatBestDate(bestDate), subDate: date, subTime: time,
|
||||
// 本地兜底也要 isMe:否则 is-me 行不渲染勋章、my-bar 条件(rank>length)又不满足,
|
||||
// 勋章在弱网/云函数故障时完全不可见
|
||||
isMe: true,
|
||||
...localBadgeFields
|
||||
}
|
||||
this.setData({ rankedList: [entry], myEntry: { ...entry, isMe: true }, loading: false, refreshing: false, empty: false })
|
||||
} else {
|
||||
@@ -206,7 +273,10 @@ Page({
|
||||
const entry = {
|
||||
rank: 1, openid: 'local', name: localName, nickname: profile.nickname || '',
|
||||
duration, durationText: util.formatDuration(duration), sessions,
|
||||
subText: `${sessions}次`
|
||||
subText: `${sessions}次`,
|
||||
// 同耐力榜:本地兜底 entry 必须带 isMe,否则勋章不渲染
|
||||
isMe: true,
|
||||
...localBadgeFields
|
||||
}
|
||||
this.setData({ rankedList: [entry], myEntry: { ...entry, isMe: true }, loading: false, refreshing: false, empty: false })
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user