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

132 lines
7.4 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.
# 微信小程序代码分析报告(平板支撑训练)
审查范围:`app.*``pages/*`5 页)、`utils/*`11 模块)、`cloudfunctions/*`4 个)、`custom-tab-bar``components/*`
整体评价:代码质量较高,日志、容错、云同步去重/合并都做得很到位。以下为发现的问题,按严重程度排列。
> **修复进度**2026-07-21):高优 #1、#2 已修;中优 #4、#5、#6 已修;低优 #7、#9、#10 已修。
> #3、#8 经复核为**误报**,无需修改(见正文)。
---
## 🔴 高(High
### 1. 同一天多次训练会错误跳过计划天数 ✅ 已修复
- **位置**`utils/plan.js``getPlanDay()`
- **问题**
```js
const trainedDays = new Set(eligibleRecords.map(r => r.date)).size
```
`r.date` 是 `"YYYY-MM-DD HH:MM:SS"`(含时分秒)。同一天训练两次会得到两条不同的字符串,
`Set` 计成 2 天,导致 `planDay` 被 +2,**平白跳过一天的进度**。计划进度应按"日历天"而非"训练次数"计算。
- **修复**:改用 `dateOnly` 去重
```js
const trainedDays = new Set(eligibleRecords.map(r => dateOnly(r.date))).size
```
`dateOnly` 已在 `utils/util.js` 中导出,本文件已 `require`
### 2. 系统深色模式切换时,3 个 tab 页不会实时重绘 ✅ 已修复
- **位置**`app.js` 的 `darkMod.watchDarkMode` 回调 + `pages/index|records|leaderboard/*.js`
- **问题**:回调只对有 `applyThemeToPage` 方法的页面生效:
```js
pages.forEach(p => { if (p && typeof p.applyThemeToPage === 'function') p.applyThemeToPage() })
```
但 **index / records / leaderboard 三个页面都没有定义 `applyThemeToPage` 方法**(只有 `settings` 定义了)。
结果:用户在手机系统里切深色/浅色时,这三页不会立刻变色,要等下次 `onShow` 才刷新。
- **修复**:在 app.js 的回调里补上兜底分支(与 settings 保持一致),并对自定义 tabBar 调用 `updateTheme()`
```js
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` 重新累加出来的
(第 102132 行遍历 `records[monthKey]` 求和),并不读取客户端传入的 `duration`。
因此单个用户最多只能伪造自己那条记录,无法影响他人或全局榜单,属于客户端上报类健身应用的固有信任模型,可接受。
- **结论**:**无需修改**。若未来要做反作弊,可再加单条时长上限 / 频率校验,但属于增强项而非缺陷。
### 4. `admin-dedupe` 云函数无鉴权且具破坏性 ✅ 已修复
- **位置**`cloudfunctions/admin-dedupe/index.js`
- **问题**:该函数以管理员身份运行(wx-server-sdk 默认绕过安全规则),会**批量删除文档**,
但函数体内**没有任何调用者鉴权**。一旦部署,任意小程序用户都能调用它删数据。
- **修复**:在 `exports.main` 入口加 openid 白名单闸门(读环境变量 `ADMIN_OPENIDS`,逗号分隔):
```js
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.js` → `start()` 注册 `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.js` → `getTodayTarget()`
```js
return day ? day.target : plan.days[0].target
```
- **问题**:若 `plan.days` 为空(totalDays=0,极端数据损坏场景),`plan.days[0]` 为 `undefined` → 访问 `.target` 抛异常。
- **修复**
```js
if (!plan.days || plan.days.length === 0) return 0
```
---
## 🟡 低(Low
### 7. `darkMode.js` 使用了已废弃 API ✅ 已修复
- `wx.getSystemInfoSync()` 已废弃,真机会打印警告。已改用 `wx.getWindowInfo()`(同样返回 `theme` 字段)。
### 8. `ui-modal` 的 `customStyle` 属性(⚠️ 误报 — 实际已使用)
- 初审称"wxml 中未使用",但复核 `components/ui-modal/ui-modal.wxml` 第 3 行:
```xml
<view class="ui-modal__body" catchtap="onNoop" style="{{customStyle}}">
```
该属性确实绑定到了 body 的 `style`,属正常可用的样式覆写入口,**不是死属性**。无需修改。
- 注:`timer.wxml` 里 `custom-style="margin-right:28rpx;"` 挂在 `ui-btn` 组件上,与 `ui-modal` 无关,亦无误。
### 9. `onTrainAgain` 用 `setData` 写入下划线前缀字段 ✅ 已修复
- `pages/timer/timer.js``this.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`。
- **修复**
```js
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 经复核为误报,无需处理。