- 新增 /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>
173 lines
3.9 KiB
Vue
173 lines
3.9 KiB
Vue
<template>
|
|
<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>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import { isDevMode } from '@/utils/env'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
bottom?: number
|
|
}>(),
|
|
{ bottom: 200 },
|
|
)
|
|
|
|
const visible = ref(isDevMode())
|
|
const expanded = ref(false)
|
|
|
|
const toggle = () => {
|
|
expanded.value = !expanded.value
|
|
}
|
|
|
|
const goto = (url: string) => {
|
|
expanded.value = false
|
|
uni.navigateTo({ url })
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.dev-entry-wrap {
|
|
position: fixed;
|
|
right: 24rpx;
|
|
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;
|
|
position: relative;
|
|
transition: transform 0.2s, background 0.2s;
|
|
|
|
&:active {
|
|
transform: scale(0.92);
|
|
}
|
|
|
|
&.expanded {
|
|
background: linear-gradient(135deg, #64748b, #334155);
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
.fab-icon {
|
|
font-size: 36rpx;
|
|
line-height: 1;
|
|
}
|
|
|
|
.fab-text {
|
|
font-size: 18rpx;
|
|
color: #fff;
|
|
margin-top: 4rpx;
|
|
line-height: 1;
|
|
}
|
|
|
|
.fab-tag {
|
|
position: absolute;
|
|
top: -8rpx;
|
|
right: -8rpx;
|
|
background: #ef4444;
|
|
color: #fff;
|
|
font-size: 16rpx;
|
|
padding: 2rpx 8rpx;
|
|
border-radius: 8rpx;
|
|
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>
|