Files
2026-07-29 11:05:03 +08:00

126 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-07-28 工作日志
## 记录页「训练勋章」UI 改造(去掉 emoji 图标)
用户反馈勋章卡片 UI 问题并要求"不用 emoji 图标"。完成:
- `utils/icons.js`:新增 3 个勋章 PATHSbolt 闪电 / moon 月亮 / diamond 钻石),并补 white 变体
boltWhite / moonWhite / diamondWhite / crownWhitecrown/hot/trophy 复用已有 PATHS)。
- `config.js``trainingDayBadges` 每项 `icon`/`iconWhite` 改为 icons.js 的 key(如 'hot','bolt'),不再用 emoji 字符串。
- `pages/records/records.wxml`:节点 `<text>`emoji 改 `<image class="badge-node-icon">`,按 unlocked 选 `icons[item.iconWhite]`(白) 或 `icons[item.icon]`(灰)
新增 `.badge-names` 层显示勋章名;把 `nextBadgeHint` 从标题右侧移到"累计训练 X 天"下方,避免标题行被拉扯。
- `pages/records/records.wxss`:节点 `.badge-node.unlocked` 改为实心主题色圆(背景 var(--primary))+ 白色图标;未解锁用灰描边+灰图标(去掉 grayscale/opacity 压暗,保证可见);
`.badge-node-icon` 改 32rpx image;新增 `.badge-names`/`.badge-name` 样式;`.badge-next` 改为居中块。
- `pages/records/records.js`:去掉"全部勋章已解锁 🎉"里的 emoji。
验证:`node --check` 三个 JS 全过;项目内已无 emoji。
## 训练勋章 P0/P1 修复 + 标题/文案对齐调整
用户确认统计口径为"累计天数、可中断",并截图指出标题图标与文字未对齐、图标太小、中间文案拥挤。完成:
- `config.js`:勋章命名全部改为累计语义(一周累计 / 半月累计 / 月度累计 / 双月累计 / 百日累计),注释明确"可中断、非连续打卡"。
- `pages/records/records.js`
- 进度算法改为 sqrt 感知映射:`pos(d)=5+sqrt(d)/sqrt(100)*90`,节点与填充同映射 → 解锁时填充正好抵达节点、满级时 100% 满格;首枚节点从 20.6% 开始,新手期进展可见。
- 新增 `_checkBadgeCelebration()`:持久化 `badge_celebrated` 集合,首次仅初始化不刷屏,后续有新解锁时弹 toast"解锁勋章:XX"并给节点打 `justUnlocked` 标记。
- `pages/records/records.wxml`:标题下新增 `.badge-sub`"累计训练天数,非连续打卡";节点加 `celebrate` 类。
- `pages/records/records.wxss`
- 标题 crown 图标从 28rpx 放大到 36rpx,加 `align-self:center``margin-top:-2rpx` 光学对齐;重置 `.badge-header .section-title` 的 margin-bottom。
- 新增 `.badge-sub` 样式;调整 `.badge-days``.badge-next` 的上下间距,减少中间文案拥挤感。
- 新增 `.badge-node.celebrate` pulse 动画与 `@keyframes badgePop`
验证:`node --check` 通过;旧名/emoji 已无残留。
## 训练勋章卡片去拥挤化
用户截图反馈勋章卡片整体拥挤、文案过多、节点图标挤压。完成:
- `pages/records/records.wxml`
- 删除 `.badge-sub`("累计训练天数,非连续打卡")这一行;
-`.badge-days``.badge-next` 合并为同一行 `.badge-status`(中间用圆点分隔),减少顶部文案层数;
- 删除常驻的 `.badge-names` 层;勋章名改为点击节点时通过 `onBadgeTap` toast 展示;
- 节点加 `data-days` + `bindtap="onBadgeTap"`
- `pages/records/records.js`
- 新增 `onBadgeTap()`:点击已解锁节点提示"已解锁:XX",未解锁提示"再练 N 天解锁「XX」"
- 轨道首尾留白从 5% 缩小到 2.5%,让 6 个节点在窄屏分布更舒展。
- `pages/records/records.wxss`
- 删除 `.badge-sub``.badge-names``.badge-name` 样式;
- 新增 `.badge-status` 及其子元素样式(居中、允许折行、圆点分隔);
- 调整 `.badge-labels``margin-top``opacity`,让天数数字更轻盈;
- 保留 `.badge-node`/`.badge-node-icon`/`.badge-track` 等核心样式。
验证:`node --check` 通过;旧 class 名(badge-sub/badge-names/badge-name)已无残留。
## 训练勋章卡片间距与节点尺寸微调
用户再次截图反馈行间距拥挤、勋章图标粘连、节点圆盘遮挡底部天数。完成:
- `pages/records/records.wxss`
- 状态行 `.badge-status` 下边距从 28rpx 增大到 44rpx
- 轨道 `.badge-track` 上下间距从 `36rpx 0 4rpx` 调整为 `44rpx 0 6rpx`,整体呼吸感加大;
- 节点圆盘从 56rpx 缩小到 44rpx,图标从 32rpx 缩小到 26rpx,避免遮挡下方天数;
- 天数标签层 `.badge-labels``margin-top` 从 14rpx 增大到 22rpx`height` 从 26rpx 增大到 30rpx,让数字与圆盘彻底分离;
- 解锁 pulse 光晕从 8rpx 缩小到 6rpx,匹配更小节点。
验证:`node --check` 通过。
## 训练勋章:改回等距节点 + 恢复大图标
用户指出前 3 个节点间距不一致(sqrt 映射副作用),并希望不缩小图标、改用增大区域高度来避免遮挡。完成:
- `pages/records/records.js`:进度算法从 sqrt 感知映射**改回等距排列**——`segWidth=(100-2*TRACK_PAD)/(n-1)``pos(i)=TRACK_PAD+i*segWidth`,节点与填充同套线性插值。TRACK_PAD 回到 4%;副作用是节点间距完全一致、视觉更整齐,但"新手期进度条动得快"的感知弱化(用户更看重整齐)。
- `pages/records/records.wxss`
- 节点圆盘恢复 56rpx、图标恢复 32rpx、celebrate 光晕恢复 8rpx(撤销上一轮的缩小);
- 通过增大区域高度避免遮挡:`.badge-track` margin 改为 `48rpx 0 20rpx``.badge-labels` margin-top 改为 32rpx;圆盘下缘到天数字起始约 52rpx(圆盘半径 28rpx + 留白),不再遮挡;
- 状态行 `.badge-status` 下边距保持 44rpx。
验证:`node --check` 通过。
## 训练勋章:让"已拥有勋章"直观可见(方案 1 + 3)
用户确认保持等距进度条布局不变,痛点转为"用户无法直观看到自己目前有哪些勋章"。落地方案 1(已解锁名字常驻)+ 方案 3(✓ 角标强化拥有感):
- `pages/records/records.wxml`
- 已解锁节点加 `<view class="badge-check" wx:if="{{item.unlocked}}">` 角标;
- `.badge-label` 改为 `item.unlocked ? item.name : item.days`——已解锁渲染勋章名、未解锁只渲染天数数字(避免之前 6 名字常驻拥挤,现在只有已解锁的 1–3 个显示名字)。
- `pages/records/records.wxss`
- 新增 `.badge-check`24rpx 主题色圆 + 2rpx 白描边,定位在节点 `right:-3rpx; bottom:-3rpx`)与 `.badge-check::after`(纯 CSS border 旋转 45° 画白勾;因图标 SVG 颜色烤死无法用变量改色,故用伪元素画);
- `.badge-label.unlocked` 字号 20→22rpx、字重 600→700,让已拥有的勋章名更醒目。
验证:`node --check` 三个 JS 全过;wxml 的 badge-check/badge-label 与 wxss 样式一致。首尾 TRACK_PAD=4%,节点在 4%96%,✓ 角标不溢出卡片。
## 训练勋章:新增 80 天「洪荒神登」图标(star)
用户在 config.js 的 trainingDayBadges 加了 80 天「洪荒神登」,临时复用了 diamond 图标(与 60 天「上古神登」撞图标)。完成:
- `utils/icons.js`PATHS 新增 `star`Material 五角星路径,24x24 单色);build() 的 Badge white variants 段新增 `out.starWhite = _svg(PATHS.star, '#FFFFFF')`(解锁态白星,坐在主题色圆盘上)。
- `config.js`:第 69 行 80 天勋章 `icon/iconWhite``diamond/diamondWhite` 改为 `star/starWhite`;并修正上方过时注释(原写"name 一律用累计措辞",实际用户已改成"登"系列幽默命名),改为"口径=累计去重训练日(可中断);name 仅作趣味命名,不暗示连续打卡"。
验证:`node --check` 通过 icons.js / config.js。注意:勋章现 7 枚,等距每段约 115rpx、节点间隙约 59rpx,仍不重叠(之前评估 8–10 枚才明显拥挤)。
## 排行榜首开延迟分析(3-4s)+ P1 索引/投影落地
用户反馈排行榜首次打开有时延迟 3-4s 才显示数据。分析结论(已用示意图拆解):
- 根因:排行榜在**用户请求时同步全量计算** `cloudfunctions/leaderboard/index.js``_fetchLatestByOpenid()` 全表扫描 plank_data;没有预计算快照。客户端缓存(30min)只缓解"重复进入",云实例缓存(5min, 内存不跨容器)随低流量容器回收频繁变冷。
- 耗时拆解(冷路径):云函数冷启动 ~1.2s + 全表扫描 ~1.5-2.5s + 头像 getTempFileURL ~0.3-0.5s;热路径 <0.6s。
- 关键坑:原查询 `_.or([{updatedAt:gte(90d)},{updatedAt:exists(false)}])``exists(false)` 分支无法走索引 → 实质全集合扫描;且每次读全字段(settings/streak/customPlans/themeId 都在 doc 里),载荷大。
- 修复分层:P0 预计算快照(定时触发器写 leaderboard_snapshot,用户函数只读1 doc)P1 updatedAt 索引+投影;P1 App 启动预热;P2 force 不再绕过云缓存。
用户已在控制台给 plank_data.updatedAt 建单字段索引。我落地 P1 代码侧:
- `cloudfunctions/leaderboard/index.js``_fetchLatestByOpenid`:去掉 `exists(false)` OR 兜底分支(让 gte(90d) 干净走索引),并给查询加 `.field({records,profile,_openid,updatedAt})` 投影,跳过 settings/streak/customPlans/themeId 大字段,缩小单次读取载荷。
- 取舍:无 updatedAt 的极旧遗留 doc(必已 90+ 天未同步)将不再入榜,可接受。
- 验证:`node --check cloudfunctions/leaderboard/index.js` 通过。
下一步可选:P0 定时快照(彻底消除首开等待与容器冷启动影响)。
## 排行榜延迟根治:P0 预计算快照 + P1 预热 + P2 force(已落地)
用户确认做 P0/P1/P2。完成:
- `cloudfunctions/leaderboard/index.js` 重写:
-`_buildFromScan(latestByOpenid, period, limit, myOpenid)`:从已抓取 Map 算单周期榜(含头像 getTempFileURL 解析),供实时与定时两条路径共用;
- 新增 `_persistSnapshot(period, result)`:写 `leaderboard_snapshot` 集合(doc._id=周期,字段 period/ranked/myOpenid/updatedAt);首次写自动建集合;
- 新增 `_serveFromSnapshot(period, limit, myOpenid)`:读快照(1 次 get≈50ms),按 openid 计算 myEntry;快照缺失或过期(>6min)返回 null 回退实时;
- 新增 `_rebuildSnapshots()`:扫描一次、`_buildFromScan` 复用算 day/month/year 三榜并各自 `_persistSnapshot`SNAPSHOT_TOP=500=函数 maxRank 上限);
- 新增 `_computeBoard(period, limit, myOpenid, {force,persist})`:实时重算并可选回写快照;
- `exports.main` 区分:**timer 触发**(event.type==='timer'||Type||triggerName||MessageType) → `_rebuildSnapshots()`;客户端请求 → force 则 `_computeBoard(force:true,persist:true)`(训练后那次,唯一整表扫描),否则 `_serveFromSnapshot`,缺失/过期则 `_computeBoard(force:false,persist:true)` 兜底并回写。
- 响应结构保持 `{period,ranked,myOpenid,myEntry,updatedAt}` 不变,客户端 `_applyResult` 无改动。
- `cloudfunctions/leaderboard/config.json`:加 `triggers` 定时器 `snapshotTimer` type=timer config=`0 */4 * * * * *`WeChat 7 字段 cron,每 4 分钟触发一次重建快照)。**注意:需重新部署该云函数定时器才生效。**
- `app.js`onLaunch 在 `cloud.init()` 后 fire-and-forget 调一次 `leaderboard`(period=day, maxRank=config.leaderboardMaxRank),提前填充云实例/快照(P1 启动预热);guard 用 `cloud.enabled`;新增 `require('./config')`
- `pages/leaderboard/leaderboard.js`:仅更新过时注释(原"云函数全表扫描冷启动 2-3s"改为"现读预计算快照 1 次 get≈50ms")。force 语义:快照架构下正常打开读快照(已满足"force 不再绕过云缓存"诉求),仅训练后那次 force 触发实时重算+回写。
验证:`node --check` 通过 index.js / app.js / leaderboard.js / config.js;函数内调用图一致。
效果预期:首开/冷启动排行榜从 2-3s 降到 ≈50ms(1 次 get);容器冷启动不再慢;每 4min 离线刷新三榜,数据最多延迟 4min(训练后 force 立即刷新)。
## 排行榜快照写入失败修复(wx-server-sdk set 需 { data } 包裹)
用户手动触发云函数,leaderboard_snapshot 集合仍无数据,日志报 `parameter.data should be object instead of undefined`day/month/year 三榜 persist 全失败)。
- 排查:本地 `node --check` 通过;本地 mock 跑 `_persistSnapshot` 逻辑确认 `set` 收到的是对象(data 在 console.log 时存在、rankedLen 能打印);WebSearch 定位根因——wx-server-sdk 写操作(add/update/set)参数必须是 `{ data: {...} }` 包裹,SDK 读 `parameter.data`,裸对象会让 `data.data`=undefined 触发该报错(读操作 `get()` 不受影响)。此前判断"部署代码未刷新"是错的。
- 修复:`cloudfunctions/leaderboard/index.js``_persistSnapshot` 改为 `docRef.set({ data: { period, ranked, myOpenid, updatedAt } })`(引用拆分存 docRef + data 包裹 + 诊断日志 + .errMsg 容错)。读取 `doc.data` 兼容(SDK 解开 data 外套,文档内容即裸对象)。
- `day rankedLen=0` 非 bug`record.date` 格式为 `YYYY-MM-DD HH:MM:SS`formatDate 生成,`pages/records/records.js:225` 注释确认),`_buildFromScan``r.date.startsWith('2026-07-28')` 能匹配;0 多为测试时(09:30)当天确实无训练记录,month/year 历史累计有 5 条属正常。
- 验证:`node --check` 通过。
- 待办:用户需**重新部署 leaderboard 云函数**(含最新 _persistSnapshot),再手动触发/进榜下拉刷新,日志应现 `persist ok`、集合出现 day/month/year 三文档。