Commit Graph

57 Commits

Author SHA1 Message Date
lc 850f919269 fix(sync): 数据同步合并化 + 排行榜缓存与游标分页
- storage: 新增 mergeRecords,云端记录与本地按 id 去重合并,restore
  不再覆盖,解决训练中冷启动丢记录与多设备记录丢失
- storage: 新增 recomputeStreak,restore 后基于合并记录重算 streak,
  不再信任云端 streak 值
- storage: 新增 _local_changed_at 标记,settings/profile/customPlans
  在本地有未同步改动时保留本地,避免被云端旧值覆盖
- app: _restoreFromCloud 改合并 + 时间戳保护 + streak 重算;Case 2
  合并后回推让两端收敛
- leaderboard: 模块级 60s 缓存 + 游标分页(_id > lastId)替代 skip,
  全表扫描从每次打开降到 60s 内一次

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 09:06:44 +08:00
lc 1d13b49682 fix: 修复头像存储、日历详情数据与弹窗定位问题
- settings: 补上缺失的 util 导入,onChooseAvatar 不再抛
  ReferenceError,头像走 data URI 永久存储主路径
- records: 日历详情改用 dateOnly 比较,修复详情永远显示 0 次/0 时长
- records: 日历详情弹窗从 ui-modal 改为普通 view + fixed,
  修复被底部 dock 遮挡/不居中;补暗黑阴影描边与滚动穿透处理

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 08:46:17 +08:00
lc ac0db6118d feat(ui): 全项目暗黑模式适配 — 加 theme 变量 + 替换全部硬编码颜色
新增主题变量 (light + dark 两套):
- --success-rgb / --danger / --danger-rgb (utils/theme.js)

全部硬编码 #XXX / rgba 颜色统一改主题变量:
- progress-ring 加 trackColor prop,默认 #EEEEEE
  (canvas 不能直接读 CSS var,需要 caller 传字面颜色)
- ui-modal sheet 背景 #FFFFFF → var(--card-bg), 加底部 padding 避开 Dock TabBar
- ui-modal handle #DDD → var(--bg-soft)
- ui-btn --danger 文字 #E53935 → var(--danger), 边框 rgba(229,57,53,...) → rgba(var(--danger-rgb),...)
- calendar-heatmap 三个 #999/#333 文字色 → var(--text-secondary)/var(--text)
- index picker: preset/custom input 背景 #F5F5F5 → var(--bg-soft), label #666 → var(--text-secondary)
- index done-badge 绿色 → var(--success-rgb), pending-text #CCC → var(--text-secondary)
- records empty/hint #CCC/#E0E0E0 → var(--text-secondary), danger 红 → var(--danger)
- settings plan-editor-sheet 背景 #FFFFFF → var(--card-bg), avatar 背景, handle, editor-input, preview 全部跟进
- leaderboard 金/铜背景 #FFF7E6/#FFF1E6 → rgba 半透明 (dark mode 下不刺眼)

