From 903951aee026765a5e168b456fa86ac52e6dcfcd Mon Sep 17 00:00:00 2001 From: cnliucheng Date: Wed, 29 Jul 2026 11:45:20 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8B=8B=E7=AB=A0=E5=88=86?= =?UTF-8?q?=E6=AE=B5=E8=BF=9B=E5=BA=A6=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/records/records.js | 24 ++++++++++--------- pages/records/records.wxml | 7 ++++-- pages/records/records.wxss | 7 ++++-- tests/badge-state.test.js | 42 ++++++++++++++++++++++++++++++-- utils/badge-state.js | 49 +++++++++++++++++++++++++++++++++++--- 5 files changed, 109 insertions(+), 20 deletions(-) diff --git a/pages/records/records.js b/pages/records/records.js index 7a69622..839f28a 100644 --- a/pages/records/records.js +++ b/pages/records/records.js @@ -3,7 +3,12 @@ const util = require('../../utils/util') const themeMod = require('../../utils/theme') const iconsMod = require('../../utils/icons') const config = require('../../config') -const { getBadgeProgress, getBadgeCelebration } = require('../../utils/badge-state') +const { + getBadgePositions, + getBadgeSegments, + getBadgeDisplayLabel, + getBadgeCelebration +} = require('../../utils/badge-state') Page({ data: { @@ -17,7 +22,7 @@ Page({ maxDurationText: '', totalDays: 0, trainingBadges: [], - badgeProgress: 0, + badgeSegments: [], nextBadgeHint: '', currentYear: new Date().getFullYear(), currentMonth: new Date().getMonth() + 1, @@ -75,20 +80,17 @@ Page({ } // 累计训练天数勋章(里程碑进度条):统计口径=累计去重训练日(可中断)。 - // 节点等距排列(间距一致、视觉整齐),填充在区间内线性插值, - // 解锁时填充正好抵达该节点,满级时填充满格。 + // 节点等距排列;每个连接段独立填充,绿色铺满即解锁下一枚勋章。 const totalDays = stats.totalDays const badgeDefs = config.trainingDayBadges - const n = badgeDefs.length - const TRACK_PAD = 4 // 首尾留白,保证首尾节点/标签不溢出 - const segWidth = n > 1 ? (100 - TRACK_PAD * 2) / (n - 1) : 0 - const posOf = (i) => TRACK_PAD + i * segWidth + const badgePositions = getBadgePositions(badgeDefs) const trainingBadges = badgeDefs.map((b, i) => ({ ...b, unlocked: totalDays >= b.days, - pos: Math.round(posOf(i) * 10) / 10 + pos: badgePositions[i], + label: getBadgeDisplayLabel(b, totalDays >= b.days) })) - const badgeProgress = getBadgeProgress(badgeDefs, totalDays) + const badgeSegments = getBadgeSegments(badgeDefs, totalDays) // 单枚解锁庆祝反馈:对比持久化"已见解锁集合",只庆祝新增勋章。 // 首次运行(无存储)仅初始化集合,避免老用户一打开就被历史勋章刷屏。 @@ -111,7 +113,7 @@ Page({ maxDurationText: util.formatDuration(stats.maxDuration), totalDays, trainingBadges, - badgeProgress, + badgeSegments, nextBadgeHint, monthRecords: records, historyList, diff --git a/pages/records/records.wxml b/pages/records/records.wxml index ea87771..3c42ab3 100644 --- a/pages/records/records.wxml +++ b/pages/records/records.wxml @@ -59,7 +59,10 @@ {{nextBadgeHint}} - + + + {{item.unlocked ? item.name : item.days}} + style="left: {{item.pos}}%;">{{item.label}} diff --git a/pages/records/records.wxss b/pages/records/records.wxss index 3f32457..ad16d5a 100644 --- a/pages/records/records.wxss +++ b/pages/records/records.wxss @@ -101,11 +101,14 @@ border-radius: 2rpx; } -.badge-fill { +.badge-segment { position: absolute; - left: 0; top: 0; height: 100%; +} + +.badge-segment-fill { + height: 100%; background: var(--primary); border-radius: 2rpx; transition: width 0.6s ease; diff --git a/tests/badge-state.test.js b/tests/badge-state.test.js index 5f517fc..ca1e18f 100644 --- a/tests/badge-state.test.js +++ b/tests/badge-state.test.js @@ -1,9 +1,23 @@ const test = require('node:test') const assert = require('node:assert/strict') -const { getBadgeProgress, getBadgeCelebration } = require('../utils/badge-state') +const { + getBadgeProgress, + getBadgePositions, + getBadgeSegments, + getBadgeDisplayLabel, + getBadgeCelebration +} = require('../utils/badge-state') -const badges = [{ days: 3 }, { days: 7 }, { days: 14 }] +const badges = [ + { days: 3, name: '抖登' }, + { days: 7, name: '中登' }, + { days: 14, name: '老登' }, + { days: 30, name: '神登' }, + { days: 60, name: '上古神登' }, + { days: 80, name: '洪荒神登' }, + { days: 100, name: '究极登祖' } +] test('badge progress remains zero before the first milestone', () => { assert.equal(getBadgeProgress(badges, 1), 0) @@ -11,6 +25,30 @@ test('badge progress remains zero before the first milestone', () => { assert.equal(getBadgeProgress(badges, 3), 4) }) +test('milestones use equal spacing and overall fill stays aligned', () => { + assert.deepEqual(getBadgePositions(badges), [4, 19.3, 34.7, 50, 65.3, 80.7, 96]) + assert.equal(getBadgeProgress(badges, 7), 19.3) + assert.equal(getBadgeProgress(badges, 60), 65.3) + assert.equal(getBadgeProgress(badges, 100), 100) +}) + +test('only the active badge connection is partially green', () => { + assert.deepEqual(getBadgeSegments(badges, 16), [ + { left: 4, width: 15.3, progress: 100 }, + { left: 19.3, width: 15.4, progress: 100 }, + { left: 34.7, width: 15.3, progress: 12.5 }, + { left: 50, width: 15.3, progress: 0 }, + { left: 65.3, width: 15.4, progress: 0 }, + { left: 80.7, width: 15.3, progress: 0 } + ]) +}) + +test('equal spacing keeps full unlocked badge names readable', () => { + assert.equal(getBadgeDisplayLabel(badges[3], true), '神登') + assert.equal(getBadgeDisplayLabel(badges[4], true), '上古神登') + assert.equal(getBadgeDisplayLabel(badges[6], false), '100天') +}) + test('badge celebration keeps prior milestones seen after records are deleted', () => { const afterDeletion = getBadgeCelebration([3], []) assert.deepEqual(afterDeletion, { newly: [], seen: [3] }) diff --git a/utils/badge-state.js b/utils/badge-state.js index 3c88a46..8337195 100644 --- a/utils/badge-state.js +++ b/utils/badge-state.js @@ -1,5 +1,15 @@ const TRACK_PAD = 4 +const roundPosition = (value) => Math.round(value * 10) / 10 + +const getBadgePositions = (badges) => { + const defs = Array.isArray(badges) ? badges : [] + if (defs.length === 0) return [] + if (defs.length === 1) return [TRACK_PAD] + const segmentWidth = (100 - TRACK_PAD * 2) / (defs.length - 1) + return defs.map((_, index) => roundPosition(TRACK_PAD + index * segmentWidth)) +} + const getBadgeProgress = (badges, totalDays) => { const defs = Array.isArray(badges) ? badges : [] if (defs.length === 0) return 0 @@ -8,12 +18,39 @@ const getBadgeProgress = (badges, totalDays) => { if (days < defs[0].days) return 0 if (defs.length === 1 || days >= defs[defs.length - 1].days) return 100 - const segmentWidth = (100 - TRACK_PAD * 2) / (defs.length - 1) + const positions = getBadgePositions(defs) let index = 0 while (index < defs.length - 1 && days >= defs[index + 1].days) index++ const ratio = (days - defs[index].days) / (defs[index + 1].days - defs[index].days) - return Math.round((TRACK_PAD + index * segmentWidth + ratio * segmentWidth) * 10) / 10 + return roundPosition(positions[index] + ratio * (positions[index + 1] - positions[index])) +} + +const getBadgeSegments = (badges, totalDays) => { + const defs = Array.isArray(badges) ? badges : [] + const positions = getBadgePositions(defs) + const days = Math.max(0, Number(totalDays) || 0) + + return defs.slice(0, -1).map((badge, index) => { + const nextBadge = defs[index + 1] + const segmentDays = nextBadge.days - badge.days + const progress = days <= badge.days + ? 0 + : days >= nextBadge.days + ? 100 + : roundPosition((days - badge.days) / segmentDays * 100) + + return { + left: positions[index], + width: roundPosition(positions[index + 1] - positions[index]), + progress + } + }) +} + +const getBadgeDisplayLabel = (badge, unlocked) => { + if (!badge) return '' + return unlocked && badge.name ? badge.name : `${badge.days}天` } const getBadgeCelebration = (previous, unlockedDays) => { @@ -25,4 +62,10 @@ const getBadgeCelebration = (previous, unlockedDays) => { return { newly, seen: Array.from(new Set(previous.concat(unlocked))) } } -module.exports = { getBadgeProgress, getBadgeCelebration } +module.exports = { + getBadgeProgress, + getBadgePositions, + getBadgeSegments, + getBadgeDisplayLabel, + getBadgeCelebration +}