style(training/metronome): 优化中央圆 UI,移除顶部拍点指示

- 移除顶部 .beat-bar 4 个圆点指示器,界面更简洁
- 中央圆按钮重新设计:
  · "速度"标签下方居中放置纯 CSS 几何绘制的大 ▶/⏸ 图标
    (icon-play 70rpx 三角形 / icon-pause 22rpx×84rpx 双竖条),
    跨机型一致、不再依赖 emoji 字符
  · 移除"开始/暂停"文字 + 药丸样式背景,保留 icon 即可识别
  · 圆心使用更深的渐变和内/外阴影,质感更立体
  · 增加第二层 ring 错相 0.4 周期播放,呼吸感更连续
- 状态提示文案调整:"点击" → "轻点"
- 顶部 padding 收紧 60rpx → 20rpx,圆体上下居中更平衡

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-25 11:18:30 +08:00
co-authored by Cursor
parent 35a42078ce
commit e5d0d7db93
+77 -87
View File
@@ -1,23 +1,16 @@
<template> <template>
<view class="metronome-page"> <view class="metronome-page">
<view class="beat-bar">
<view
v-for="i in accentEvery"
:key="i"
class="beat-dot"
:class="{
accent: i === 1,
active: ((beatIndex - 1) % accentEvery) + 1 === i && isPlaying,
}"
/>
</view>
<view class="pulse-stage" @click="togglePlay"> <view class="pulse-stage" @click="togglePlay">
<view <view
class="pulse-ring" class="pulse-ring"
:class="{ playing: isPlaying }" :class="{ playing: isPlaying }"
:style="ringStyle" :style="ringStyle"
/> />
<view
class="pulse-ring pulse-ring-2"
:class="{ playing: isPlaying }"
:style="ringStyle"
/>
<view <view
class="pulse-core" class="pulse-core"
:class="{ playing: isPlaying, accent: isAccent }" :class="{ playing: isPlaying, accent: isAccent }"
@@ -25,9 +18,12 @@
> >
<text class="bpm-num">{{ bpm }}</text> <text class="bpm-num">{{ bpm }}</text>
<text class="bpm-label">速度</text> <text class="bpm-label">速度</text>
<view class="play-state"> <view class="ctrl-icon">
<text class="play-icon">{{ isPlaying ? '⏸' : '▶' }}</text> <view v-if="isPlaying" class="icon-pause">
<text class="play-text">{{ isPlaying ? '暂停' : '开始' }}</text> <view class="bar" />
<view class="bar" />
</view>
<view v-else class="icon-play" />
</view> </view>
</view> </view>
</view> </view>
@@ -126,9 +122,11 @@ const coreStyle = computed(() => ({
})) }))
const statusHint = computed(() => { const statusHint = computed(() => {
if (isPlaying.value) if (isPlaying.value) {
return `${((beatIndex.value - 1) % accentEvery.value) + 1} 拍 / 共 ${accentEvery.value}` const cur = ((beatIndex.value - 1) % accentEvery.value) + 1
return '点击中间圆圈开始' return `${cur} 拍 / 共 ${accentEvery.value}`
}
return '轻点圆圈开始'
}) })
onShow(() => { onShow(() => {
@@ -150,7 +148,7 @@ onUnmounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.metronome-page { .metronome-page {
min-height: 100vh; min-height: 100vh;
padding: 60rpx 32rpx 80rpx; padding: 20rpx 32rpx 80rpx;
background: linear-gradient(180deg, #0f172a 0%, #1e293b 100%); background: linear-gradient(180deg, #0f172a 0%, #1e293b 100%);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -158,50 +156,18 @@ onUnmounted(() => {
color: #f1f5f9; color: #f1f5f9;
} }
/* ===== 拍点指示 ===== */
.beat-bar {
display: flex;
gap: 28rpx;
margin-bottom: 60rpx;
height: 40rpx;
align-items: center;
.beat-dot {
width: 28rpx;
height: 28rpx;
border-radius: 50%;
background: #334155;
transition: all 0.12s;
&.accent {
background: #475569;
}
&.active {
background: #22c55e;
transform: scale(1.6);
box-shadow: 0 0 24rpx #22c55e;
&.accent {
background: #f59e0b;
box-shadow: 0 0 28rpx #f59e0b;
}
}
}
}
/* ===== 中心可点击圆 ===== */ /* ===== 中心可点击圆 ===== */
.pulse-stage { .pulse-stage {
position: relative; position: relative;
width: 560rpx; width: 600rpx;
height: 560rpx; height: 600rpx;
margin-bottom: 60rpx; margin: 40rpx 0 50rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
&:active .pulse-core { &:active .pulse-core {
transform: scale(0.96); transform: scale(0.97);
} }
} }
@@ -210,20 +176,25 @@ onUnmounted(() => {
width: 100%; width: 100%;
height: 100%; height: 100%;
border-radius: 50%; border-radius: 50%;
border: 6rpx solid rgba(34, 197, 94, 0.2); border: 4rpx solid rgba(34, 197, 94, 0.18);
pointer-events: none;
&.playing { &.playing {
animation: ring-pulse var(--beat-duration, 750ms) ease-out infinite; animation: ring-pulse var(--beat-duration, 750ms) ease-out infinite;
} }
&.pulse-ring-2 {
animation-delay: calc(var(--beat-duration, 750ms) * -0.4);
}
} }
@keyframes ring-pulse { @keyframes ring-pulse {
0% { 0% {
transform: scale(0.95); transform: scale(0.92);
opacity: 1; opacity: 0.9;
border-color: rgba(34, 197, 94, 0.6); border-color: rgba(34, 197, 94, 0.55);
} }
100% { 100% {
transform: scale(1.12); transform: scale(1.18);
opacity: 0; opacity: 0;
border-color: rgba(34, 197, 94, 0); border-color: rgba(34, 197, 94, 0);
} }
@@ -234,57 +205,76 @@ onUnmounted(() => {
width: 460rpx; width: 460rpx;
height: 460rpx; height: 460rpx;
border-radius: 50%; border-radius: 50%;
background: linear-gradient(135deg, #22c55e, #16a34a); background: linear-gradient(140deg, #22c55e 0%, #15803d 100%);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
box-shadow: 0 16rpx 48rpx rgba(34, 197, 94, 0.4); box-shadow:
transition: transform 0.1s, background 0.15s, box-shadow 0.15s; inset 0 -20rpx 60rpx rgba(0, 0, 0, 0.18),
inset 0 20rpx 60rpx rgba(255, 255, 255, 0.12),
0 24rpx 60rpx rgba(34, 197, 94, 0.45);
transition: transform 0.12s, background 0.15s, box-shadow 0.15s;
&.playing { &.playing {
animation: core-beat var(--beat-duration, 750ms) ease-in-out infinite; animation: core-beat var(--beat-duration, 750ms) ease-in-out infinite;
} }
&.playing.accent { &.playing.accent {
background: linear-gradient(135deg, #f59e0b, #d97706); background: linear-gradient(140deg, #fbbf24 0%, #b45309 100%);
box-shadow: 0 16rpx 48rpx rgba(245, 158, 11, 0.45); box-shadow:
inset 0 -20rpx 60rpx rgba(0, 0, 0, 0.18),
inset 0 20rpx 60rpx rgba(255, 255, 255, 0.18),
0 24rpx 60rpx rgba(245, 158, 11, 0.5);
} }
.bpm-num { .bpm-num {
font-size: 200rpx; font-size: 200rpx;
font-weight: 700; font-weight: 800;
color: #fff; color: #fff;
line-height: 1; line-height: 1;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.18);
} }
.bpm-label { .bpm-label {
font-size: 30rpx; font-size: 28rpx;
color: rgba(255, 255, 255, 0.85); color: rgba(255, 255, 255, 0.7);
letter-spacing: 6rpx; letter-spacing: 8rpx;
margin-top: 12rpx; margin-top: 8rpx;
} }
.play-state { /* ===== 中央播放/暂停 icon(CSS 几何绘制,跨机型一致) ===== */
position: absolute; .ctrl-icon {
bottom: 60rpx; margin-top: 36rpx;
width: 110rpx;
height: 90rpx;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12rpx; justify-content: center;
padding: 12rpx 28rpx; }
background: rgba(255, 255, 255, 0.18);
border-radius: 40rpx;
.play-icon { .icon-play {
font-size: 32rpx; width: 0;
color: #fff; height: 0;
line-height: 1; border-left: 70rpx solid #fff;
} border-top: 44rpx solid transparent;
.play-text { border-bottom: 44rpx solid transparent;
font-size: 28rpx; margin-left: 16rpx;
color: #fff; filter: drop-shadow(0 2rpx 6rpx rgba(0, 0, 0, 0.2));
font-weight: 500; }
.icon-pause {
display: flex;
gap: 22rpx;
height: 84rpx;
.bar {
width: 22rpx;
height: 100%;
background: #fff;
border-radius: 6rpx;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.2);
} }
} }
} }
@@ -294,7 +284,7 @@ onUnmounted(() => {
transform: scale(1); transform: scale(1);
} }
50% { 50% {
transform: scale(1.05); transform: scale(1.045);
} }
} }