Files
wx_pbzc/code-review-report.md
lc 6176f2c341 feat(ui): 记录页月份联动与多项 UI 美化/修复
- records: 底部列表与月份选择器联动,新增 [本月|最近] 分段控件;标题/空态随模式切换
- leaderboard: 领奖台布局(冠军居中+皇冠)、次数标注于底座且三档水平对齐;头像 cloud:// 批量预解析提速(云函数需重新部署)
- index/timer: 主色底图标改白色变体修复隐形;目标数字按微信字体缩放补偿真机遮挡
- settings: 配色方案横滑渐变色卡;头像上传前压缩至 200px
- theme: 新增 teal 主题;暗黑背景统一对齐
- 圆形头像双保险(image 自身 border-radius)修复月/年榜方图

注: cloudfunctions/leaderboard 改动须在开发者工具重新部署才生效。
2026-07-22 09:36:22 +08:00

7.4 KiB
Raw Permalink Blame History

微信小程序代码分析报告(平板支撑训练)

审查范围:app.*pages/*5 页)、utils/*11 模块)、cloudfunctions/*4 个)、custom-tab-barcomponents/*。 整体评价:代码质量较高,日志、容错、云同步去重/合并都做得很到位。以下为发现的问题,按严重程度排列。

修复进度2026-07-21):高优 #1、#2 已修;中优 #4、#5、#6 已修;低优 #7、#9、#10 已修。 #3、#8 经复核为误报,无需修改(见正文)。


🔴 高(High

1. 同一天多次训练会错误跳过计划天数 已修复

  • 位置utils/plan.jsgetPlanDay()
  • 问题
    const trainedDays = new Set(eligibleRecords.map(r => r.date)).size
    
    r.date"YYYY-MM-DD HH:MM:SS"(含时分秒)。同一天训练两次会得到两条不同的字符串, Set 计成 2 天,导致 planDay 被 +2平白跳过一天的进度。计划进度应按"日历天"而非"训练次数"计算。
  • 修复:改用 dateOnly 去重
    const trainedDays = new Set(eligibleRecords.map(r => dateOnly(r.date))).size
    
    dateOnly 已在 utils/util.js 中导出,本文件已 require

2. 系统深色模式切换时,3 个 tab 页不会实时重绘 已修复

  • 位置app.jsdarkMod.watchDarkMode 回调 + pages/index|records|leaderboard/*.js
  • 问题:回调只对有 applyThemeToPage 方法的页面生效:
    pages.forEach(p => { if (p && typeof p.applyThemeToPage === 'function') p.applyThemeToPage() })
    
    index / records / leaderboard 三个页面都没有定义 applyThemeToPage 方法(只有 settings 定义了)。 结果:用户在手机系统里切深色/浅色时,这三页不会立刻变色,要等下次 onShow 才刷新。
  • 修复:在 app.js 的回调里补上兜底分支(与 settings 保持一致),并对自定义 tabBar 调用 updateTheme()
    pages.forEach(p => {
      if (p && typeof p.applyThemeToPage === 'function') p.applyThemeToPage()
      else if (p && p.route) { try { themeMod.applyThemeToPage(p) } catch(e){} }
      try { const tb = p.getTabBar && p.getTabBar(); if (tb && tb.updateTheme) tb.updateTheme() } catch(e){}
    })
    
    (三页 data 中本就有 themeStyle,且 onLoad/onShow 都套用过主题,兜底可直接生效。)

🟠 中(Medium

3. 排行榜数据可伪造(⚠️ 经复核为误报 — 无需修复)

  • 位置cloudfunctions/leaderboard/index.js
  • 复核结论:初审认为"信任客户端上传的 records.duration",但实际查看云函数实现后发现—— 该函数只接收 period / maxRank,duration 是在服务端从用户自己的 doc.records 重新累加出来的 (第 102–132 行遍历 records[monthKey] 求和),并不读取客户端传入的 duration。 因此单个用户最多只能伪造自己那条记录,无法影响他人或全局榜单,属于客户端上报类健身应用的固有信任模型,可接受。
  • 结论无需修改。若未来要做反作弊,可再加单条时长上限 / 频率校验,但属于增强项而非缺陷。

4. admin-dedupe 云函数无鉴权且具破坏性 已修复

  • 位置cloudfunctions/admin-dedupe/index.js
  • 问题:该函数以管理员身份运行(wx-server-sdk 默认绕过安全规则),会批量删除文档 但函数体内没有任何调用者鉴权。一旦部署,任意小程序用户都能调用它删数据。
  • 修复:在 exports.main 入口加 openid 白名单闸门(读环境变量 ADMIN_OPENIDS,逗号分隔):
    const ctx = cloud.getWXContext()
    const caller = (ctx && ctx.OPENID) || ''
    const allowlist = (process.env.ADMIN_OPENIDS || '').split(',').map(s => s.trim()).filter(Boolean)
    if (!caller || allowlist.length === 0 || !allowlist.includes(caller)) {
      return { err: 'forbidden', msg: '无权限执行该操作' }
    }
    
    • 部署要求:必须在云函数环境变量中配置 ADMIN_OPENIDS(你的微信 openid);未配置时函数默认拒绝执行(失败安全)
    • 仍建议用毕即删/禁用该函数,白名单只是兜底闸门。

5. Timer 前台监听只注册不注销(监听器泄漏) 已修复

  • 位置utils/timer.jsstart() 注册 wx.onAppShow,但 stop()/pause()/onUnload 均未 wx.offAppShow
  • 问题:每次进入训练页新建 Timer 都会注册一个 onAppShow,旧的不会被移除。 虽有 if (this._running && !this._paused) 守卫不会出错,但多次训练后会累积监听器(内存泄漏、潜在性能问题)。
  • 修复:抽出 _bindAppShow() / _unbindAppShow(),并在 stop() 中调用 wx.offAppShow(this._onAppShow) 且重置 _appShowBound = false。 页面 onUnload 已调用 this._timer.stop(),故页面销毁时监听一并注销,无遗漏。

6. getTodayTarget 在空计划天数数组时会崩溃 已修复

  • 位置utils/plan.jsgetTodayTarget()
    return day ? day.target : plan.days[0].target
    
  • 问题:若 plan.days 为空(totalDays=0,极端数据损坏场景),plan.days[0]undefined → 访问 .target 抛异常。
  • 修复
    if (!plan.days || plan.days.length === 0) return 0
    

🟡 低(Low

7. darkMode.js 使用了已废弃 API 已修复

  • wx.getSystemInfoSync() 已废弃,真机会打印警告。已改用 wx.getWindowInfo()(同样返回 theme 字段)。

8. ui-modalcustomStyle 属性(⚠️ 误报 — 实际已使用)

  • 初审称"wxml 中未使用",但复核 components/ui-modal/ui-modal.wxml 第 3 行:
    <view class="ui-modal__body" catchtap="onNoop" style="{{customStyle}}">
    
    该属性确实绑定到了 body 的 style,属正常可用的样式覆写入口,不是死属性。无需修改。
  • 注:timer.wxmlcustom-style="margin-right:28rpx;" 挂在 ui-btn 组件上,与 ui-modal 无关,亦无误。

9. onTrainAgainsetData 写入下划线前缀字段 已修复

  • pages/timer/timer.jsthis.setData({ ..., _countUpTask: null })
  • 修复:改为实例字段赋值 this._countUpTask = null(下划线前缀字段不应写进 page data)。

10. index.js 的 planProgress 边界为 NaN 已修复

  • Math.round((Math.min(planDay, plan.totalDays) / plan.totalDays) * 100),当 plan.totalDays 为 0 时得到 NaN
  • 修复
    const planProgress = plan.totalDays > 0 ? Math.round((clampedDay / plan.totalDays) * 100) : 0
    

亮点(值得肯定)

  • 云同步的"按 openid 定位 + 合并去重 + 永不重复 add"逻辑处理得很扎实,避免了旧版"每次冷启动新建文档导致排行翻倍"的坑。
  • 录音/语音、震动提醒、暗色模式、自定义计划编辑器、完成庆祝动画等交互细节都考虑周全,且有完善的降级(云未部署/无密钥时静默回落本地)。
  • custom-tab-bar 单独监听了系统主题变化,避免了切 tab 闪白,处理到位。

建议优先处理顺序(最终)

  1. #1 计划天数统计 bug
  2. #2 深色模式实时刷新
  3. #4 admin 函数鉴权 (高优安全项,已修;部署时记得配 ADMIN_OPENIDS
  4. #5 / #6 / #7 / #9 / #10
  5. #3、#8 经复核为误报,无需处理。