Files
2026-07-29 11:05:03 +08:00

16 KiB
Raw Permalink Blame History

2026-07-21

代码审查:平板支撑训练小程序(wx_pbzc)

对微信小程序做了完整 code review,报告见 /Users/liubleed/Documents/wx_pbzc/code-review-report.md

关键问题:

  • 高#1utils/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.jswx.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-skeletoncustom-tab-barrecords.wxss 注释全部对齐新色值;ui-card 深色下加深阴影+微亮描边防"卡片沉底"。

验证:node --check 通过;全库 grep 旧深色值 #0F0F12/#2C2C2E/#1C1C1E 仅剩 docs/ 历史文档(无需改)。

待办(用户确认后做):排行榜领奖台+period tab 改分段控件、Emoji→SVG 统一、排版/圆角标尺收敛、设置页主题色卡横滑、微交互(数字滚动/按钮回弹)。

UI 美化第二轮(2026-07-21

  1. 排行榜period-tabs(白底下划线)→ period-seg 胶囊分段控件(呼应设置页 .segmented,sticky 保留);≥3 人时前三名上领奖台(冠军居中放大头像 128rpx+皇冠图标+渐变金牌,2/3 名银铜奖牌,阶梯底座,跳过列表前三);my-bar 从半透明描边→主色渐变实色卡+白字+主色投影;rank-item 的 is-me 判空保护(myEntry 可为 null)。
  2. Emoji→SVGutils/icons.js 新增 trophy/party/sparkle+Fill)及白色语义变体 trophyWhite/partyWhite/hotWhite/sparkleWhite/checkWhitetimer 完成弹窗 emoji 文本→SVG 图标(.completion-icon 80rpx),庆祝粒子 emoji→sparkle/crown/like 图片(.spark 改 44rpx 图片),streak 🔥→hotFill 图片;成就横幅文案去掉内嵌 emoji(渐变横幅自身够视觉)。排行榜奖牌 emoji 已在任务4随领奖台移除。
  3. 设置页:配色方案从纵向列表→横向渐变色卡3 列,primary→primaryLight 渐变 swatch + 白色对勾,选中态放大 1.04+主色外描边,名称高亮);深色模式选择保留原行式(.theme-item 样式保留);计划选中项加左侧 6rpx 主色渐变竖条。 校验:全部改动文件 node --check 通过;grep 确认无残留界面 emoji(仅 settings 关于页 ❤️ 与文档,保留)。

UI 对比度修复(2026-07-21

修首页渐变 hero 卡(训练首页顶部"连续打卡")数字/文案看不清:

  • 根因:小程序组件默认 style isolation.ui-card.gradient <slot后代> 跨作用域选择器全部失效;hero 卡内 .streak-num/.progress-fillvar(--primary)(橙)画在橙色渐变底上=隐形。
  • 修复ui-card 渐变变体新增 --on-primary:#FFFFFF(自定义属性能跨隔离边界继承进 slot);.streak-numvar(--text)(渐变卡继承为白,普通卡为深);.progress-fill/.progress-glowvar(--on-primary, <原渐变/主色>)--text-secondary 渐变卡内由 0.75 提到 0.85 提升小标签可读性;删除 index.wxss 里 4 条失效的 .ui-card.gradient ... 页面选择器。
  • 全局扫描确认其余彩色/渐变底(按钮/横幅/完成弹窗/my-bar/主题色卡/奖牌/列表时长)均显式白字或位于浅色底,无同类问题。

UI 对比度审计(2026-07-21 下午)

用户反馈:训练首页(index)顶部"连续打卡"数字与渐变卡底几乎同色、看不清。

根因(关键项目陷阱):ui-card 自定义组件默认 style isolationisolated),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-glowvar(--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-iconicons.hotFill(橙)on 渐变卡
  • .plan-badge-iconicons.formFill(橙)on 渐变卡

修复:

  • utils/icons.js 新增 formWhite(白填色,与已有 hotWhite 并列,注释说明渐变卡必须用白填色)。
  • pages/index/index.wxml.streak-iconicons.hotWhite.plan-badge-iconicons.formWhite(白色图标在橙卡上清晰)。
  • 校验 grepindex 页仅此两处用主色 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-srcplayFillplayWhiteindex.wxml:73、timer.wxml:90、timer.wxml:109)。
  • components/ui-btn/ui-btn.wxss.ui-btn__icon 尺寸 1em1.3emxl 按钮 36→约 47rpx,随按钮字号自适应放大)。
  • pages/index/index.wxss.target-ring 260→280rpxbox-shadow 16→14rpx 收外扩、加 overflow:hidden);.target-time 112→84rpxletter-spacing -1rpx、max-width:240rpx、text-align:center、overflow:hidden),3 位数不再溢出环。
  • 校验 node --check 通过;grep 确认全项目无残留 playFill
  • 项目 MEMORY.md 补充"图标烤色无法被 CSS 改 + 主按钮须用 *White 变体"陷阱。

配色方案 +12026-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.avatarUrlcloud.js:146 同步整份 profile 到云端),但 cloudfunctions/leaderboard/index.js 只读了 profile.nickname、返回的 ranked/myEntry 没有 avatarUrl 字段 → 前端无真头像可用。

修复:

  • cloudfunctions/leaderboard/index.jsuserMap.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.wxssmy-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__img100%填充 block)、.avatar--sm/md/lg 三档尺寸;.podium-avatar-wrap 去掉 margin-bottom(改由 avatar--md/lg 自带)。
  • leaderboard.wxml5 处头像 <image> 改为 <view class="avatar avatar--sm/md/lg [my-bar__avatar]"><image class="avatar__img" .../></view>(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' : ''}}),且 <image>mode 改为 {{x.avatarUrl ? 'aspectFill' : 'aspectFit'}}(真实照片填满圆、占位图仍 fit 居中)。
  • leaderboard.wxss 新增 .avatar.has-avatar { padding: 0; opacity: 1; }(双类选择器特异性高于 .avatar--md/lg/sm,覆盖留白与淡透明度),照片填满圆且不发虚。
  • 未设头像的占位图仍保留 padding 环(设计预期),仅真实照片受影响。
  • 仅前端改动,结构与设置页完全一致。