lc
ed16ac5efd
fix(vibrate): 修复旧用户震动失效 + 新增震动强度选项
...
根因:user_settings 早期无 vibrate 字段,getSettings 未兜底,
_remind 的 !s.vibrate 误判 undefined 为关闭,导致震动静默失效,
需手动操作开关才恢复。
- storage: getSettings 兜底 vibrate/voiceGuide/vibrateIntensity 默认值
- timer: !s.vibrate 改 === false,字段缺失视为开(与 voiceGuide 一致)
- timer: vibrate 按 vibrateIntensity 选 API(light/medium=vibrateShort,
heavy=vibrateLong),fail 回调辅助排查
- settings: 新增"震动强度"三档(短促/标准/强烈),默认强烈
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-09 10:36:14 +08:00
lc
a7ed017267
feat(voice): 语音参数移入 config.js + 设置页男女声切换
...
- config: 新增 tts.female/male 两组参数(voiceType/volume/speed),
直接改数字即可调音色,无需改逻辑代码
- storage: getSettings 兜底 voiceGender(旧用户迁移为女声)
- voice: 新增 _getTtsParams 读 config+性别;缓存 key 含完整参数,
改音色/音量/性别自动重新合成,无需手动清缓存
- settings: 训练偏好加"语音音色"segmented(女声/男声)
- tts 云函数: _synthesize 用传入 opts 合成;_upload 路径含 voiceType
(男/女不同文件);需重新部署
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-09 10:08:09 +08:00
lc
dd9cd1859c
feat(timer): 训练到目标时长不再自动结束,支持超长发挥
...
- Timer: 到时间触发 onGoalReached(提示)而非自动结束,elapsed/overtime
继续累加直到用户手动停止
- timer: onGoalReached 播 goal 语音 + goalReached 状态;finishTraining
补 complete 语音;完成弹窗显示"超长发挥 +X 秒"
- wxml: 超时 badge 训练中也显示;新增 goalReached hint 分支
- voice/tts: 新增 goal 语音 prompt(需部署 tts 云函数)
- config: 版本号 v2.5 -> v2.6
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-09 09:37:32 +08:00
lc
6fffc5aece
fix: 振动定时器清理、完成保存守卫、暗黑冷启动、头像云存储
...
- timer: 振动 setTimeout 入栈追踪,暂停/停止/卸载/重练统一清理,
避免页面销毁后残留震动(_clearVibrateTimers)
- timer: _saving 守卫统一到 _saveAndShowCompletion,自然完成与手动
结束两路径都防重复保存;修复 onTrainAgain 不重置会话状态导致
再练一次后第二次无语音/振动且保存被挡
- app.wxss: @media dark 补全 --text/--card-bg/--bg-soft/--border/
--success/--danger,冷启动即为完整深色,不再背景深卡片白的割裂闪烁
- settings: 头像优先上传云存储存 fileID,三级回退(fileID->base64->
临时URL),pushAll 不再每次传输图片字节
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com >
2026-07-09 09:17:27 +08:00
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