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:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user