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:
@@ -0,0 +1,133 @@
|
||||
import { ref, computed, onUnmounted } from 'vue'
|
||||
|
||||
export type SessionPhase = 'idle' | 'training' | 'resting' | 'done'
|
||||
|
||||
export interface TrainingSessionConfig {
|
||||
sets: number
|
||||
reps: number
|
||||
restSec: number
|
||||
beatsPerRep?: number
|
||||
onRepComplete?: (currentRep: number, totalReps: number) => void
|
||||
onSetComplete?: (currentSet: number, totalSets: number) => void
|
||||
onEnterRest?: (restSec: number) => void
|
||||
onExitRest?: () => void
|
||||
onDone?: () => void
|
||||
onCountdownTick?: (remainingSec: number) => void
|
||||
}
|
||||
|
||||
export function useTrainingSession() {
|
||||
const phase = ref<SessionPhase>('idle')
|
||||
const currentSet = ref<number>(0)
|
||||
const currentRep = ref<number>(0)
|
||||
const restRemainingSec = ref<number>(0)
|
||||
const beatCounter = ref<number>(0)
|
||||
|
||||
let config: TrainingSessionConfig | null = null
|
||||
let restTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const totalReps = computed(() => config?.reps ?? 0)
|
||||
const totalSets = computed(() => config?.sets ?? 0)
|
||||
const beatsPerRep = computed(() => config?.beatsPerRep ?? 2)
|
||||
|
||||
const isTraining = computed(() => phase.value === 'training')
|
||||
const isResting = computed(() => phase.value === 'resting')
|
||||
const isDone = computed(() => phase.value === 'done')
|
||||
|
||||
const start = (cfg: TrainingSessionConfig) => {
|
||||
config = cfg
|
||||
phase.value = 'training'
|
||||
currentSet.value = 1
|
||||
currentRep.value = 0
|
||||
beatCounter.value = 0
|
||||
restRemainingSec.value = 0
|
||||
}
|
||||
|
||||
const onBeat = () => {
|
||||
if (phase.value !== 'training' || !config) return
|
||||
|
||||
beatCounter.value++
|
||||
|
||||
if (beatCounter.value % beatsPerRep.value !== 0) return
|
||||
|
||||
currentRep.value++
|
||||
config.onRepComplete?.(currentRep.value, totalReps.value)
|
||||
|
||||
if (currentRep.value >= totalReps.value) {
|
||||
config.onSetComplete?.(currentSet.value, totalSets.value)
|
||||
|
||||
if (currentSet.value >= totalSets.value) {
|
||||
phase.value = 'done'
|
||||
config.onDone?.()
|
||||
return
|
||||
}
|
||||
|
||||
enterRest()
|
||||
}
|
||||
}
|
||||
|
||||
const enterRest = () => {
|
||||
if (!config) return
|
||||
phase.value = 'resting'
|
||||
restRemainingSec.value = config.restSec
|
||||
config.onEnterRest?.(config.restSec)
|
||||
|
||||
restTimer = setInterval(() => {
|
||||
restRemainingSec.value--
|
||||
config?.onCountdownTick?.(restRemainingSec.value)
|
||||
if (restRemainingSec.value <= 0) {
|
||||
exitRest()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const exitRest = () => {
|
||||
if (restTimer) {
|
||||
clearInterval(restTimer)
|
||||
restTimer = null
|
||||
}
|
||||
if (!config) return
|
||||
|
||||
currentSet.value++
|
||||
currentRep.value = 0
|
||||
beatCounter.value = 0
|
||||
phase.value = 'training'
|
||||
config.onExitRest?.()
|
||||
}
|
||||
|
||||
const skipRest = () => {
|
||||
if (phase.value !== 'resting') return
|
||||
exitRest()
|
||||
}
|
||||
|
||||
const stop = () => {
|
||||
if (restTimer) {
|
||||
clearInterval(restTimer)
|
||||
restTimer = null
|
||||
}
|
||||
phase.value = 'idle'
|
||||
currentSet.value = 0
|
||||
currentRep.value = 0
|
||||
beatCounter.value = 0
|
||||
restRemainingSec.value = 0
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
if (restTimer) clearInterval(restTimer)
|
||||
})
|
||||
|
||||
return {
|
||||
phase,
|
||||
currentSet,
|
||||
currentRep,
|
||||
restRemainingSec,
|
||||
totalReps,
|
||||
totalSets,
|
||||
isTraining,
|
||||
isResting,
|
||||
isDone,
|
||||
start,
|
||||
stop,
|
||||
skipRest,
|
||||
onBeat,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user