Files
wx_pbzc/.workbuddy/memory/2026-07-28.md
T
2026-07-29 11:05:03 +08:00

13 KiB
Raw Blame History

2026-07-28 工作日志

记录页「训练勋章」UI 改造(去掉 emoji 图标)

用户反馈勋章卡片 UI 问题并要求"不用 emoji 图标"。完成:

  • utils/icons.js:新增 3 个勋章 PATHSbolt 闪电 / moon 月亮 / diamond 钻石),并补 white 变体 boltWhite / moonWhite / diamondWhite / crownWhitecrown/hot/trophy 复用已有 PATHS)。
  • config.jstrainingDayBadges 每项 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:centermargin-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-labelsmargin-topopacity,让天数数字更轻盈;
    • 保留 .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-labelsmargin-top 从 14rpx 增大到 22rpxheight 从 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-check24rpx 主题色圆 + 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.jsPATHS 新增 starMaterial 五角星路径,24x24 单色);build() 的 Badge white variants 段新增 out.starWhite = _svg(PATHS.star, '#FFFFFF')(解锁态白星,坐在主题色圆盘上)。
  • config.js:第 69 行 80 天勋章 icon/iconWhitediamond/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 三榜并各自 _persistSnapshotSNAPSHOT_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.jsonLaunch 在 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 undefinedday/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 非 bugrecord.date 格式为 YYYY-MM-DD HH:MM:SSformatDate 生成,pages/records/records.js:225 注释确认),_buildFromScanr.date.startsWith('2026-07-28') 能匹配;0 多为测试时(09:30)当天确实无训练记录,month/year 历史累计有 5 条属正常。
  • 验证:node --check 通过。
  • 待办:用户需重新部署 leaderboard 云函数(含最新 _persistSnapshot),再手动触发/进榜下拉刷新,日志应现 persist ok、集合出现 day/month/year 三文档。