126 lines
13 KiB
Markdown
126 lines
13 KiB
Markdown
# 2026-07-28 工作日志
|
||
|
||
## 记录页「训练勋章」UI 改造(去掉 emoji 图标)
|
||
用户反馈勋章卡片 UI 问题并要求"不用 emoji 图标"。完成:
|
||
- `utils/icons.js`:新增 3 个勋章 PATHS(bolt 闪电 / moon 月亮 / diamond 钻石),并补 white 变体
|
||
boltWhite / moonWhite / diamondWhite / crownWhite(crown/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 三文档。
|