# 2026-07-21 ## 代码审查:平板支撑训练小程序(wx_pbzc) 对微信小程序做了完整 code review,报告见 `/Users/liubleed/Documents/wx_pbzc/code-review-report.md`。 关键问题: - **高#1**:`utils/plan.js getPlanDay()` 用完整时间戳(`r.date`含时分秒)做 `new Set().size` 统计训练天数,同一天多次训练会多算天数、错误跳过计划进度。应改用 `dateOnly(r.date)`。 - **高#2**:系统深色模式切换时 index/records/leaderboard 三页不实时重绘——它们没定义 `applyThemeToPage` 方法,而 app.js `watchDarkMode` 回调只在有该方法时才调用。只有 settings 页有该方法。修复:回调补兜底或给三页加方法。 - **中**:排行榜完全信任客户端上传的 records.duration,可伪造;`admin-dedupe` 云函数无鉴权且具破坏性。 - **低**:Timer 的 `wx.onAppShow` 只注册不注销;`ui-modal` 的 customStyle 是死属性;`getTodayTarget` 空 days 会崩溃;darkMode.js 用了废弃的 getSystemInfoSync。 代码整体质量高(云同步去重/合并、降级处理都到位)。 ## 修复记录(2026-07-21,两轮) **第一轮(高优)** - `utils/plan.js getPlanDay()`:改用 `dateOnly(r.date)` 去重,修同一天多次训练错误跳计划天数。 - `app.js watchDarkMode` 回调:补兜底,对无 `applyThemeToPage` 方法的页面直接调 `themeMod.applyThemeToPage(p)`,并通知自定义 tabBar `updateTheme()`,修系统深/浅色切换时三主页面不实时刷新。 **第二轮("接着修")** - `cloudfunctions/admin-dedupe/index.js`:入口加 openid 白名单闸门(读环境变量 `ADMIN_OPENIDS`,未配置默认拒绝=失败安全),防任意用户调用批量删文档。部署时须配置该变量。 - `utils/timer.js`:抽 `_bindAppShow/_unbindAppShow`,在 `stop()` 中 `wx.offAppShow`,修每次进训练页累积 onAppShow 监听的内存泄漏。 - `utils/plan.js getTodayTarget`:空 days 数组返回 0,防 `plan.days[0].target` 崩溃。 - `utils/darkMode.js`:`wx.getSystemInfoSync()` 废弃,改 `wx.getWindowInfo()`。 - `pages/timer/timer.js onTrainAgain`:`_countUpTask` 改为实例字段赋值,不再写进 setData。 - `pages/index/index.js refresh()`:`planProgress` 在 totalDays=0 返回 0,防 NaN。 - 复核纠正两条误报(已写进报告):#3 排行榜 duration 实为服务端从 doc.records 重算,不读客户端,无需修;#8 ui-modal 的 customStyle 在 wxml 第3行已被 `style="{{customStyle}}"` 使用,非死属性。 - 全部 6 文件 `node --check` 通过;报告 `code-review-report.md` 已标修复状态并纠正误报。 ## UI 美化第一轮(2026-07-21) 基于 UI 分析(色彩层级单薄/排版无标尺/进度环太素/深色不一致),按约定先做见效最快的三项: 1. **进度环**(`components/progress-ring/progress-ring.js`):canvas 分段绘制锥形渐变弧(primary→primaryLight,每段 0.06rad、上限 120 段),末端加 `shadowBlur` 发光圆点+白色内核,起点圆头补圆;`primaryLightColor` 加入 `_draw` observer;环宽 timer 页 16→22rpx,时间字 80→88rpx/800 字重;呼吸环从描边圆改 radial-gradient 弥散光晕(`timer.wxss`)。 2. **首页**:`ui-card` 新增 `gradient` 变体(主色渐变底,局部重定义 `--text/--text-secondary/--border` 为白色系,后代自动反白);连续打卡卡用 `variant="gradient in"`,卡内 streak 数字/进度条/光点显式改白;今日目标环 200→260rpx、时间 64→112rpx、加外圈光晕。 3. **背景分层+深色对齐**:`theme.js getThemeStyle` 浅色背景渐变起点改用各主题 `primaryBg`(背景随主题变色),深色统一为 `#131316` 系(bg/card `#26262B`/soft `#343439`);`applyThemeToPage/applyWindowBg` 窗口背景同步;`app.wxss` 冷启动变量、`theme.json`、timer `trackColor`(深色 `#3A3A40`)、`ui-skeleton`、`custom-tab-bar`、`records.wxss` 注释全部对齐新色值;`ui-card` 深色下加深阴影+微亮描边防"卡片沉底"。 验证:`node --check` 通过;全库 grep 旧深色值 `#0F0F12/#2C2C2E/#1C1C1E` 仅剩 docs/ 历史文档(无需改)。 待办(用户确认后做):排行榜领奖台+period tab 改分段控件、Emoji→SVG 统一、排版/圆角标尺收敛、设置页主题色卡横滑、微交互(数字滚动/按钮回弹)。 ## UI 美化第二轮(2026-07-21) 4. **排行榜**:period-tabs(白底下划线)→ `period-seg` 胶囊分段控件(呼应设置页 .segmented,sticky 保留);≥3 人时前三名上**领奖台**(冠军居中放大头像 128rpx+皇冠图标+渐变金牌,2/3 名银铜奖牌,阶梯底座,跳过列表前三);`my-bar` 从半透明描边→主色渐变实色卡+白字+主色投影;rank-item 的 is-me 判空保护(myEntry 可为 null)。 5. **Emoji→SVG**:`utils/icons.js` 新增 trophy/party/sparkle(+Fill)及白色语义变体 `trophyWhite/partyWhite/hotWhite/sparkleWhite/checkWhite`;timer 完成弹窗 emoji 文本→SVG 图标(.completion-icon 80rpx),庆祝粒子 emoji→sparkle/crown/like 图片(.spark 改 44rpx 图片),streak 🔥→hotFill 图片;成就横幅文案去掉内嵌 emoji(渐变横幅自身够视觉)。排行榜奖牌 emoji 已在任务4随领奖台移除。 6. **设置页**:配色方案从纵向列表→**横向渐变色卡**(3 列,primary→primaryLight 渐变 swatch + 白色对勾,选中态放大 1.04+主色外描边,名称高亮);深色模式选择保留原行式(.theme-item 样式保留);计划选中项加左侧 6rpx 主色渐变竖条。 校验:全部改动文件 node --check 通过;grep 确认无残留界面 emoji(仅 settings 关于页 ❤️ 与文档,保留)。 ## UI 对比度修复(2026-07-21) 修首页渐变 hero 卡(训练首页顶部"连续打卡")数字/文案看不清: - **根因**:小程序组件默认 `style isolation`,`.ui-card.gradient ` 跨作用域选择器全部失效;hero 卡内 `.streak-num`/`.progress-fill` 用 `var(--primary)`(橙)画在橙色渐变底上=隐形。 - **修复**:`ui-card` 渐变变体新增 `--on-primary:#FFFFFF`(自定义属性能跨隔离边界继承进 slot);`.streak-num` 改 `var(--text)`(渐变卡继承为白,普通卡为深);`.progress-fill`/`.progress-glow` 改 `var(--on-primary, <原渐变/主色>)`;`--text-secondary` 渐变卡内由 0.75 提到 0.85 提升小标签可读性;删除 index.wxss 里 4 条失效的 `.ui-card.gradient ...` 页面选择器。 - 全局扫描确认其余彩色/渐变底(按钮/横幅/完成弹窗/my-bar/主题色卡/奖牌/列表时长)均显式白字或位于浅色底,无同类问题。 ## UI 对比度审计(2026-07-21 下午) 用户反馈:训练首页(index)顶部"连续打卡"数字与渐变卡底几乎同色、看不清。 根因(关键项目陷阱):`ui-card` 自定义组件默认 style isolation(isolated),`variant="gradient in"` 产生的 `gradient`/`in` 类在**组件作用域**内,而卡内 slot 内容(`.streak-num` 等)的样式写在**页面作用域**。因此页面里的 `.ui-card.gradient .streak-num{color:#FFF}` 这类跨作用域后代选择器**全部失效**(运行时症状即证明)。唯一能跨边界生效的是 **CSS 自定义属性**——`.ui-card.gradient` 重定义的 `--text/--text-secondary/--border` 会向下继承进 slot,所以标签等能反白,但 `.streak-num` 用的 `var(--primary)` 没被重定义 → 仍是橙色画在橙色渐变上 = 看不见。 同类受损(同一机制):index 渐变 hero 卡内 `.progress-fill`/`.progress-glow` 用 `var(--primary)` 渐变,覆盖选择器同样失效 → 进度条橙色填充在橙色卡上不可见(细,用户未提但已确认)。 已确认安全(渐变/主色底 + 文字全部显式 `#FFFFFF`):ui-btn--primary、achievement-banner、completion-emoji-wrap(白色 SVG)、completion-btn--primary、stop-confirm-btn--primary、leaderboard my-bar(全显式白)、editor-action-save、records first-train-cta、settings 主题色卡(白对勾 + 名称在卡底)、rank-dur/podium-dur--first(主色字放在白卡/页面底)、target-time(主色字放在 0.10 浅色环)。 修复原则(待用户确认):渐变/主色容器内文字只用 `var(--text)/var(--text-secondary)`(随卡反白),绝不用 `var(--primary)`;进度填充改 `var(--on-primary, <原渐变>)` 让容器可覆盖为白。删掉那些失效的 `.ui-card.gradient ...` 页面选择器。 ## UI 图标对比度修复(2026-07-21) 用户反馈:index 页几个图标颜色与背景基本一样、看不清(同 gradient hero 卡问题延续)。 根因:图标是 SVG data URI,**颜色烤死在 URI 里**(icons.js `build()` 中 `*Fill` 变体统一用 `primary` 主题色绘制),CSS 的 `color`/变量均无法改它。index 渐变 hero 卡上的两个图标因此仍是橙色,画在橙色渐变底上=隐形: - `.streak-icon` 用 `icons.hotFill`(橙)on 渐变卡 - `.plan-badge-icon` 用 `icons.formFill`(橙)on 渐变卡 修复: - `utils/icons.js` 新增 `formWhite`(白填色,与已有 `hotWhite` 并列,注释说明渐变卡必须用白填色)。 - `pages/index/index.wxml`:`.streak-icon` → `icons.hotWhite`、`.plan-badge-icon` → `icons.formWhite`(白色图标在橙卡上清晰)。 - 校验 grep:index 页仅此两处用主色 Fill 变量,均已改;timer/settings/records 页的 hotFill/formFill 在深色/白卡背景,对比正常,不在本次范围。 结论:index 首页其余图标(targetFill 在白卡、time 灰在白卡、lightFill 在浅 soft 卡、successFill 在绿徽章)对比均正常。精力/问候图标(emojiFill 橙)在页面浅色底,橙主题下略低对比但可见,未改。 ## UI 图标尺寸 + 主按钮图标对比度 + 目标数字溢出(2026-07-21) 用户反馈:1) index「开始训练」前的图标太小也看不清;2) 今日目标数字太大超出圆环。并要求排查项目类似小图标。 排查(全项目 `icon-src` 审计):主按钮用 `playFill`(橙) 放在橙渐变背景上=隐形且 36rpx 太小,同病根的有 3 处——index 开始训练、timer 开始、timer 继续。ghost(浅底)/outline(灰底)/delete(灰) 按钮图标对比正常。 修复: - `utils/icons.js` 新增 `playWhite`(白色播放图标,与 hotWhite/formWhite 并列;注释强调主按钮必须白图标)。 - 3 个主按钮 `icon-src`:`playFill`→`playWhite`(index.wxml:73、timer.wxml:90、timer.wxml:109)。 - `components/ui-btn/ui-btn.wxss`:`.ui-btn__icon` 尺寸 `1em`→`1.3em`(xl 按钮 36→约 47rpx,随按钮字号自适应放大)。 - `pages/index/index.wxss`:`.target-ring` 260→280rpx(box-shadow 16→14rpx 收外扩、加 overflow:hidden);`.target-time` 112→84rpx(letter-spacing -1rpx、max-width:240rpx、text-align:center、overflow:hidden),3 位数不再溢出环。 - 校验 node --check 通过;grep 确认全项目无残留 `playFill`。 - 项目 MEMORY.md 补充"图标烤色无法被 CSS 改 + 主按钮须用 *White 变体"陷阱。 ## 配色方案 +1(2026-07-21) 用户反馈:设置页配色方案只有 5 个、3 列网格下不对称(2 行满+1 孤)。要求再加一个。 新增第 6 个主题 `teal`(湖青):`utils/theme.js` 的 THEMES 末尾追加 - id:'teal', name:'湖青', primary:'#00BCD4', primaryLight:'#4DD0E1', primaryBg:'#E0F7FA', primaryRgb:'0,188,212' 选青/湖蓝原因:区别于已有蓝/绿,且避开红色(项目 --danger 用红,主色不宜红)。 效果:设置页色卡变 6 个=2 行×3 列,对称。theme.js 单源、settings.js 直接读 themeMod.THEMES,无数量硬限制(grep 仅 cloudfunctions/admin-dedupe 有 slice(0,5),无关)。node --check 通过。 ## 排行榜头像不显示 - 修复(2026-07-21) 根因:排行榜头像写死 `icons.peopleFill`(统一占位人像)。App 其实支持头像(settings 页 chooseAvatar→上传云存储 fileID→存 profile.avatarUrl,cloud.js:146 同步整份 profile 到云端),但 `cloudfunctions/leaderboard/index.js` 只读了 `profile.nickname`、返回的 ranked/myEntry 没有 avatarUrl 字段 → 前端无真头像可用。 修复: - `cloudfunctions/leaderboard/index.js`:userMap.set、ranked map、myEntry 三处均加 `avatarUrl: profile.avatarUrl || ''`(后者用 item.avatarUrl)。 - `pages/leaderboard/leaderboard.wxml`:领奖台 3 处 `rankedList[0/1/2].avatarUrl || icons.peopleFill`、列表项 `item.avatarUrl || icons.peopleFill`、my-bar `myEntry.avatarUrl || icons.peopleFill`(my-bar 原无头像,补上)。共 5 处。 - `leaderboard.wxss`:my-bar 头像 `.my-bar__avatar` opacity:1 + 白底白边(渐变条上占位图更协调)。 - `leaderboard.js._applyResult` 用 `...item` 透传,avatarUrl 自动带入,无需改。 - 校验:云函数 node --check 通过;grep 确认 5 处头像均改、占位图保留为兜底。 - ⚠️ 需重新部署 leaderboard 云函数(`wx cloud deploy` 或开发者工具上传)后头像才会在真机/线上生效;本地仅前端改动预览看不到他人头像(本地 fallback 也只取 profile.nickname)。 ## 排行榜头像方形未裁圆(2026-07-21) 用户验收:头像已显示,但是方形图套在圆框里(border-radius:50% 加在 上,部分微信基础库不裁切图片内容)。 修复(微信可靠做法:圆角 包裹 让父层裁切): - `leaderboard.wxss`:删除 `.podium-avatar`/`.podium-avatar--first`/`.rank-avatar`,新增统一 `.avatar`(圆角+overflow:hidden+背景)、`.avatar__img`(100%填充 block)、`.avatar--sm/md/lg` 三档尺寸;`.podium-avatar-wrap` 去掉 margin-bottom(改由 avatar--md/lg 自带)。 - `leaderboard.wxml`:5 处头像 `` 改为 ``(md/lg 用于领奖台 2/3 名与冠军放大;sm 用于列表项与 my-bar)。 - `.my-bar__avatar` 覆盖(opacity:1+白底白边)仍作用于新元素。 - 校验 grep:无悬空旧类 `podium-avatar`/`rank-avatar`(仅 `.podium-avatar-wrap` 定位容器保留);5 处均改新结构。 - 此坑记入 MEMORY.md(微信圆形头像裁剪陷阱)。 ## 月榜/年榜头像仍方(仅日榜圆)二次修复(2026-07-21) 用户验收:日榜正常,月榜/年榜头像还是方的。 根因确认:三档周期共用同一 `.avatar` 结构(WXML/WXSS 完全一致),差异只在图片内容—— - 日榜其他用户多未设头像 → 显示 `peopleFill` 占位 SVG → 被外层 view 的 overflow:hidden 裁圆; - 月榜/年榜出现真实照片头像(cloud://)→ 该基础库下,外层 view 的 overflow:hidden **裁不掉远程照片的原生图片层**,照片露方。 修复:给 `.avatar__img` 自身补 `border-radius:50%`(双保险,对齐设置页 `.avatar-btn`+`.avatar-img` 的实测可用写法),与父层 overflow:hidden 并存。 - 设置页 `settings.wxml:10` 的"我的头像"正是此写法(button overflow:hidden + image border-radius:50%)且用户未投诉,证明该组合在用户机器上可靠。 - 已更新 MEMORY.md 圆形头像陷阱条目为更精确结论:必须 image 自身 border-radius,不能只靠父层 overflow。 ## 月/年榜头像"大圆套小圆"缩小问题(2026-07-21) 用户验收:圆形有了,但真实照片偏小,像大圆里套小圆,不像设置页头像。 根因:`.avatar--md/lg/sm` 自带 `padding:20/26/0 rpx` 留白——占位 SVG 是"图标居中+外圈"(OK),但真实照片被这圈留白缩成小图 → 大圆套小圆。 修复(对齐设置页"有头像就填满"的写法): - `leaderboard.wxml` 5 处头像加条件 `has-avatar` 类(`{{x.avatarUrl ? 'has-avatar' : ''}}`),且 `` 的 `mode` 改为 `{{x.avatarUrl ? 'aspectFill' : 'aspectFit'}}`(真实照片填满圆、占位图仍 fit 居中)。 - `leaderboard.wxss` 新增 `.avatar.has-avatar { padding: 0; opacity: 1; }`(双类选择器特异性高于 `.avatar--md/lg/sm`,覆盖留白与淡透明度),照片填满圆且不发虚。 - 未设头像的占位图仍保留 padding 环(设计预期),仅真实照片受影响。 - 仅前端改动,结构与设置页完全一致。