diff --git a/TUICallKit-Vue3/components/dev-training-entry/index.vue b/TUICallKit-Vue3/components/dev-training-entry/index.vue index f5a82248..b24ef9e4 100644 --- a/TUICallKit-Vue3/components/dev-training-entry/index.vue +++ b/TUICallKit-Vue3/components/dev-training-entry/index.vue @@ -16,7 +16,7 @@ - 🟢 + 握力环 @@ -263,8 +263,8 @@ $brand-light: #34d399; background: linear-gradient(140deg, $brand-light, $brand-deep); } &--grip { - background: linear-gradient(140deg, #86efac, #22c55e); - box-shadow: 0 2rpx 8rpx rgba(34, 197, 94, 0.36); + background: linear-gradient(140deg, #5eead4, #14b8a6); + box-shadow: 0 2rpx 8rpx rgba(20, 184, 166, 0.36); } &--metro { background: linear-gradient(140deg, #fbbf24, #d97706); @@ -278,6 +278,17 @@ $brand-light: #34d399; line-height: 1; } +/* 握力环小 icon: 白色描边的环(甜甜圈) */ +.grip-ring-shape { + width: 32rpx; + height: 32rpx; + border-radius: 50%; + border: 7rpx solid #fff; + box-shadow: + 0 1rpx 2rpx rgba(0, 0, 0, 0.18), + inset 0 1rpx 2rpx rgba(255, 255, 255, 0.6); +} + /* 节拍器小 icon: 3 根高低柱模拟均衡器/节拍 */ .metro-bar { width: 4rpx; diff --git a/TUICallKit-Vue3/training/pages/grip-ring.vue b/TUICallKit-Vue3/training/pages/grip-ring.vue index 1e2d63e1..7426bf10 100644 --- a/TUICallKit-Vue3/training/pages/grip-ring.vue +++ b/TUICallKit-Vue3/training/pages/grip-ring.vue @@ -111,10 +111,43 @@ - - - - {{ tip }} + + + + + + + 观看动作示范 + 视频教学 · 学会正确握法 + + + + + + + + + 动作示范 + + × + + + @@ -210,6 +243,9 @@ const PRESET_ICONS: Record = { const TIPS = ['握紧停 1 秒', '完全放松', '左右手交替'] as const +// 视频教学 +const TUTORIAL_VIDEO_URL = 'https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/video/20260528/20260528173911bf6f93386.mp4' + interface TargetOption { minutes: 0 | 5 | 10 | 15 label: string @@ -291,6 +327,7 @@ const targetMinutes = ref<0 | 5 | 10 | 15>(0) // 训练目标(0=自由) const gripCanvasRef = ref(null) const showRewardPopup = ref(false) const showSummary = ref(false) +const showTutorial = ref(false) // 视频教学弹层 const currentReward = ref(REWARDS[0]) const crushAudioCtx = ref(null) const bgmAudioCtx = ref(null) @@ -550,6 +587,20 @@ function dismissSummary() { showSummary.value = false } +// 打开视频教学(避免与 BGM 音轨冲突) +function openTutorial() { + stopBgm() + showTutorial.value = true +} + +function closeTutorial() { + showTutorial.value = false + // 训练中且 BGM 开关开着 → 恢复 BGM + if (isPlaying.value && bgmEnabled.value) { + startBgm() + } +} + function onContinue() { showSummary.value = false startTrainingInternal() @@ -618,22 +669,22 @@ onShareTimeline(() => ({ })) onHide(() => { - if (isPlaying.value) { - metronome.stop() - stopBgm() - stopTimer() - uni.setKeepScreenOn({ keepScreenOn: false }) + // 切出页面 → 自动暂停训练 + 强制停掉所有音频 + // 不依赖 isPlaying 判断: 暂停/结算态也可能有未释放的音频实例 + // requiredBackgroundModes:['audio'] 下,InnerAudioContext.pause 不一定立即生效,这里用 stop+destroy 兜底 + try { metronome.stop() } catch (_) {} + stopTimer() + uni.setKeepScreenOn({ keepScreenOn: false }) + + if (bgmAudioCtx.value) { + try { bgmAudioCtx.value.stop() } catch (_) {} + try { bgmAudioCtx.value.destroy() } catch (_) {} + bgmAudioCtx.value = null } if (crushAudioCtx.value) { - try { - crushAudioCtx.value.destroy() - } catch (_) {} - } - if (bgmAudioCtx.value) { - try { - bgmAudioCtx.value.destroy() - } catch (_) {} - bgmAudioCtx.value = null + try { crushAudioCtx.value.stop() } catch (_) {} + try { crushAudioCtx.value.destroy() } catch (_) {} + crushAudioCtx.value = null } }) @@ -1139,26 +1190,190 @@ $shadow-sm: 0 4rpx 12rpx rgba(15, 23, 42, 0.04); } /* ============================================================ - * 动作要领条 + * 视频教学入口卡片 * ============================================================ */ -.tips-strip { +.tutorial-card { display: flex; - flex-wrap: wrap; - gap: 10rpx; + align-items: center; + gap: 16rpx; + padding: 18rpx 24rpx 18rpx 18rpx; + background: linear-gradient(135deg, #ffffff 0%, #f0fdfa 100%); + border-radius: 24rpx; + border: 2rpx solid rgba(20, 184, 166, 0.18); + box-shadow: + 0 4rpx 16rpx rgba(15, 23, 42, 0.04), + 0 1rpx 0 rgba(255, 255, 255, 0.9) inset; + transition: transform 0.15s; + + &:active { + transform: scale(0.98); + background: linear-gradient(135deg, #f0fdfa 0%, #ecfeff 100%); + } +} + +.tutorial-icon { + width: 72rpx; + height: 72rpx; + border-radius: 50%; + background: linear-gradient(140deg, $brand 0%, $brand-deep 100%); + display: flex; + align-items: center; justify-content: center; + flex-shrink: 0; + box-shadow: 0 4rpx 12rpx rgba(20, 184, 166, 0.35); } -.tip-chip { - padding: 10rpx 20rpx; - background: rgba(255, 255, 255, 0.7); - border-radius: 999rpx; - border: 2rpx solid $card-border; +.tutorial-play-triangle { + width: 0; + height: 0; + border-left: 22rpx solid #fff; + border-top: 14rpx solid transparent; + border-bottom: 14rpx solid transparent; + margin-left: 6rpx; } -.tip-text { +.tutorial-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 4rpx; + min-width: 0; +} + +.tutorial-title { + font-size: 28rpx; + font-weight: 700; + color: $text-1; + letter-spacing: 1rpx; +} + +.tutorial-sub { font-size: 22rpx; + color: $text-3; + letter-spacing: 0.5rpx; +} + +.tutorial-arrow { + font-size: 36rpx; + color: #cbd5e1; + font-weight: 300; + line-height: 1; + flex-shrink: 0; +} + +/* ============================================================ + * 视频教学弹层 + * ============================================================ */ +.tutorial-mask { + position: fixed; + inset: 0; + background: rgba(15, 23, 42, 0.65); + backdrop-filter: blur(8rpx); + display: flex; + align-items: center; + justify-content: center; + z-index: 950; + padding: 0 24rpx; + animation: fade-in 0.2s ease; +} + +.tutorial-modal { + width: 100%; + max-width: 700rpx; + background: #fff; + border-radius: 28rpx; + overflow: hidden; + display: flex; + flex-direction: column; + animation: tutorial-pop 0.25s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +@keyframes tutorial-pop { + from { + transform: scale(0.92); + opacity: 0; + } + to { + transform: scale(1); + opacity: 1; + } +} + +.tutorial-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 24rpx 28rpx 16rpx; +} + +.tutorial-modal-title { + font-size: 32rpx; + font-weight: 700; + color: $text-1; + letter-spacing: 1rpx; +} + +.tutorial-close { + width: 56rpx; + height: 56rpx; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + background: #f1f5f9; + + &:active { + background: #e2e8f0; + } +} + +.tutorial-close-icon { + font-size: 40rpx; color: $text-2; - line-height: 1.3; + line-height: 1; + font-weight: 300; +} + +.tutorial-video { + width: 100%; + // 视频源 720×720 方形, 容器同比贴合避免左右黑边 + // modal max-width = 700rpx, 这里也用 700rpx 完美 1:1 + height: 700rpx; + background: #000; +} + +.tutorial-tips { + padding: 24rpx 28rpx 32rpx; + display: flex; + flex-direction: column; + gap: 12rpx; +} + +.tutorial-tips-title { + font-size: 24rpx; + color: $text-3; + letter-spacing: 1rpx; + margin-bottom: 4rpx; +} + +.tutorial-tip-item { + display: flex; + align-items: center; + gap: 12rpx; +} + +.tip-dot { + width: 8rpx; + height: 8rpx; + border-radius: 50%; + background: $brand; + flex-shrink: 0; +} + +.tip-content { + font-size: 26rpx; + color: $text-2; + line-height: 1.5; } /* ============================================================