diff --git a/pages/timer/timer.js b/pages/timer/timer.js
index a5843e2..4f40ae6 100644
--- a/pages/timer/timer.js
+++ b/pages/timer/timer.js
@@ -25,6 +25,7 @@ Page({
confettiPieces: [],
// Completion modal (full-screen summary after training ends)
showCompletion: false,
+ showStopConfirm: false, // 自定义"结束训练"确认弹窗,替代 wx.showModal
completionElapsed: 0,
completionTarget: 0,
completionOvertime: 0,
@@ -260,13 +261,19 @@ Page({
},
onStop() {
- wx.showModal({
- title: '结束训练',
- content: '确定要结束本次训练吗?',
- success: (res) => {
- if (res.confirm) this.finishTraining()
- }
- })
+ // 用自定义确认弹窗替代 wx.showModal:
+ // 系统原生 modal 在 WebView stacking context 下 hit-test 偶发不稳,
+ // 自定义 modal + catchtouchmove 与现有 completion-mask 同模式,行为更可靠。
+ this.setData({ showStopConfirm: true })
+ },
+
+ onStopCancel() {
+ this.setData({ showStopConfirm: false })
+ },
+
+ onStopConfirm() {
+ this.setData({ showStopConfirm: false })
+ this.finishTraining()
},
finishTraining() {
@@ -381,5 +388,10 @@ Page({
// Re-init countUp so the big number rolls again on next start
_countUpTask: null
})
- }
+ },
+
+ // No-op handler for catchtouchmove. Swallows move events that would
+ // otherwise bubble to underlying timer page controls (causing missed
+ // taps on the completion modal).
+ onNoop() { /* swallow */ }
})
diff --git a/pages/timer/timer.wxml b/pages/timer/timer.wxml
index 3dbc304..2e7ef82 100644
--- a/pages/timer/timer.wxml
+++ b/pages/timer/timer.wxml
@@ -25,6 +25,7 @@
wx:if="{{status !== 'completed'}}">
@@ -114,8 +116,21 @@
+
+
+
+ 结束训练
+ 确定要结束本次训练吗?
+
+ 取消
+ 确定
+
+
+
+
-
+
{{completionLevel === 'first' ? '🎉' : completionLevel === 'record' ? '🏆' : completionLevel === 'milestone' ? '🔥' : '✨'}}
diff --git a/pages/timer/timer.wxss b/pages/timer/timer.wxss
index fe8f103..6e38461 100644
--- a/pages/timer/timer.wxss
+++ b/pages/timer/timer.wxss
@@ -70,8 +70,8 @@
bottom: 10rpx;
left: 50%;
transform: translateX(-50%);
- background: rgba(76, 175, 80, 0.12);
- border: 2rpx solid rgba(76, 175, 80, 0.3);
+ background: rgba(var(--success-rgb), 0.12);
+ border: 2rpx solid rgba(var(--success-rgb), 0.3);
color: var(--success);
padding: 8rpx 24rpx;
border-radius: 24rpx;
@@ -393,3 +393,84 @@
font-size: 26rpx;
margin-top: 4rpx;
}
+
+/* ---- stop confirm modal (替代 wx.showModal,自定义实现) ---- */
+.stop-confirm-mask {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.55);
+ z-index: 9999;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 40rpx;
+ box-sizing: border-box;
+ animation: completionMaskIn 0.25s ease;
+}
+
+.stop-confirm-card {
+ width: 100%;
+ max-width: 560rpx;
+ background: var(--card-bg);
+ border-radius: 24rpx;
+ padding: 48rpx 40rpx 24rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ box-shadow: 0 12rpx 36rpx rgba(0, 0, 0, 0.22);
+ animation: stopConfirmPop 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+@keyframes stopConfirmPop {
+ 0% { transform: scale(0.85); opacity: 0; }
+ 100% { transform: scale(1); opacity: 1; }
+}
+
+.stop-confirm-title {
+ font-size: 32rpx;
+ font-weight: 700;
+ color: var(--text);
+ margin-bottom: 20rpx;
+ line-height: 1.3;
+}
+
+.stop-confirm-content {
+ font-size: 28rpx;
+ color: var(--text-secondary);
+ line-height: 1.5;
+ text-align: center;
+ margin-bottom: 40rpx;
+}
+
+.stop-confirm-actions {
+ display: flex;
+ width: 100%;
+ gap: 16rpx;
+ border-top: 1rpx solid var(--border);
+ padding-top: 24rpx;
+}
+
+.stop-confirm-btn {
+ flex: 1;
+ height: 80rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 30rpx;
+ font-weight: 600;
+ border-radius: 12rpx;
+ background: var(--bg-soft);
+ color: var(--text);
+ transition: transform 0.15s ease, opacity 0.15s ease, background 0.2s ease;
+}
+
+.stop-confirm-btn:active {
+ transform: scale(0.96);
+ opacity: 0.85;
+}
+
+.stop-confirm-btn--primary {
+ background: linear-gradient(135deg, var(--primary), var(--primary-light));
+ color: #FFFFFF;
+ box-shadow: 0 6rpx 16rpx rgba(var(--primary-rgb), 0.3);
+}