fix(timer): 完成页圆环覆盖弹窗 + 自定义确认 modal 替代 wx.showModal
训练完成的三个相关 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)))
This commit is contained in:
+20
-8
@@ -25,6 +25,7 @@ Page({
|
|||||||
confettiPieces: [],
|
confettiPieces: [],
|
||||||
// Completion modal (full-screen summary after training ends)
|
// Completion modal (full-screen summary after training ends)
|
||||||
showCompletion: false,
|
showCompletion: false,
|
||||||
|
showStopConfirm: false, // 自定义"结束训练"确认弹窗,替代 wx.showModal
|
||||||
completionElapsed: 0,
|
completionElapsed: 0,
|
||||||
completionTarget: 0,
|
completionTarget: 0,
|
||||||
completionOvertime: 0,
|
completionOvertime: 0,
|
||||||
@@ -260,13 +261,19 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onStop() {
|
onStop() {
|
||||||
wx.showModal({
|
// 用自定义确认弹窗替代 wx.showModal:
|
||||||
title: '结束训练',
|
// 系统原生 modal 在 WebView stacking context 下 hit-test 偶发不稳,
|
||||||
content: '确定要结束本次训练吗?',
|
// 自定义 modal + catchtouchmove 与现有 completion-mask 同模式,行为更可靠。
|
||||||
success: (res) => {
|
this.setData({ showStopConfirm: true })
|
||||||
if (res.confirm) this.finishTraining()
|
},
|
||||||
}
|
|
||||||
})
|
onStopCancel() {
|
||||||
|
this.setData({ showStopConfirm: false })
|
||||||
|
},
|
||||||
|
|
||||||
|
onStopConfirm() {
|
||||||
|
this.setData({ showStopConfirm: false })
|
||||||
|
this.finishTraining()
|
||||||
},
|
},
|
||||||
|
|
||||||
finishTraining() {
|
finishTraining() {
|
||||||
@@ -381,5 +388,10 @@ Page({
|
|||||||
// Re-init countUp so the big number rolls again on next start
|
// Re-init countUp so the big number rolls again on next start
|
||||||
_countUpTask: null
|
_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 */ }
|
||||||
})
|
})
|
||||||
|
|||||||
+16
-1
@@ -25,6 +25,7 @@
|
|||||||
wx:if="{{status !== 'completed'}}"></view>
|
wx:if="{{status !== 'completed'}}"></view>
|
||||||
|
|
||||||
<progress-ring
|
<progress-ring
|
||||||
|
wx:if="{{status !== 'completed'}}"
|
||||||
duration="{{duration}}"
|
duration="{{duration}}"
|
||||||
remaining="{{isCompleted ? 0 : remaining}}"
|
remaining="{{isCompleted ? 0 : remaining}}"
|
||||||
size="{{360}}"
|
size="{{360}}"
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
status="{{status}}"
|
status="{{status}}"
|
||||||
primaryColor="{{theme.primary}}"
|
primaryColor="{{theme.primary}}"
|
||||||
primaryLightColor="{{theme.primaryLight}}"
|
primaryLightColor="{{theme.primaryLight}}"
|
||||||
|
trackColor="{{isDark ? '#2C2C2E' : '#EEEEEE'}}"
|
||||||
></progress-ring>
|
></progress-ring>
|
||||||
|
|
||||||
<!-- 完成庆祝粒子 -->
|
<!-- 完成庆祝粒子 -->
|
||||||
@@ -114,8 +116,21 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 自定义"结束训练"确认弹窗 — 替代 wx.showModal,避免系统原生 modal
|
||||||
|
在 WebView stacking context 下 hit-test 不稳的问题 -->
|
||||||
|
<view class="stop-confirm-mask" wx:if="{{showStopConfirm}}" catchtouchmove="onNoop">
|
||||||
|
<view class="stop-confirm-card" catchtap="onNoop">
|
||||||
|
<text class="stop-confirm-title">结束训练</text>
|
||||||
|
<text class="stop-confirm-content">确定要结束本次训练吗?</text>
|
||||||
|
<view class="stop-confirm-actions">
|
||||||
|
<view class="stop-confirm-btn" bindtap="onStopCancel">取消</view>
|
||||||
|
<view class="stop-confirm-btn stop-confirm-btn--primary" bindtap="onStopConfirm">确定</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 完成训练全屏弹窗 -->
|
<!-- 完成训练全屏弹窗 -->
|
||||||
<view class="completion-mask" wx:if="{{showCompletion}}">
|
<view class="completion-mask" wx:if="{{showCompletion}}" catchtouchmove="onNoop">
|
||||||
<view class="completion-card">
|
<view class="completion-card">
|
||||||
<view class="completion-emoji-wrap">
|
<view class="completion-emoji-wrap">
|
||||||
<text class="completion-emoji">{{completionLevel === 'first' ? '🎉' : completionLevel === 'record' ? '🏆' : completionLevel === 'milestone' ? '🔥' : '✨'}}</text>
|
<text class="completion-emoji">{{completionLevel === 'first' ? '🎉' : completionLevel === 'record' ? '🏆' : completionLevel === 'milestone' ? '🔥' : '✨'}}</text>
|
||||||
|
|||||||
+83
-2
@@ -70,8 +70,8 @@
|
|||||||
bottom: 10rpx;
|
bottom: 10rpx;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
background: rgba(76, 175, 80, 0.12);
|
background: rgba(var(--success-rgb), 0.12);
|
||||||
border: 2rpx solid rgba(76, 175, 80, 0.3);
|
border: 2rpx solid rgba(var(--success-rgb), 0.3);
|
||||||
color: var(--success);
|
color: var(--success);
|
||||||
padding: 8rpx 24rpx;
|
padding: 8rpx 24rpx;
|
||||||
border-radius: 24rpx;
|
border-radius: 24rpx;
|
||||||
@@ -393,3 +393,84 @@
|
|||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
margin-top: 4rpx;
|
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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user