6176f2c341
- records: 底部列表与月份选择器联动,新增 [本月|最近] 分段控件;标题/空态随模式切换 - leaderboard: 领奖台布局(冠军居中+皇冠)、次数标注于底座且三档水平对齐;头像 cloud:// 批量预解析提速(云函数需重新部署) - index/timer: 主色底图标改白色变体修复隐形;目标数字按微信字体缩放补偿真机遮挡 - settings: 配色方案横滑渐变色卡;头像上传前压缩至 200px - theme: 新增 teal 主题;暗黑背景统一对齐 - 圆形头像双保险(image 自身 border-radius)修复月/年榜方图 注: cloudfunctions/leaderboard 改动须在开发者工具重新部署才生效。
7.4 KiB
7.4 KiB
微信小程序代码分析报告(平板支撑训练)
审查范围: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() - 问题:
const trainedDays = new Set(eligibleRecords.map(r => r.date)).sizer.date是"YYYY-MM-DD HH:MM:SS"(含时分秒)。同一天训练两次会得到两条不同的字符串,Set计成 2 天,导致planDay被 +2,平白跳过一天的进度。计划进度应按"日历天"而非"训练次数"计算。 - 修复:改用
dateOnly去重(const trainedDays = new Set(eligibleRecords.map(r => dateOnly(r.date))).sizedateOnly已在utils/util.js中导出,本文件已require)
2. 系统深色模式切换时,3 个 tab 页不会实时重绘 ✅ 已修复
- 位置:
app.js的darkMod.watchDarkMode回调 +pages/index|records|leaderboard/*.js - 问题:回调只对有
applyThemeToPage方法的页面生效:但 index / records / leaderboard 三个页面都没有定义pages.forEach(p => { if (p && typeof p.applyThemeToPage === 'function') p.applyThemeToPage() })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.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()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-modal 的 customStyle 属性(⚠️ 误报 — 实际已使用)
- 初审称"wxml 中未使用",但复核
components/ui-modal/ui-modal.wxml第 3 行:该属性确实绑定到了 body 的<view class="ui-modal__body" catchtap="onNoop" style="{{customStyle}}">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。- 修复:
const planProgress = plan.totalDays > 0 ? Math.round((clampedDay / plan.totalDays) * 100) : 0
✅ 亮点(值得肯定)
- 云同步的"按 openid 定位 + 合并去重 + 永不重复 add"逻辑处理得很扎实,避免了旧版"每次冷启动新建文档导致排行翻倍"的坑。
- 录音/语音、震动提醒、暗色模式、自定义计划编辑器、完成庆祝动画等交互细节都考虑周全,且有完善的降级(云未部署/无密钥时静默回落本地)。
custom-tab-bar单独监听了系统主题变化,避免了切 tab 闪白,处理到位。
建议优先处理顺序(最终)
#1 计划天数统计 bug✅#2 深色模式实时刷新✅#4 admin 函数鉴权✅(高优安全项,已修;部署时记得配ADMIN_OPENIDS)#5 / #6 / #7 / #9 / #10✅- #3、#8 经复核为误报,无需处理。