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>
This commit is contained in:
+23
-17
@@ -17,6 +17,7 @@ Page({
|
||||
isPaused: false,
|
||||
isCompleted: false,
|
||||
overtime: 0,
|
||||
goalReached: false,
|
||||
todayTarget: 0,
|
||||
planDay: 1,
|
||||
isFreeMode: false,
|
||||
@@ -79,7 +80,8 @@ Page({
|
||||
remaining: target,
|
||||
todayTarget: target,
|
||||
planDay: isFreeMode ? 0 : planDay,
|
||||
isFreeMode
|
||||
isFreeMode,
|
||||
goalReached: false
|
||||
})
|
||||
|
||||
if (this._timer) {
|
||||
@@ -104,17 +106,14 @@ Page({
|
||||
})
|
||||
this._remind(tick)
|
||||
},
|
||||
onComplete: () => {
|
||||
const elapsed = this._timer.elapsed
|
||||
onGoalReached: () => {
|
||||
// Target hit - don't end. Voice prompt only (no vibration, per
|
||||
// design); keep counting so the user can train to failure.
|
||||
const s = storage.getSettings()
|
||||
if (s.vibrate) {
|
||||
try { wx.vibrateLong() } catch (e) {}
|
||||
}
|
||||
if (s.voiceGuide !== false) {
|
||||
voice.play('complete')
|
||||
voice.play('goal')
|
||||
}
|
||||
// Auto-save + show big completion modal
|
||||
this._saveAndShowCompletion(elapsed)
|
||||
this.setData({ goalReached: true })
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -124,7 +123,7 @@ Page({
|
||||
* - halfway: once when elapsed reaches half of duration
|
||||
* - 30s: once when remaining hits 30
|
||||
* - 10s: once when remaining hits 10
|
||||
* - complete: fired in onComplete above
|
||||
* - complete: fired in finishTraining when the user ends the session
|
||||
*
|
||||
* Vibration and voice are independently gated by `vibrate` / `voiceGuide`
|
||||
* settings — user can mute haptics but keep voice (e.g. in office) or vice versa.
|
||||
@@ -196,8 +195,8 @@ Page({
|
||||
},
|
||||
|
||||
/**
|
||||
* Detect which celebration level to show. Called at onComplete (BEFORE
|
||||
* saveRecord fires in finishTraining), so stats reflect pre-save state.
|
||||
* Detect which celebration level to show. Called from _saveAndShowCompletion
|
||||
* (runs in finishTraining, BEFORE saveRecord), so stats reflect pre-save state.
|
||||
* - first: user's very first training ever
|
||||
* - milestone: streak will hit a memorable number after this save
|
||||
* - record: this duration beats the user's previous max
|
||||
@@ -312,18 +311,24 @@ Page({
|
||||
setTimeout(() => { wx.navigateBack() }, 1500)
|
||||
return
|
||||
}
|
||||
// Completion voice (replaces the old auto-onComplete cue). No vibration.
|
||||
const s = storage.getSettings()
|
||||
if (s.voiceGuide !== false) {
|
||||
voice.play('complete')
|
||||
}
|
||||
this._saveAndShowCompletion(elapsed)
|
||||
},
|
||||
|
||||
/**
|
||||
* Persist the just-finished training and pop the big completion modal.
|
||||
* Called from both onComplete (natural finish) and finishTraining
|
||||
* (user ended early). Detection of "first / record / milestone" uses
|
||||
* pre-save stats so the level is decided before the data changes.
|
||||
* Called from finishTraining (the user manually ends; the timer keeps
|
||||
* running past the goal until they stop). Detection of "first / record
|
||||
* / milestone" uses pre-save stats so the level is decided before the
|
||||
* data changes.
|
||||
*/
|
||||
_saveAndShowCompletion(elapsed) {
|
||||
// Unified re-entry guard: covers both the natural-complete (onComplete)
|
||||
// and manual-stop (finishTraining) paths so the save never runs twice.
|
||||
// Re-entry guard: protects the finishTraining path so the save never
|
||||
// runs twice even if the user double-taps end.
|
||||
if (this._saving) return
|
||||
this._saving = true
|
||||
|
||||
@@ -421,6 +426,7 @@ Page({
|
||||
isPaused: false,
|
||||
remaining: this.data.duration,
|
||||
overtime: 0,
|
||||
goalReached: false,
|
||||
// Re-init countUp so the big number rolls again on next start
|
||||
_countUpTask: null
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user