This commit is contained in:
2026-05-29 18:17:44 +08:00
parent c589bfa647
commit 132a17b946
15 changed files with 285 additions and 2175 deletions
+70 -95
View File
@@ -72,26 +72,9 @@
<!-- ===== 控制面板统计 + 档位 + 目标 ===== -->
<view class="control-deck" :class="{ 'is-playing': isPlaying, 'is-paused': isPaused }">
<view class="stats-row">
<view class="stat-chip">
<text class="stat-chip-label">次数</text>
<text class="stat-chip-value" :class="{ muted: !hasStats }">{{ hasStats ? totalReps : '—' }}</text>
</view>
<view class="stat-chip">
<text class="stat-chip-label">时长</text>
<text class="stat-chip-value" :class="{ muted: !hasStats }">{{ hasStats ? formatTime(elapsedSeconds) : '—' }}</text>
</view>
<view class="stat-chip">
<text class="stat-chip-label">糖分</text>
<text class="stat-chip-value" :class="{ muted: !hasStats }">{{ hasStats ? `${totalSugar.toFixed(1)}g` : '—' }}</text>
</view>
</view>
<view class="deck-divider" />
<view class="presets-block">
<view class="presets-header">
<text class="presets-title">训练强度</text>
<text class="presets-title">选择训练强度</text>
<text v-if="isPlaying" class="presets-hint">训练中不可切换</text>
</view>
<view class="presets">
@@ -102,26 +85,24 @@
:class="{ active: currentPreset === p.id, disabled: isPlaying }"
@click="onPresetTap(p.id)"
>
<text class="preset-icon">{{ PRESET_ICONS[p.id] }}</text>
<text class="preset-name">{{ p.label }}</text>
<text class="preset-bpm">{{ p.bpm }} BPM</text>
<text class="preset-desc">{{ p.desc }}</text>
</view>
</view>
<view class="intensity-estimate">
<text class="estimate-icon">🔥</text>
<text class="estimate-text">{{ estimateText }}</text>
</view>
</view>
<view class="deck-divider" />
<!-- 训练时长(默认折叠,点击展开) -->
<view class="target-block">
<view class="target-header">
<text class="target-title">训练目标</text>
<text class="target-hint">到时间自动结束</text>
<view class="target-header" @click="targetExpanded = !targetExpanded">
<text class="target-title">训练时长</text>
<view class="target-current">
<text class="target-current-text">{{ currentTargetLabel }}</text>
<text class="target-toggle">{{ targetExpanded ? '▲' : '▼' }}</text>
</view>
</view>
<view class="target-chips">
<view v-if="targetExpanded" class="target-chips">
<view
v-for="t in TARGET_OPTIONS"
:key="t.minutes"
@@ -134,10 +115,9 @@
</view>
</view>
<!-- 仅暂停时显示一行提示 + 结束训练文字链 -->
<view class="deck-divider" />
<!-- 训练功效介绍(可折叠) -->
<!-- 训练功效介绍(可折叠,默认收起) -->
<view class="benefit-block">
<view class="benefit-header" @click="benefitExpanded = !benefitExpanded">
<text class="benefit-title">训练功效</text>
@@ -264,21 +244,6 @@ const DUMBBELL_PRESETS: readonly DumbbellPreset[] = [
{ id: 'heavy', bpm: 80, met: 6.5, label: '重度', desc: '强化 · 挑战' },
]
// 训练功效介绍(专业文案)
const TRAINING_BENEFIT = {
muscles: ['肱二头肌', '三角肌', '胸大肌', '背阔肌'],
effect: '多角度抗阻训练上肢与肩背肌群,提升肌肉力量与耐力,雕塑手臂与肩部线条,增强骨密度,改善上肢日常功能。',
}
// 强度预估参考时长(选择"自由"目标时按此估算)
const ESTIMATE_REF_MINUTES = 10
const PRESET_ICONS: Record<PresetId, string> = {
light: '🌱',
medium: '💪',
heavy: '🔥',
}
// 画布指导视频
const TUTORIAL_VIDEO_URL = 'https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/video/20260528/202605281739117bf3c2914.mp4'
const TUTORIAL_POSTER_URL = 'https://gz-1349751149.cos.ap-guangzhou.myqcloud.com/uploads/images/20260529/202605291142269212d9402.jpg'
@@ -349,16 +314,21 @@ const timerInterval = ref<number | null>(null)
const preset = computed(() => DUMBBELL_PRESETS.find(p => p.id === currentPreset.value) || DUMBBELL_PRESETS[1])
const currentBpm = computed(() => preset.value.bpm)
const currentMet = computed(() => preset.value.met)
const benefitExpanded = ref(false)
// 当前强度预估消耗文案: 按选中目标时长(自由→10分钟参考)估算
const estimateText = computed(() => {
const mins = targetMinutes.value > 0 ? targetMinutes.value : ESTIMATE_REF_MINUTES
const sugar = (currentMet.value * 60 * (mins / 60)) / 4
const suffix = targetMinutes.value > 0 ? '' : '(按 10 分钟估算)'
return `${preset.value.label}训练 ${mins} 分钟,约消耗 ${sugar.toFixed(1)}g 糖分${suffix}`
// 训练时长折叠状态 + 当前时长文案
const targetExpanded = ref(false)
const currentTargetLabel = computed(() => {
const opt = TARGET_OPTIONS.find(t => t.minutes === targetMinutes.value)
return opt ? opt.label : '自由'
})
// 训练功效介绍(专业文案) + 折叠状态
const benefitExpanded = ref(false)
const TRAINING_BENEFIT = {
muscles: ['肱二头肌', '三角肌', '胸大肌', '背阔肌'],
effect: '多角度抗阻训练上肢与肩背肌群,提升肌肉力量与耐力,雕塑手臂与肩部线条,增强骨密度,改善上肢日常功能。',
}
// 示范视频跟随训练状态
watch(isPlaying, (playing) => {
const videoCtx = uni.createVideoContext('dumbbellDemoVideo')
@@ -826,7 +796,7 @@ $shadow-sm: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
margin-left: -28rpx;
margin-right: -28rpx;
flex-shrink: 0;
height: 520rpx;
height: 740rpx;
display: flex;
justify-content: center;
align-items: center;
@@ -859,8 +829,8 @@ $shadow-sm: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
.ring-container {
position: relative;
width: 560rpx;
height: 560rpx;
width: 720rpx;
height: 720rpx;
display: flex;
align-items: center;
justify-content: center;
@@ -869,8 +839,8 @@ $shadow-sm: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
.beat-halo {
position: absolute;
width: 560rpx;
height: 560rpx;
width: 720rpx;
height: 720rpx;
border-radius: 50%;
background: radial-gradient(circle,
rgba(59, 130, 246, 0.45) 0%,
@@ -899,8 +869,8 @@ $shadow-sm: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
.video-ring {
position: relative;
width: 460rpx;
height: 460rpx;
width: 640rpx;
height: 640rpx;
border-radius: 50%;
overflow: hidden;
box-shadow:
@@ -964,8 +934,8 @@ $shadow-sm: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
}
.hint-icon {
width: 116rpx;
height: 116rpx;
width: 140rpx;
height: 140rpx;
display: flex;
align-items: center;
justify-content: center;
@@ -981,10 +951,10 @@ $shadow-sm: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
.play-triangle {
width: 0;
height: 0;
border-left: 34rpx solid #ffffff;
border-top: 23rpx solid transparent;
border-bottom: 23rpx solid transparent;
margin-left: 8rpx;
border-left: 44rpx solid #ffffff;
border-top: 30rpx solid transparent;
border-bottom: 30rpx solid transparent;
margin-left: 10rpx;
}
@keyframes hint-bounce {
@@ -997,7 +967,7 @@ $shadow-sm: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
}
.hint-text {
font-size: 26rpx;
font-size: 34rpx;
color: #ffffff;
font-weight: 700;
letter-spacing: 3rpx;
@@ -1182,8 +1152,8 @@ $shadow-sm: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
}
.presets-title {
font-size: 26rpx;
font-weight: 700;
font-size: 32rpx;
font-weight: 800;
color: $text-1;
}
@@ -1200,38 +1170,26 @@ $shadow-sm: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
.preset {
flex: 1;
background: #f8fafc;
border: 2rpx solid transparent;
border-radius: 20rpx;
padding: 20rpx 6rpx 18rpx;
border: 3rpx solid transparent;
border-radius: 24rpx;
padding: 32rpx 8rpx 28rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 4rpx;
gap: 10rpx;
transition: all 0.18s;
.preset-icon {
font-size: 32rpx;
line-height: 1;
margin-bottom: 4rpx;
}
.preset-name {
font-size: 26rpx;
font-weight: 700;
font-size: 40rpx;
font-weight: 800;
color: $text-1;
}
.preset-bpm {
font-size: 22rpx;
font-size: 26rpx;
color: $text-2;
font-weight: 700;
font-variant-numeric: tabular-nums;
}
.preset-desc {
font-size: 18rpx;
color: $text-3;
text-align: center;
line-height: 1.3;
}
&:active {
transform: scale(0.97);
@@ -1270,17 +1228,34 @@ $shadow-sm: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 4rpx;
padding: 12rpx 6rpx;
border-radius: 16rpx;
&:active {
background: #f8fafc;
}
}
.target-title {
font-size: 26rpx;
font-weight: 700;
font-size: 32rpx;
font-weight: 800;
color: $text-1;
}
.target-hint {
font-size: 20rpx;
.target-current {
display: flex;
align-items: center;
gap: 12rpx;
}
.target-current-text {
font-size: 30rpx;
font-weight: 700;
color: $brand-deep;
}
.target-toggle {
font-size: 22rpx;
color: $text-3;
}
@@ -1291,7 +1266,7 @@ $shadow-sm: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
.target-chip {
flex: 1;
height: 72rpx;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
@@ -1301,8 +1276,8 @@ $shadow-sm: 0 4rpx 12rpx rgba(15, 23, 42, 0.04);
transition: all 0.18s;
.target-chip-text {
font-size: 24rpx;
font-weight: 600;
font-size: 30rpx;
font-weight: 700;
color: $text-2;
}