Tab bar inactive 图标重新根据 dark mode 切换颜色 (#999999 light / #98989F dark)

leaderboard.json 删除 backgroundColor: #F5F5F5 覆盖,让 theme.json 接管

故意保留:
- 各 .wxss 中渐变按钮上的 #FFFFFF 文字色
- 排行榜金/铜文字色 #FA8C16/#D46B08 (视觉符号约定)
- 各页 data 中 orange theme 默认值 (onShow 后被 themeMod 覆盖)
2026-07-08 15:22:09 +08:00
lc 326bc013fe fix(timer): 完成页圆环覆盖弹窗 + 自定义确认 modal 替代 wx.showModal
训练完成的三个相关 bug:
1. progress-ring 圆环覆盖完成页弹窗 → @tap ring wx:if 让完成态时直接 unmount
2. 完成弹窗内按钮需要点 2 次才能响应 → completion-mask 加 catchtouchmove 拦截 touchmove
3. wx.showModal "结束训练" 系统弹窗在 stacking context 下 hit-test 不稳

根因: .timer-page.page-enter 永久保留 transform: translateY(0),
创建永久 stacking context,影响 native modal 的 WebView 部分 hit-test。

修法:
- 新增自定义 stop-confirm modal (catchtouchmove + 内部 catchtap + bindtap),
  与 ui-modal/settings.plan-editor-mask 同模式,行为可靠
- completion-mask 用相同模式拦截 touchmove
- 顺便修 overtime-badge 暗色 (rgba(var(--success-rgb)))
2026-07-08 15:21:46 +08:00
lc 503f01f6e9 feat: 训练完成全屏弹窗 (大数字+countUp+超时+3按钮)
替换原来不显眼的 wx.showToast:
- 全屏遮罩 + 大圆角卡片,scale-in 弹性入场
- 大数字 180rpx countUp 从 0 滚到实际秒数(1.2s)
- 超时标签 +Xs 秒超时(OvertimePop 弹性出现,延迟 1.1s 等数字到末尾)
- 连续打卡显示 🔥
- 3 按钮:再来一次(留在当前页重置) / 查看记录(跳记录页) / 回到首页
- 装饰 emoji 圆形 1.6s 循环脉冲
- onComplete 触发时自动保存,不需要再手动点结束
2026-07-08 14:19:40 +08:00
lc f052327276 feat: 训练完成后首页 streak 卡片高亮 3 秒
timer 记录保存时写 _last_train_at 时间戳,index onShow 检测
< 10s 的标记则触发 just-completed class,卡片脉冲 2 次 + 主题色
边框 + 阴影,3 秒后自动淡出。让'完成训练'的反馈在首页也可见。
2026-07-08 14:13:50 +08:00
lc c0f7ca612c feat: 切深色模式时高亮/卡片元素加颜色过渡动画
.rank-item / .period-tab / .theme-item / .plan-item 加
transition: background-color 0.3s ease + border-color 0.3s ease。
切深色时不再瞬间切换,有 300ms 流动感。
2026-07-08 14:13:20 +08:00
lc 96d92cfe10 feat: 完成训练分级庆祝 (normal/first/milestone/record)
- normal: 12 条彩带
- record (破个人纪录): 24 条 + 横幅"🏆 新纪录!X秒"
- milestone (连击 7/14/30/50/100/365 天): 32 条 + 横幅
- first (首次训练): 40 条 + 横幅"🎉 第一次训练"

横幅 3.5s 后自动淡出,彩带时长随等级递增(2.4s → 4s)。
2026-07-08 14:13:11 +08:00
lc 9bd4b8c2f4 feat: 记录页首次空状态显示引导卡
之前: totalSessions=0 时,用户看到 stats 显示 0次/0秒 + 空日历 + 空历史,
一整页'什么都没有'。
现在: 完全没记录时只显示一张大引导卡(插画+CTA'开始训练'),
有数据后才显示 3 个统计/日历/历史卡片。
2026-07-08 14:11:56 +08:00
lc 486390917c fix: 拒绝保存未来日期的记录(防止改手机时间污染数据)
用户改手机时间到未来,record.date 就会变成未来时间,
推上云后污染排行榜统计。saveRecord 增加 1 分钟宽容度的
未来时间校验,触发时 toast 提示 + 静默拒绝保存。
2026-07-08 14:11:07 +08:00
lc c41b3e138e fix: 头像转 base64 持久化
WeChat chooseAvatar 返回的 wxfile:// 临时 URL 几天后失效,
头像会变灰。加 util.fileToDataURI 把临时文件读成 data URI
存到本地(限制 512KB,超出走降级方案),从根上解决。
2026-07-08 14:10:25 +08:00
lc ddfcd14252 fix: timer 后台回到前台时立即刷新 UI
WeChat 在后台冻结 setInterval,用户从训练页切走再回来时
看到的还是离开前的画面。注册 wx.onAppShow 监听器,回到
前台立即强制 _tick() 一次,UI 跳到真实 elapsed,半程/30s/10s
等延迟到达的提醒也能补触发。
2026-07-08 14:09:35 +08:00
lc 799d799bb9 fix: 深色模式下高亮/占位元素正确适配
修改:
- theme.js: 新增 --bg-soft 变量(浅色 #F0F0F0, 深色 #2C2C2E)
- settings.wxss: .theme-item.active / .plan-item.active
  改用 rgba(var(--primary-rgb), 0.12) 替代 var(--primary-bg)
- leaderboard.wxss:
  - .period-tabs 背景用 var(--card-bg) 替代 #FFFFFF
  - .rank-num / .rank-avatar 用 var(--bg-soft) 替代硬编码灰
  - .rank-item.is-me / .my-bar 用 rgba() 替代 var(--primary-bg)

根因: --primary-bg 是品牌色极浅版,深色模式下仍是浅色,
不适合做高亮底色。rgba() 在两种模式下都自适应。
2026-07-08 11:52:01 +08:00
lc 4268735053 fix: 深色模式下页面底色不正确
根因: 渐变背景放在 page 元素,渐变变量定义在 page 选择器,
用户手动选深色时 themeStyle 只更新容器 <view> 上的变量,
page 上的渐变变量不变(CSS 变量只能向下继承)。

修复: 渐变移到 .container::before (position: fixed 覆盖视口),
themeStyle 加 --bg-gradient-start/--bg-gradient-end 变量。
2026-07-08 11:49:21 +08:00
lc 5f79367ffa docs: 添加视觉与动效升级验收报告 2026-07-08 11:40:02 +08:00
lc e0e5f6b449 feat: 设置页深色模式 3 档开关 (system/light/dark) 2026-07-08 11:39:32 +08:00
lc 5bbd84868a feat: 空状态插画 (CSS 简笔平板支撑小人 + 主题色气泡) 2026-07-08 11:38:41 +08:00
lc f3b825bdf9 feat: 排行榜行依次滑入动画 (前 10 行 60ms 错开) 2026-07-08 11:38:24 +08:00
lc 477f052cbd feat: 浮动 Dock TabBar 样式 (圆角胶囊 + 阴影 + 深色覆盖) 2026-07-08 11:38:14 +08:00
lc 813c322249 feat: ui-btn 按下涟漪效果 (白色/主题色径向扩散) 2026-07-08 11:38:01 +08:00
lc d192769dbe feat: timer 完成全屏彩带动画 (10 条彩色碎片飘落) 2026-07-08 11:37:25 +08:00
lc 5d810fd058 feat: timer 页面接入 countUp 数字滚动 (load 时 0→duration) 2026-07-08 11:37:13 +08:00
lc c3be8450aa feat: utils/countUp 数字滚动工具 (easeOut cubic) 2026-07-08 11:36:35 +08:00
lc b41960d074 feat: records 页面接入骨架屏 + loading 状态 2026-07-08 11:36:06 +08:00
lc f904870cbc feat: leaderboard 页面接入骨架屏 2026-07-08 11:35:21 +08:00
lc 677a3ff64b feat: 新增 ui-skeleton 骨架屏组件 (card / list-row / circle) 2026-07-08 11:35:16 +08:00
lc 0bba403b00 feat: 页面级入场动画 (fade + translateY) 应用到 5 个页面 2026-07-08 11:33:29 +08:00
lc 05746d8011 feat: 渐变背景 + 渐变变量 (深色 media query 覆盖) 2026-07-08 11:33:18 +08:00
lc c43f1d7389 feat: 深色模式基础设施 (system theme + 跟随系统) 2026-07-08 11:33:09 +08:00
lc a88c4da361 docs: 添加视觉与动效全量升级实施计划 (15 个 task) 2026-07-08 11:29:45 +08:00
lc 53b57e8bd4 docs: 添加视觉与动效全量升级设计 spec
完整方案:深色模式 + 骨架屏 + 浮动 TabBar + 数字滚动
+ 全屏彩带 + 页面级转场 + 排名位移动画 + 空状态插画
零预算,不引第三方包,2 个工作日完成
2026-07-08 11:26:44 +08:00
lc fb435121e4 fix: packOptions忽略编辑器临时文件,防止上传报错
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 13:25:01 +08:00
lc 6af09e4324 fix: 防连点产生重复训练记录 + 补充遗漏的状态重置
- finishTraining 添加 _saving 防重入,避免1.5秒延迟期间连点
- _init 补充 _last30Signaled/_last10Signaled 重置,修复
  多次训练时语音提示丢失的bug

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 13:20:03 +08:00
lc 1e61ab4fba fix: _openid为空时也应允许add()创建云端文档
之前当getOpenid未部署时,_doPush直接return,永远跳过了
db.collection().add(),导致集合无法自动创建、数据不能上云。
改为跳过locate+update但允许add(),CloudBase会自动填充_openid。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 13:13:36 +08:00
lc 9578273906 fix: 移除榜单失败toast,避免无数据时误导用户以为云端故障
日榜无数据是正常业务场景(用户当天未训练),弹toast会
让用户误解为系统故障。本地fallback已提供足够的降级体验。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 12:30:51 +08:00
lc 46738dd7cd fix: 榜单云函数失败时仅无数据才提示toast,避免已有数据时误弹
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 12:29:33 +08:00
lc d817d5d012 fix: 13项数据存储与排行榜修复
致命修复:
- leaderboard: 日榜日期比较改用 dateOnly() 替代严格全等
- plan: 计划天数计算日期比较改用 dateOnly()
- cloud: pullAll 添加 orderBy('updatedAt','desc') 确保取最新文档避免数据丢失

严重修复:
- cloud: 新增 cancelPendingPush() 防止清除数据后被延迟推送复活
- settings: _doClearData 补充 user_profile 清除, 先取消推送再清除
- app: 云端同步改为时间戳比对策略, 支持跨设备拉取
- app: _restoreFromCloud 增加空数据保护和 profile 恢复
- storage: 记录ID改用时间戳+自增计数器确保唯一性

中等修复:
- cloud: init/_fetchOpenid 日志升级, 新增 syncStatus getter
- leaderboard云函数: 添加90天活跃过滤减少全表扫描
- storage: validateStreak 增加lastDate无记录时的通用修复
- leaderboard页面: 云函数失败时Toast提示用户而非静默降级

轻微修复:
- timer: onUnload 添加 voice.destroy() 释放音频上下文
- records: onLongPressDelete 委托给 onDeleteRecord 消除重复代码

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-24 12:22:54 +08:00
liucheng 6d1992869e fix: 修复排行榜数据自增长(冷启动重复创建云端文档)
每次小程序冷启动都会为同一用户创建新的云端文档,
导致 leaderboard 把同一份数据累加 N 次。

改动:
- app.js: onLaunch 改为 async,每次启动先 pullAll 填充 _openid / _docId 缓存
- utils/cloud.js: 引入 _ensureOpenid() 单次解析;_doPush 总是先按 openid
  定位现有文档;update 失败不再回退到 add()(避免创建重复文档)
- utils/storage.js: getTodayRecord 按日期部分比较,修复一天第二次训练
2026-06-21 22:56:51 +08:00
lc 47f718a129 feat: 训练开始时添加语音播报 2026-06-11 11:51:14 +08:00
lc 2e2594d154 feat: 修复清除数据、语音缓存、排行榜限制,新增项目配置文件
- 修复 cloud.clearAll 静默失败导致重启后数据恢复
- 清除数据弹窗改用自定义 ui-modal 替代 wx.showModal
- TTS 语音合成增加 fileID 持久化缓存,重启不再重新合成
- 排行榜限制前15名,maxRank 参数化到 config.js
- 新增 config.js 统一管理版本号/更新日期/开发者/排行榜限制
- progress-ring 消除 getSystemInfoSync 弃用警告
- voice.js 增加 InnerAudioContext 错误监听
- storage/util 完善用户资料、打卡日期精度、记录日志
2026-06-11 11:30:47 +08:00
lc 1d55df72b3 feat: v1.5 — plan editor, voice prompts, UI polish
Major features:
- Training plan editor: edit preset days/duration per plan (in-place
  override via custom_plans storage; preset ids preserved so existing
  records stay valid)
- Voice prompts at 4 fixed points during training (halfway / 30s /
  10s / done) via Tencent Cloud TTS (cloudfunctions/tts + utils/voice.js)
- Free-training button: switched from outline (transparent) to ghost
  variant (theme-tinted) for visual weight

Bug fixes:
- 4 functional pages: SVG data URIs failed to render in WeChat because
  '\#' in colors was parsed as a data-URI fragment delimiter. encode the
  whole SVG via encodeURIComponent in utils/icons.js build().
- Old records (saved before id field was added) could not be deleted
  (data-id was empty, triggered defensive guard). Backfill stable
  legacy-<month>-<index>-<duration> ids in getRecords() and persist.
- settings.js plan-row editor button event was bubbling up to the row's
  bindtap (which also fired onSelectPlan). Wrapped in catch:tap.

UI:
- Settings page: 3 hardcoded plans replaced with dynamic buildPlansList
  that overlays custom_plans on top of presets
- Plan editor: bottom-sheet modal in settings page (regular view, not
  ui-modal — WeChat custom component root element drops position:fixed
  in this runtime)
- Free-training button: rounded pill style, full-width, 24rpx gap from
  primary action; bottom sheet uses max-height: 88vh + internal scroll
- Version bumped to v1.5, last updated 2026-06-10

Removed:
- Daily reminder section (dailyReminder / reminderTime) — replaced by
  the 4 voice prompts which cover the same user need without requiring
  long-term scheduling that WeChat mini-programs can't actually do

Misc:
- utils/plan.js refactored to formula-driven: presets declare
  totalDays/startTarget/increment/cycleDays, days[] is generated. Same
  formula applies to custom plans.
- Timer _remind() guards each prompt on minimum duration so short
  free-mode sessions don't fire 'last30' at the start
- Cloud storage cloud_plans field added to data push payload; restored
  on first install via _restoreFromCloud
- .gitignore added for local AI tool caches (.reasonix/, reasonix.toml,
  .codegraph/daemon.pid)
2026-06-10 17:23:13 +08:00
lc 90e0e64156 fix: correct cloud upsert — query then update/add per user
Before: add-first-then-update caused duplicate docs on each push
because add() always succeeds once collection exists.

Now: query {_openid} first → update if found, add if not.
Each user always has exactly one document in plank_data.

Also removed debug console.log — only production-scale errors remain.
2026-06-04 17:41:29 +08:00
lc 6f33dd54c0 debug: add console logs to cloud.js for diagnostics 2026-06-04 17:37:01 +08:00
lc fa8d8e7b4b fix: seed existing local data to cloud on first launch
Previously cloud sync only triggered on writes. If the user already
had training records before cloud was enabled, they'd never be pushed
— leaving the database empty until the next training session.

Now onLaunch handles both cases:
  - Local empty → restore from cloud (recovery after deletion)
  - Local has data → check cloud; if cloud empty, push existing data up

Also extract _restoreFromCloud to reduce nesting.
2026-06-04 17:29:13 +08:00
lc ec52eb7b0d fix: auto-create cloud collection on first push, remove manual setup
The collection 'plank_data' no longer needs to be created manually.
db.collection().add() auto-creates the collection on first write.
Changed push strategy from 'query-then-add' to 'add-first':
  - add() succeeds → collection created, data stored
  - add() fails (duplicate) → fall back to query + update
  - .get() on non-existent collection now caught gracefully

Also switched updatedAt to db.serverDate() for consistent timestamps.
2026-06-04 17:22:51 +08:00
lc 9b67cc2e22 chore: configure CloudBase env ID cloudbase-d1g56kl2q8f4f7d8a 2026-06-04 17:17:57 +08:00
lc a04c017e37 feat: cloud sync with WeChat CloudBase — data survives app deletion
Problem: All data stored in wx.StorageSync is permanently lost when
user deletes the mini-program or clears cache.

Solution: Sync data to WeChat CloudBase (wx.cloud.database).
- Graceful degradation: if cloud env isn't configured, all local
  storage works exactly as before.
- On app launch: if local records are empty, pull from cloud.
- On writes: push to cloud (debounced 2s) after saveRecord,
  deleteRecord, saveSettings, updateStreak, setTheme.
- Clear data in settings also clears cloud.

Files:
- utils/cloud.js: cloud sync wrapper (init, pullAll, pushAll, clearAll)
- app.js: cloud.init() + restore on fresh install
- utils/storage.js: cloud.pushAll() after every mutation
- utils/theme.js: cloud.pushAll() after setTheme
- pages/settings/settings.js: cloud.clearAll() when clearing data
- project.config.json: cloudfunctionRoot added

Setup required (one-time in WeChat DevTools):
  1. Open 'Cloud Development' panel → enable CloudBase
  2. Create environment → copy env ID
  3. In app.js, change wx.cloud.init() → wx.cloud.init({ env: 'YOUR-ENV-ID' })
  4. Create 'plank_data' collection in cloud database
  5. Set collection permission to 'Only creator can read/write'
2026-06-04 17:13:35 +08:00
lc a5c591eaf3 fix: navigation bar color not updating on per-page basis
Root cause: _setNavBarColor used _lastNavColor (a module-level cache)
to skip wx.setNavigationBarColor when the color appeared unchanged.
But wx.setNavigationBarColor is per-page — each tab page has its own
navigation bar and needs an explicit call. The cache prevented
subsequent pages from ever receiving the call.

Example trace:
  index onShow → _setNavBarColor(#FF6B35) → _lastNavColor=#FF6B35 ✓
  settings onShow → _setNavBarColor(#FF6B35) → cache hit, return ✗
  User picks green → setTheme → _setNavBarColor(#43A047) ✓
  index onShow → _setNavBarColor(#43A047) → cache hit, return ✗
  → Index still shows orange!

Fix: remove _lastNavColor guard entirely. Keep setTimeout debounce.
2026-06-04 17:08:52 +08:00
lc 4338501671 fix: update last updated date to 2026-06-04 2026-06-04 17:04:58 +08:00
lc 6dc7d2da1c chore: bump version to v1.2.0, add last updated date 2026-06-04 17:04:28 +08:00