feat(records): 训练勋章 UI 改造
- 勋章图标从 emoji 改为矢量 SVG 线性图标(utils/icons.js 新增 bolt/moon/diamond 路径及 white 变体) - 命名改为累计语义(一周累计/半月累计/月度累计/双月累计/百日累计),并标注"非连续打卡" - 进度条改回等距节点,解锁时填充正好抵达节点、满级满格 - 新增单枚解锁庆祝:持久化已见解锁集合,进入页面弹 toast + 节点 pop 动画 - 已解锁节点常驻显示勋章名 + 右上角 ✓ 角标,直观体现"已拥有" - utils/storage.js 新增 totalDays(累计去重训练日),为勋章统计提供数据源
This commit is contained in:
@@ -14,6 +14,10 @@ Page({
|
||||
totalSessions: 0,
|
||||
maxDuration: 0,
|
||||
maxDurationText: '',
|
||||
totalDays: 0,
|
||||
trainingBadges: [],
|
||||
badgeProgress: 0,
|
||||
nextBadgeHint: '',
|
||||
currentYear: new Date().getFullYear(),
|
||||
currentMonth: new Date().getMonth() + 1,
|
||||
monthRecords: [],
|
||||
@@ -69,6 +73,43 @@ Page({
|
||||
if (historyList.length >= 30) break
|
||||
}
|
||||
|
||||
// 累计训练天数勋章(里程碑进度条):统计口径=累计去重训练日(可中断)。
|
||||
// 节点等距排列(间距一致、视觉整齐),填充在区间内线性插值,
|
||||
// 解锁时填充正好抵达该节点,满级时填充满格。
|
||||
const totalDays = stats.totalDays
|
||||
const badgeDefs = config.trainingDayBadges
|
||||
const n = badgeDefs.length
|
||||
const maxDays = badgeDefs[n - 1].days
|
||||
const TRACK_PAD = 4 // 首尾留白,保证首尾节点/标签不溢出
|
||||
const segWidth = n > 1 ? (100 - TRACK_PAD * 2) / (n - 1) : 0
|
||||
const posOf = (i) => TRACK_PAD + i * segWidth
|
||||
const trainingBadges = badgeDefs.map((b, i) => ({
|
||||
...b,
|
||||
unlocked: totalDays >= b.days,
|
||||
pos: Math.round(posOf(i) * 10) / 10
|
||||
}))
|
||||
let badgeProgress
|
||||
if (totalDays <= 0) {
|
||||
badgeProgress = 0
|
||||
} else if (totalDays >= maxDays) {
|
||||
badgeProgress = 100
|
||||
} else {
|
||||
let i = 0
|
||||
while (i < n - 1 && totalDays >= badgeDefs[i + 1].days) i++
|
||||
const ratio = (totalDays - badgeDefs[i].days) / (badgeDefs[i + 1].days - badgeDefs[i].days)
|
||||
badgeProgress = Math.round((posOf(i) + ratio * segWidth) * 10) / 10
|
||||
}
|
||||
|
||||
// 单枚解锁庆祝反馈:对比持久化"已见解锁集合",只庆祝新增勋章。
|
||||
// 首次运行(无存储)仅初始化集合,避免老用户一打开就被历史勋章刷屏。
|
||||
const unlockedDays = trainingBadges.filter(b => b.unlocked).map(b => b.days)
|
||||
const newlySet = this._checkBadgeCelebration(unlockedDays)
|
||||
if (newlySet) trainingBadges.forEach(b => { b.justUnlocked = newlySet.has(b.days) })
|
||||
const nextIdx = trainingBadges.findIndex(b => !b.unlocked)
|
||||
const nextBadgeHint = nextIdx === -1
|
||||
? '全部勋章已解锁!'
|
||||
: `还差 ${badgeDefs[nextIdx].days - totalDays} 天解锁「${badgeDefs[nextIdx].name}」`
|
||||
|
||||
const theme = themeMod.getCurrentTheme()
|
||||
this.setData({
|
||||
theme,
|
||||
@@ -78,6 +119,10 @@ Page({
|
||||
totalSessions: stats.totalSessions,
|
||||
maxDuration: stats.maxDuration,
|
||||
maxDurationText: util.formatDuration(stats.maxDuration),
|
||||
totalDays,
|
||||
trainingBadges,
|
||||
badgeProgress,
|
||||
nextBadgeHint,
|
||||
monthRecords: records,
|
||||
historyList,
|
||||
listMode: this.data.listMode || 'month',
|
||||
@@ -245,5 +290,44 @@ Page({
|
||||
return {
|
||||
title: config.share.timelineTitle
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击勋章节点:显示勋章名称与解锁状态。
|
||||
*/
|
||||
onBadgeTap(e) {
|
||||
const days = Number(e.currentTarget.dataset.days)
|
||||
const b = config.trainingDayBadges.find(x => x.days === days)
|
||||
if (!b) return
|
||||
const unlocked = this.data.totalDays >= days
|
||||
wx.showToast({
|
||||
title: unlocked ? `已解锁:${b.name}` : `再练 ${days - this.data.totalDays} 天解锁「${b.name}」`,
|
||||
icon: 'none',
|
||||
duration: 1800
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 单枚勋章解锁庆祝:对比持久化的"已见解锁集合",只对新增的解锁弹 toast
|
||||
* 并标记 justUnlocked(节点高亮动画)。首次运行(无存储)仅初始化集合、不庆祝,
|
||||
* 避免老用户一打开就刷一堆历史勋章。返回新增天数集合(无新增返回 null)。
|
||||
*/
|
||||
_checkBadgeCelebration(unlockedDays) {
|
||||
const KEY = 'badge_celebrated'
|
||||
const prev = wx.getStorageSync(KEY)
|
||||
if (!Array.isArray(prev)) {
|
||||
wx.setStorageSync(KEY, unlockedDays)
|
||||
return null
|
||||
}
|
||||
const prevSet = new Set(prev)
|
||||
const newly = unlockedDays.filter(d => !prevSet.has(d))
|
||||
wx.setStorageSync(KEY, unlockedDays)
|
||||
if (newly.length === 0) return null
|
||||
const names = newly.map(d => {
|
||||
const b = config.trainingDayBadges.find(x => x.days === d)
|
||||
return b ? b.name : (d + '天')
|
||||
})
|
||||
wx.showToast({ title: '解锁勋章:' + names.join('、'), icon: 'none', duration: 2000 })
|
||||
return new Set(newly)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user