diff --git a/cloudfunctions/tts/index.js b/cloudfunctions/tts/index.js
index 5e4b7c4..b60ab2d 100644
--- a/cloudfunctions/tts/index.js
+++ b/cloudfunctions/tts/index.js
@@ -24,6 +24,7 @@ const PROMPTS = {
halfway: '已完成一半啦,坚持就是胜利!',
last30: '最后30秒,保持呼吸,稳住姿势!',
last10: '最后10秒,再加把劲!',
+ goal: '目标达成,继续挑战!',
complete: '太棒了!今天的目标已完成,继续加油!'
}
diff --git a/config.js b/config.js
index 78ec93a..3d327af 100644
--- a/config.js
+++ b/config.js
@@ -4,7 +4,7 @@
*/
module.exports = {
/** 应用版本号,外显在设置页「关于」 */
- version: 'v2.5',
+ version: 'v2.6',
/** 最后更新日期,外显在设置页「关于」 */
updatedAt: '2026-07-09',
diff --git a/pages/timer/timer.js b/pages/timer/timer.js
index 0d82d0d..a5b3f3d 100644
--- a/pages/timer/timer.js
+++ b/pages/timer/timer.js
@@ -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
})
diff --git a/pages/timer/timer.wxml b/pages/timer/timer.wxml
index 2e7ef82..f4eb3a9 100644
--- a/pages/timer/timer.wxml
+++ b/pages/timer/timer.wxml
@@ -46,7 +46,7 @@
💪
-
+
+{{overtime}}s
@@ -61,6 +61,10 @@
今日目标 {{duration}} 秒 · 第 {{planDay}} 天
+
+
+ 目标达成,继续挑战!
+
保持姿势,核心收紧!
@@ -145,7 +149,7 @@
+{{completionOvertime}}
- 秒超时
+ 秒超长发挥
diff --git a/utils/timer.js b/utils/timer.js
index 312d2c6..ce29628 100644
--- a/utils/timer.js
+++ b/utils/timer.js
@@ -1,7 +1,7 @@
class Timer {
constructor(options = {}) {
this.onTick = options.onTick || (() => {})
- this.onComplete = options.onComplete || (() => {})
+ this.onGoalReached = options.onGoalReached || (() => {})
this._duration = 0
this._remaining = 0
@@ -11,7 +11,7 @@ class Timer {
this._intervalId = null
this._running = false
this._paused = false
- this._completed = false
+ this._goalReached = false
this._appShowBound = false
// WeChat freezes setInterval while the mini-program is in background.
@@ -29,7 +29,7 @@ class Timer {
this._duration = duration
this._remaining = duration
this._elapsed = 0
- this._completed = false
+ this._goalReached = false
this._running = true
this._paused = false
this._startTime = Date.now()
@@ -81,15 +81,18 @@ class Timer {
duration: this._duration
})
- if (this._remaining <= 0 && !this._completed) {
- this._completed = true
- this.onComplete({ elapsed: this._elapsed, duration: this._duration })
+ // Goal reached: fire once but DON'T stop - the user keeps training past
+ // the target until they manually end. elapsed keeps climbing, so
+ // overtime (elapsed - duration) grows and the UI shows the surplus.
+ if (this._remaining <= 0 && !this._goalReached) {
+ this._goalReached = true
+ this.onGoalReached({ elapsed: this._elapsed, duration: this._duration })
}
}
get isRunning() { return this._running && !this._paused }
get isPaused() { return this._paused }
- get isCompleted() { return this._completed }
+ get isGoalReached() { return this._goalReached }
get remaining() { return this._remaining }
get elapsed() { return this._elapsed }
}
diff --git a/utils/voice.js b/utils/voice.js
index 74fc3d9..307b6d7 100644
--- a/utils/voice.js
+++ b/utils/voice.js
@@ -22,6 +22,7 @@ const PROMPTS = {
halfway: '已完成一半啦,坚持就是胜利!',
last30: '最后30秒,保持呼吸,稳住姿势!',
last10: '最后10秒,再加把劲!',
+ goal: '目标达成,继续挑战!',
complete: '太棒了!今天的目标已完成,继续加油!'
}