feat(timer): 训练时长低于阈值不记录并明确提示

- config.js 新增 minRecordSeconds: 20 阈值配置,改配置即可调整门槛
- timer.js: finishTraining/_onCircuitComplete 低时长不播完成语音;
  _saveAndShowCompletion 兜底判断,任何入口都不会把短时训练落库;
  新增 _showNotRecorded: 不保存记录、不更新连胜,复用完成弹窗展示实际秒数
- timer.wxml/wxss: 完成弹窗新增"未计入记录"提示行(门槛说明),
  未记录时隐藏"查看记录/分享"按钮,只留"再来一次+回到首页"
- 修复状态残留: 未记录后点"再来一次",下次正常完成不会误显示"未计入记录"
This commit is contained in:
2026-08-12 10:20:40 +08:00
parent 178a51d261
commit f1ad3eeb95
5 changed files with 152 additions and 3 deletions
+64 -2
View File
@@ -40,6 +40,10 @@ Page({
// 组进度改由 progress-ring 外圈分段弧呈现,仅需传 sets/currentSet/resting,
// 页面不再维护点阵渲染数据(circuitDots/dotsDone 等已随旧面板一并删除)。
celebrationLevel: 'normal', // normal / first / milestone / record
// 训练时长低于 config.minRecordSeconds 时置 true:完成弹窗提示"未计入记录",
// 隐藏查看记录/分享入口;弹窗内展示门槛值给用户明确反馈
completionNotRecorded: false,
minRecordSeconds: 20,
celebrationMessage: '',
confettiPieces: [],
// Completion modal (full-screen summary after training ends)
@@ -435,7 +439,9 @@ Page({
_onCircuitComplete() {
this._clearVibrateTimers()
const s = storage.getSettings()
if (s.voiceGuide !== false) voice.play('complete')
// 有效撑持秒数未达最低记录时长时不播完成语音(弹窗走"未计入记录"分支)
const minSec = config.minRecordSeconds || 20
if (this.data.workTotal >= minSec && s.voiceGuide !== false) voice.play('complete')
this._saveAndShowCompletion(this.data.workTotal)
},
@@ -725,8 +731,10 @@ Page({
return
}
// Completion voice (replaces the old auto-onComplete cue). No vibration.
// 未达最低记录时长时不播完成语音——弹窗会走"未计入记录"分支,不庆祝。
const s = storage.getSettings()
if (s.voiceGuide !== false) {
const minSec = config.minRecordSeconds || 20
if (elapsed >= minSec && s.voiceGuide !== false) {
voice.play('complete')
}
this._saveAndShowCompletion(elapsed)
@@ -759,6 +767,15 @@ Page({
// Re-entry guard: protects the finishTraining path so the save never
// runs twice even if the user double-taps end.
if (this._saving) return
// 未达 config.minRecordSeconds:不写入记录/连胜,弹"未计入记录"提示。
// 兜底判断——手动结束(finishTraining)与循环自然完成(_onCircuitComplete)
// 两条路径最终都汇聚到这里,保证任何入口都不会把短时训练落库。
const minSec = config.minRecordSeconds || 20
if (elapsed < minSec) {
this._showNotRecorded(elapsed, minSec)
return
}
this._saving = true
const { level, message } = this._detectCelebrationLevel(elapsed)
@@ -797,6 +814,9 @@ Page({
// Background effects keep playing behind the modal
status: 'completed',
isCompleted: true,
// 重置未记录标记(上一轮若走了 _showNotRecorded,这里必须回 false,
// 否则下次正常完成的弹窗会误显示"未计入记录"且隐藏分享/记录按钮)
completionNotRecorded: false,
celebrationLevel: level,
celebrationMessage: message,
confettiPieces,
@@ -827,6 +847,46 @@ Page({
})
},
/**
* 训练时长低于 config.minRecordSeconds:不保存记录、不更新连胜,
* 复用完成弹窗(避免系统原生 modal 在 WebView stacking context 下
* hit-test 不稳),弹窗内明确提示"本次未计入记录"。
* 仍显示本次实际撑持秒数(大数字 countUp),但不放 confetti/连胜/科学提示,
* 并隐藏"查看记录/分享"入口(没有记录可看)。
*/
_showNotRecorded(elapsed, minSec) {
if (this._saving) return
this._saving = true
this.setData({
status: 'completed',
isCompleted: true,
completionNotRecorded: true,
minRecordSeconds: minSec,
showCompletion: true,
completionElapsed: 0,
completionActualElapsed: elapsed,
completionTarget: this.data.duration,
completionOvertime: 0,
completionStreak: 0,
completionLevel: 'normal',
completionMessage: '未达最低记录时长',
completionTipValue: '',
completionTipRisk: '',
confettiPieces: []
})
// Animate the big number from 0 to elapsed (same as the normal modal)
if (this._completionCountUp) this._completionCountUp.cancel()
this._completionCountUp = countUp({
from: 0,
to: elapsed,
duration: 1200,
onUpdate: (v) => this.setData({ completionElapsed: v }),
onComplete: () => { this._completionCountUp = null }
})
},
onCloseCompletion() {
if (this._completionCountUp) {
this._completionCountUp.cancel()
@@ -882,6 +942,8 @@ Page({
status: 'idle',
isRunning: false,
isPaused: false,
// 复位未记录标记:若上一轮走了 _showNotRecorded,重练前必须回 false
completionNotRecorded: false,
remaining: this.data.duration,
elapsed: 0,
goalReached: false,