From 326bc013fe8a23192837e60b461e03124437e28f Mon Sep 17 00:00:00 2001 From: cnliucheng Date: Wed, 8 Jul 2026 15:21:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(timer):=20=E5=AE=8C=E6=88=90=E9=A1=B5?= =?UTF-8?q?=E5=9C=86=E7=8E=AF=E8=A6=86=E7=9B=96=E5=BC=B9=E7=AA=97=20+=20?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E7=A1=AE=E8=AE=A4=20modal=20?= =?UTF-8?q?=E6=9B=BF=E4=BB=A3=20wx.showModal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 训练完成的三个相关 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))) --- pages/timer/timer.js | 28 ++++++++++---- pages/timer/timer.wxml | 17 ++++++++- pages/timer/timer.wxss | 85 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 119 insertions(+), 11 deletions(-) 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); +}