export type ExerciseSet = { durationSec?: number; // 计时型(秒) reps?: number; // 次数型(可选) restSecAfter?: number; // 组间休息 }; export type Exercise = { key: string; name: string; description: string; videoUrl?: string; sets: ExerciseSet[]; tips?: string[]; }; export type DayPlan = { dayNumber: number; // 1..30 type: 'training' | 'recovery' | 'rest'; title: string; focus: string; // 训练重点 exercises: Exercise[]; // rest/recovery 可为空数组 }; export type PilatesLevel = 'beginner' | 'intermediate' | 'advanced'; const BASE_EXERCISES: Omit[] = [ { key: 'hundred', name: '百次拍击 (The Hundred)', description: '仰卧,抬腿至桌面位,核心收紧,小幅快速摆动手臂并配合呼吸。', videoUrl: undefined, }, { key: 'single_leg_stretch', name: '单腿伸展 (Single Leg Stretch)', description: '仰卧,交替伸直一条腿,另一条腿屈膝抱向胸口,核心稳定。', }, { key: 'double_leg_stretch', name: '双腿伸展 (Double Leg Stretch)', description: '仰卧同时伸直双腿与双臂,呼气收回环抱膝盖。', }, { key: 'roll_up', name: '卷起 (Roll Up)', description: '由仰卧卷起至坐并前屈,再控制还原,强调脊柱分节活动。', }, { key: 'spine_twist', name: '脊柱扭转 (Spine Twist)', description: '坐姿,躯干直立,左右控制旋转,强调轴向延展与核心稳定。', }, { key: 'bridge', name: '桥式 (Bridge)', description: '仰卧,卷尾抬起骨盆至肩桥位,感受臀腿后侧发力,控制还原。', }, { key: 'side_leg_lift', name: '侧抬腿 (Side Leg Lift)', description: '侧卧,髋稳定,抬高上侧腿,控制下放。', }, { key: 'swimming', name: '游泳式 (Swimming)', description: '俯卧,交替抬起对侧手臂与腿,保持脊柱中立并延展。', }, { key: 'cat_cow', name: '猫牛式 (Cat-Cow)', description: '四点支撑,呼吸带动脊柱屈伸,作为热身或整理放松。', }, { key: 'saw', name: '锯式 (Saw)', description: '坐姿分腿,旋转并前屈,斜向触碰对侧脚尖。', }, { key: 'plank', name: '平板支撑 (Plank)', description: '前臂/掌支撑,身体成直线,保持核心紧致。', }, ]; function buildSets(level: PilatesLevel, baseSec: number, setCount: number): ExerciseSet[] { const effort = baseSec + (level === 'intermediate' ? 10 : level === 'advanced' ? 20 : 0); const rest = 15; return Array.from({ length: setCount }).map(() => ({ durationSec: effort, restSecAfter: rest })); } function pickExercises(keys: string[], level: PilatesLevel, baseSec: number, setCount: number): Exercise[] { return keys.map((k) => { const base = BASE_EXERCISES.find((e) => e.key === k)!; return { ...base, sets: buildSets(level, baseSec, setCount), tips: [ '保持呼吸与动作节奏一致', '核心始终收紧,避免腰椎塌陷', '以控制优先于速度,专注动作质量', ], }; }); } export function generatePilates30DayPlan(level: PilatesLevel = 'beginner'): DayPlan[] { // 周期化:每7天安排恢复/拉伸;逐周增加时长或组数 const plan: DayPlan[] = []; for (let d = 1; d <= 30; d += 1) { const weekIndex = Math.ceil(d / 7); // 1..5 const isRecovery = d % 7 === 0 || d === 14 || d === 21 || d === 28; // 每周末恢复 if (isRecovery) { plan.push({ dayNumber: d, type: 'recovery', title: '恢复与拉伸日', focus: '呼吸、脊柱分节、柔韧性', exercises: pickExercises(['cat_cow', 'saw'], level, 40, 2), }); continue; } // 训练参数随周数递增 const baseSec = 35 + (weekIndex - 1) * 5; // 35,40,45,50,55 const setCount = 2 + (weekIndex > 3 ? 1 : 0); // 第4周开始 3 组 // 交替不同侧重点 let title = '核心激活与稳定'; let focus = '腹横肌、骨盆稳定、呼吸控制'; let keys: string[] = ['hundred', 'single_leg_stretch', 'double_leg_stretch', 'roll_up', 'spine_twist']; if (d % 3 === 0) { title = '后链力量与体态'; focus = '臀腿后侧、胸椎伸展、姿态矫正'; keys = ['bridge', 'swimming', 'plank', 'spine_twist', 'roll_up']; } else if (d % 3 === 2) { title = '髋稳定与侧链'; focus = '中臀肌、侧链控制、骨盆稳定'; keys = ['side_leg_lift', 'single_leg_stretch', 'bridge', 'saw', 'plank']; } const exercises = pickExercises(keys, level, baseSec, setCount); plan.push({ dayNumber: d, type: 'training', title, focus, exercises }); } return plan; } export function estimateSessionMinutes(day: DayPlan): number { if (day.type !== 'training' && day.type !== 'recovery') return 0; const totalSec = day.exercises.reduce((sum, ex) => { return ( sum + ex.sets.reduce((s, st) => s + (st.durationSec ?? 0) + (st.restSecAfter ?? 0), 0) ); }, 0); return Math.max(10, Math.round(totalSec / 60)); } // 用户自定义配置(用于挑战页可调节) export type ExerciseCustomConfig = { key: string; enabled: boolean; sets: number; // 目标组数(计时型) durationSec?: number; // 每组计时秒数(若为空可按默认) reps?: number; // 每组次数(可选) }; export function buildDefaultCustomFromPlan(day: DayPlan): ExerciseCustomConfig[] { return (day.exercises || []).map((ex) => ({ key: ex.key, enabled: true, sets: Math.max(1, ex.sets?.length || 2), durationSec: ex.sets?.[0]?.durationSec ?? 40, reps: ex.sets?.[0]?.reps, })); } export function estimateSessionMinutesWithCustom(day: DayPlan, custom: ExerciseCustomConfig[] | undefined): number { if (!custom || custom.length === 0) return estimateSessionMinutes(day); const restSec = 15; // 估算默认休息 const totalSec = custom.reduce((sum, c) => { if (!c.enabled) return sum; const perSet = (c.durationSec ?? 40) + restSec; return sum + perSet * Math.max(1, c.sets); }, 0); return Math.max(5, Math.round(totalSec / 60)); }