feat(TUICallKit-Vue3): 迁入节拍器与练一练训练模块
之前误将训练模块加在 uniapp 项目,实际目标项目是 TUICallKit-Vue3 (甄养堂主小程序壳),整体迁入: - hooks/useMetronome.ts 节拍器(音频实例池+预热,无冷启动延迟) - hooks/useTrainingSession.ts 训练 Session 状态机 - hooks/useVoiceCoach.ts 语音教练队列 - hooks/useTrainingBgm.ts 训练/休息 BGM 切换+渐变 - pages/training/index.vue 练一练主页(器械跟练) - pages/training/metronome.vue 独立节拍器(老人友好版, 中央圆即开始/暂停按钮,纯 CSS 几何 icon,大字大按钮) - pages/training/components/exercise-anim.vue CSS 动作动画 - pages/training/exercises.ts 5 个器械动作预设 - static/training/audio/*.mp3 3 种 click 音色(MIT 协议) - scripts/generate-voice.mjs MiMo TTS 批量生成语音脚本 - pages.json 注册 pages/training/index 与 pages/training/metronome - 「我的」页加 DevTrainingEntry 悬浮入口,生产环境自动隐藏 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
<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'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
bottom?: number
|
||||
}>(),
|
||||
{ bottom: 200 },
|
||||
)
|
||||
|
||||
const isDevMode = (): boolean => {
|
||||
// HBuilderX 工程:发行(release)模式下 NODE_ENV === 'production'
|
||||
try {
|
||||
return process.env.NODE_ENV !== 'production'
|
||||
} catch (_) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
Reference in New Issue
Block a user