feat(uniapp): 节拍器独立工具页面
- 新增 /pages/training/metronome 节拍器页面: · 中心大脉冲圆 + 重音变色(绿→金) · 拍号选择 1/4 ~ 6/8,第 1 拍重音视觉指示 · BPM 滑块 (40-240) + 一键 ±1/±5 步进 · Tap Tempo(连续 2-5 次点击自动测速) · 速度术语提示(Largo / Andante / Allegro 等) · 屏幕常亮、切后台自动暂停 - useMetronome 新增 setAccentEvery / tapTempo 方法,支持 accentSrc 重音音色 - 训练页右上角加节拍器入口 - DEV 悬浮按钮升级为可展开菜单(练一练 + 节拍器) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,8 +1,27 @@
|
||||
<template>
|
||||
<view v-if="visible" class="dev-training-entry" @click="goTraining">
|
||||
<view class="entry-icon">💪</view>
|
||||
<view class="entry-text">练一练</view>
|
||||
<view class="entry-tag">DEV</view>
|
||||
<view v-if="visible" class="dev-entry-wrap" :style="{ bottom: props.bottom + 'rpx' }">
|
||||
<view v-if="expanded" class="menu-list" @click.stop>
|
||||
<view class="menu-item" @click="goto('/pages/training/index')">
|
||||
<view class="menu-icon">💪</view>
|
||||
<view class="menu-info">
|
||||
<text class="menu-title">练一练</text>
|
||||
<text class="menu-sub">器械跟练</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="menu-item" @click="goto('/pages/training/metronome')">
|
||||
<view class="menu-icon metronome-icon">🎵</view>
|
||||
<view class="menu-info">
|
||||
<text class="menu-title">节拍器</text>
|
||||
<text class="menu-sub">独立工具</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="entry-fab" :class="{ expanded }" @click="toggle">
|
||||
<text class="fab-icon">{{ expanded ? '✕' : '💪' }}</text>
|
||||
<text v-if="!expanded" class="fab-text">练</text>
|
||||
<view v-if="!expanded" class="fab-tag">DEV</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -18,49 +37,64 @@ const props = withDefaults(
|
||||
)
|
||||
|
||||
const visible = ref(isDevMode())
|
||||
const expanded = ref(false)
|
||||
|
||||
const goTraining = () => {
|
||||
uni.navigateTo({ url: '/pages/training/index' })
|
||||
const toggle = () => {
|
||||
expanded.value = !expanded.value
|
||||
}
|
||||
|
||||
const goto = (url: string) => {
|
||||
expanded.value = false
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dev-training-entry {
|
||||
.dev-entry-wrap {
|
||||
position: fixed;
|
||||
right: 24rpx;
|
||||
bottom: v-bind('props.bottom + "rpx"');
|
||||
z-index: 999;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.entry-fab {
|
||||
width: 110rpx;
|
||||
height: 110rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #22c55e, #16a34a);
|
||||
box-shadow: 0 6rpx 20rpx rgba(34, 197, 94, 0.4);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
transition: transform 0.2s;
|
||||
position: relative;
|
||||
transition: transform 0.2s, background 0.2s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.entry-icon {
|
||||
font-size: 40rpx;
|
||||
&.expanded {
|
||||
background: linear-gradient(135deg, #64748b, #334155);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.fab-icon {
|
||||
font-size: 36rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.entry-text {
|
||||
.fab-text {
|
||||
font-size: 18rpx;
|
||||
color: #fff;
|
||||
margin-top: 4rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.entry-tag {
|
||||
.fab-tag {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: -8rpx;
|
||||
@@ -72,4 +106,67 @@ const goTraining = () => {
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
animation: slide-up 0.2s ease-out;
|
||||
}
|
||||
@keyframes slide-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20rpx);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
background: #fff;
|
||||
padding: 16rpx 24rpx;
|
||||
border-radius: 32rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
min-width: 240rpx;
|
||||
|
||||
&:active {
|
||||
background: #f0fdf4;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #22c55e, #16a34a);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
|
||||
&.metronome-icon {
|
||||
background: linear-gradient(135deg, #6366f1, #4f46e5);
|
||||
}
|
||||
}
|
||||
|
||||
.menu-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.menu-title {
|
||||
font-size: 28rpx;
|
||||
color: #111827;
|
||||
font-weight: 600;
|
||||
}
|
||||
.menu-sub {
|
||||
font-size: 20rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user