refactor: 训练模块整体迁入分包,减小主包体积

主包超出 2MB 上限无法上传发布版,把训练相关全部移到独立分包。

目录调整:
- pages/training/* → training/pages/*
- hooks/use{Metronome,VoiceCoach,TrainingSession,TrainingBgm}.ts
  → training/hooks/*
- static/training/* → training/static/*
- 主包 hooks/ 目录已清空,所有 use* 都被分包独占

引用路径更新:
- 分包内 hook import 用相对路径 ../hooks/xxx,不依赖 @/ alias
- 资源路径 /static/training/audio/click.mp3
  → /training/static/audio/click.mp3
- useMetronome 默认 clickSrc / useVoiceCoach VOICE_BASE
  / useTrainingBgm BGM_BASE 全部对齐新路径

pages.json:
- 主包 pages 移除 training/index 和 training/metronome
- subPackages 新增 root="training",含 pages/index 和 pages/metronome
- 跟现有 TUIKit / doctor 分包风格一致

调用方:
- components/dev-training-entry navigateTo URL 更新成
  /training/pages/metronome (此组件留在主包,主包跳分包是合法操作)

文档/工具同步:
- training/static/README.md 路径示例改为 training/static/...
- scripts/generate-voice.mjs VOICE_DIR 与提示输出对齐新路径

Git mv 检测到的全是 rename(R),历史完整保留。
分包总体 80K(pages 44K + hooks 20K + static 16K),主包瘦身明显。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-26 09:00:01 +08:00
co-authored by Cursor
parent f718df833e
commit 50abe5dece
15 changed files with 45 additions and 40 deletions
@@ -3,7 +3,7 @@
<!-- 展开后的菜单卡片(从下往上动画) -->
<view v-if="expanded" class="menu-list" @click.stop>
<!-- "练一练" 暂时隐藏,后续恢复时取消注释即可
<view class="menu-item" @click="goto('/pages/training/index')">
<view class="menu-item" @click="goto('/training/pages/index')">
<view class="menu-icon menu-icon&#45;&#45;train">
<text class="menu-icon-text">💪</text>
</view>
@@ -14,7 +14,7 @@
<view class="menu-arrow"></view>
</view>
-->
<view class="menu-item" @click="goto('/pages/training/metronome')">
<view class="menu-item" @click="goto('/training/pages/metronome')">
<view class="menu-icon menu-icon--metro">
<view class="metro-bar metro-bar-1" />
<view class="metro-bar metro-bar-2" />
+21 -16
View File
@@ -69,22 +69,6 @@
"style": {
"navigationBarTitleText": "付款"
}
},
{
"path": "pages/training/index",
"style": {
"navigationBarTitleText": "练一练"
}
},
{
"path": "pages/training/metronome",
"style": {
"navigationBarTitleText": "节拍器",
"navigationBarBackgroundColor": "#f8fafc",
"navigationBarTextStyle": "black",
"backgroundColor": "#f8fafc",
"disableScroll": true
}
}
],
"subPackages": [
@@ -121,6 +105,27 @@
}
}
]
},
{
"root": "training",
"pages": [
{
"path": "pages/index",
"style": {
"navigationBarTitleText": "练一练"
}
},
{
"path": "pages/metronome",
"style": {
"navigationBarTitleText": "节拍器",
"navigationBarBackgroundColor": "#f8fafc",
"navigationBarTextStyle": "black",
"backgroundColor": "#f8fafc",
"disableScroll": true
}
}
]
}
],
"tabBar": {
+7 -7
View File
@@ -16,9 +16,9 @@
* node scripts/generate-voice.mjs --voice 茉莉
* node scripts/generate-voice.mjs --force # 已存在的也重新生成
*
* 输出:
* src/static/training/voice/numbers/{1..30}.mp3
* src/static/training/voice/prompts/{key}.mp3
* 输出(分包):
* training/static/voice/numbers/{1..30}.mp3
* training/static/voice/prompts/{key}.mp3
*
* 需要 Node 18+(用内置 fetch
*/
@@ -32,7 +32,7 @@ const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const ROOT = join(__dirname, '..')
const VOICE_DIR = join(ROOT, 'src/static/training/voice')
const VOICE_DIR = join(ROOT, 'training/static/voice')
const NUMBERS_DIR = join(VOICE_DIR, 'numbers')
const PROMPTS_DIR = join(VOICE_DIR, 'prompts')
@@ -202,9 +202,9 @@ async function main() {
}
console.log('\n下一步:')
console.log(' 1. 准备 click 音 → src/static/training/audio/click.mp3')
console.log(' 2. 准备 BGM → src/static/training/bgm/{train-light,rest-meditation}.mp3')
console.log(' 3. npm run dev:mp-weixin → 进入 /pages/training/index')
console.log(' 1. 准备 click 音 → training/static/audio/click.mp3')
console.log(' 2. 准备 BGM → training/static/bgm/{train-light,rest-meditation}.mp3')
console.log(' 3. 编译运行 → 进入 /training/pages/index')
}
main().catch((e) => {
@@ -93,7 +93,7 @@ export function useMetronome(options: MetronomeOptions = {}) {
initialBpm = 80,
bpmMin = 40,
bpmMax = 240,
clickSrc = '/static/training/audio/click.mp3',
clickSrc = '/training/static/audio/click.mp3',
accentSrc,
poolSize = 4,
onBeat,
@@ -1,6 +1,6 @@
import { ref, onUnmounted } from 'vue'
const BGM_BASE = '/static/training/bgm'
const BGM_BASE = '/training/static/bgm'
export type BgmMode = 'none' | 'training' | 'resting'
@@ -1,6 +1,6 @@
import { onUnmounted } from 'vue'
const VOICE_BASE = '/static/training/voice'
const VOICE_BASE = '/training/static/voice'
export type VoicePromptKey =
| 'start'
@@ -151,10 +151,10 @@ import { onShow, onHide } from '@dcloudio/uni-app'
import ExerciseAnim from './components/exercise-anim.vue'
import { EXERCISE_PRESETS, getPresetById } from './exercises'
import type { ExercisePreset } from './exercises'
import { useMetronome } from '@/hooks/useMetronome'
import { useTrainingSession } from '@/hooks/useTrainingSession'
import { useVoiceCoach } from '@/hooks/useVoiceCoach'
import { useTrainingBgm } from '@/hooks/useTrainingBgm'
import { useMetronome } from '../hooks/useMetronome'
import { useTrainingSession } from '../hooks/useTrainingSession'
import { useVoiceCoach } from '../hooks/useVoiceCoach'
import { useTrainingBgm } from '../hooks/useTrainingBgm'
const equipmentList = ['哑铃', '握力环', '健身环', '徒手'] as const
type Equipment = (typeof equipmentList)[number]
@@ -286,7 +286,7 @@ const onSkipRest = () => {
}
const goMetronome = () => {
uni.navigateTo({ url: '/pages/training/metronome' })
uni.navigateTo({ url: '/training/pages/metronome' })
}
onHide(() => {
@@ -89,7 +89,7 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { onShow, onHide } from '@dcloudio/uni-app'
import { useMetronome } from '@/hooks/useMetronome'
import { useMetronome } from '../hooks/useMetronome'
/**
* 跑步动画视频(腾讯云 COS CDN)
@@ -145,8 +145,8 @@ const METER_OPTIONS = [
const metronome = useMetronome({
initialBpm: 80,
accentEvery: 4,
clickSrc: '/static/training/audio/click.mp3',
accentSrc: '/static/training/audio/click-wood.mp3',
clickSrc: '/training/static/audio/click.mp3',
accentSrc: '/training/static/audio/click-wood.mp3',
})
const {
bpm,
@@ -5,7 +5,7 @@
## 目录结构
```
src/static/training/
training/static/
├── audio/
│ └── click.mp3 # 节拍器主音(短促金属敲击声,约 50ms)
├── voice/
@@ -92,9 +92,9 @@ node scripts/generate-voice.mjs --force
## 在代码里被引用的位置
- `src/hooks/useMetronome.ts``audio/click.mp3`
- `src/hooks/useVoiceCoach.ts``voice/numbers/*.mp3``voice/prompts/*.mp3`
- `src/hooks/useTrainingBgm.ts``bgm/*.mp3`
- `training/hooks/useMetronome.ts``audio/click.mp3`
- `training/hooks/useVoiceCoach.ts``voice/numbers/*.mp3``voice/prompts/*.mp3`
- `training/hooks/useTrainingBgm.ts``bgm/*.mp3`
如需修改路径,修改对应 hook 文件里的常量即可。

Before

Width:  |  Height:  |  Size: 730 B

After

Width:  |  Height:  |  Size: 730 B