import { Ionicons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import { useLocalSearchParams, useRouter } from 'expo-router'; import React, { useEffect, useMemo, useState } from 'react'; import { Alert, FlatList, Modal, SafeAreaView, StyleSheet, Switch, Text, TextInput, TouchableOpacity, View } from 'react-native'; import Animated, { FadeInUp } from 'react-native-reanimated'; import { ThemedText } from '@/components/ThemedText'; import { HeaderBar } from '@/components/ui/HeaderBar'; import { palette } from '@/constants/Colors'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { buildClassicalSession } from '@/utils/classicalSession'; // 训练计划排课项目类型 export interface ScheduleExercise { key: string; name: string; category: string; sets: number; reps?: number; durationSec?: number; restSec?: number; note?: string; itemType?: 'exercise' | 'rest' | 'note'; completed?: boolean; } // 训练计划排课数据 export interface PlanSchedule { planId: string; exercises: ScheduleExercise[]; note?: string; lastModified: string; } const GOAL_TEXT: Record = { postpartum_recovery: { title: '产后恢复', color: '#9BE370', description: '温和激活,核心重建' }, fat_loss: { title: '减脂塑形', color: '#FFB86B', description: '全身燃脂,线条雕刻' }, posture_correction: { title: '体态矫正', color: '#95CCE3', description: '打开胸肩,改善圆肩驼背' }, core_strength: { title: '核心力量', color: '#A48AED', description: '核心稳定,提升运动表现' }, flexibility: { title: '柔韧灵活', color: '#B0F2A7', description: '拉伸延展,释放紧张' }, rehab: { title: '康复保健', color: '#FF8E9E', description: '循序渐进,科学修复' }, stress_relief: { title: '释压放松', color: '#9BD1FF', description: '舒缓身心,改善睡眠' }, }; // 动态背景组件 function DynamicBackground({ color }: { color: string }) { return ( ); } export default function PlanScheduleScreen() { const router = useRouter(); const dispatch = useAppDispatch(); const params = useLocalSearchParams<{ planId?: string; newExercise?: string }>(); const { plans } = useAppSelector((s) => s.trainingPlan); const planId = params.planId; const plan = useMemo(() => plans.find(p => p.id === planId), [plans, planId]); // 排课数据状态 const [exercises, setExercises] = useState([]); const [scheduleNote, setScheduleNote] = useState(''); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); // 一键排课配置 const [genVisible, setGenVisible] = useState(false); const [genLevel, setGenLevel] = useState<'beginner' | 'intermediate' | 'advanced'>('beginner'); const [genWithRests, setGenWithRests] = useState(true); const [genWithNotes, setGenWithNotes] = useState(true); const [genRest, setGenRest] = useState('30'); const goalConfig = plan ? (GOAL_TEXT[plan.goal] || { title: '训练计划', color: palette.primary, description: '开始你的训练之旅' }) : null; useEffect(() => { if (!plan) { Alert.alert('错误', '找不到指定的训练计划', [ { text: '确定', onPress: () => router.back() } ]); return; } // TODO: 从存储中加载已有的排课数据 // loadPlanSchedule(planId); }, [plan, planId]); // 处理从选择页面传回的新动作 useEffect(() => { if (params.newExercise) { try { const newExercise: ScheduleExercise = JSON.parse(params.newExercise); setExercises(prev => [...prev, newExercise]); setHasUnsavedChanges(true); // 清除路由参数,避免重复添加 router.setParams({ newExercise: undefined } as any); } catch (error) { console.error('解析新动作数据失败:', error); } } }, [params.newExercise]); const handleSave = async () => { if (!plan) return; try { // TODO: 保存排课数据到存储 const scheduleData: PlanSchedule = { planId: plan.id, exercises, note: scheduleNote, lastModified: new Date().toISOString(), }; console.log('保存排课数据:', scheduleData); setHasUnsavedChanges(false); Alert.alert('保存成功', '训练计划排课已保存'); } catch (error) { console.error('保存排课失败:', error); Alert.alert('保存失败', '请稍后重试'); } }; const handleAddExercise = () => { router.push(`/training-plan/schedule/select?planId=${planId}` as any); }; const handleRemoveExercise = (key: string) => { Alert.alert('确认移除', '确定要移除该动作吗?', [ { text: '取消', style: 'cancel' }, { text: '移除', style: 'destructive', onPress: () => { setExercises(prev => prev.filter(ex => ex.key !== key)); setHasUnsavedChanges(true); }, }, ]); }; const handleToggleCompleted = (key: string) => { setExercises(prev => prev.map(ex => ex.key === key ? { ...ex, completed: !ex.completed } : ex )); setHasUnsavedChanges(true); }; const onGenerate = () => { const restSec = Math.max(10, Math.min(120, parseInt(genRest || '30', 10))); const { items, note } = buildClassicalSession({ withSectionRests: genWithRests, restSeconds: restSec, withNotes: genWithNotes, level: genLevel }); // 转换为排课格式 const scheduleItems: ScheduleExercise[] = items.map((item, index) => ({ key: `generated_${Date.now()}_${index}`, name: item.name, category: item.category, sets: item.sets, reps: item.reps, durationSec: item.durationSec, restSec: item.restSec, note: item.note, itemType: item.itemType, completed: false, })); setExercises(scheduleItems); setScheduleNote(note || ''); setHasUnsavedChanges(true); setGenVisible(false); Alert.alert('排课已生成', '已为你生成经典普拉提序列,可继续调整。'); }; if (!plan || !goalConfig) { return ( router.back()} /> 找不到指定的训练计划 ); } return ( {/* 动态背景 */} router.back()} withSafeTop={false} tone='light' transparent={true} right={hasUnsavedChanges ? ( 保存 ) : undefined} /> {/* 计划信息头部 */} {goalConfig.title} {goalConfig.description} {/* 操作按钮区域 */} 添加动作 setGenVisible(true)} > 一键排课 {/* 动作列表 */} item.key} contentContainerStyle={styles.listContent} showsVerticalScrollIndicator={false} ListEmptyComponent={ 💪 还没有添加任何动作 点击"添加动作"开始排课,或使用"一键排课"快速生成 } renderItem={({ item, index }) => { const isRest = item.itemType === 'rest'; const isNote = item.itemType === 'note'; if (isRest || isNote) { return ( {isRest ? `间隔休息 ${item.restSec ?? 30}s` : (item.note || '提示')} handleRemoveExercise(item.key)} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} > ); } return ( {item.name} {item.category} 组数 {item.sets} {item.reps ? ` · 每组 ${item.reps} 次` : ''} {item.durationSec ? ` · 每组 ${item.durationSec}s` : ''} handleToggleCompleted(item.key)} hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }} > handleRemoveExercise(item.key)} > 移除 ); }} /> {/* 一键排课配置弹窗 */} setGenVisible(false)}> setGenVisible(false)}> e.stopPropagation() as any}> 一键排课配置 强度水平 {(['beginner', 'intermediate', 'advanced'] as const).map((lv) => ( setGenLevel(lv)} > {lv === 'beginner' ? '入门' : lv === 'intermediate' ? '进阶' : '高级'} ))} 段间休息 插入操作提示 休息秒数 生成训练计划 ); } const styles = StyleSheet.create({ safeArea: { flex: 1, }, contentWrapper: { flex: 1, }, content: { flex: 1, paddingHorizontal: 20, }, // 动态背景 backgroundOrb: { position: 'absolute', width: 300, height: 300, borderRadius: 150, top: -150, right: -100, }, backgroundOrb2: { position: 'absolute', width: 400, height: 400, borderRadius: 200, bottom: -200, left: -150, }, // 计划信息头部 planHeader: { flexDirection: 'row', alignItems: 'center', padding: 16, borderRadius: 16, marginBottom: 16, }, planColorIndicator: { width: 4, height: 40, borderRadius: 2, marginRight: 12, }, planInfo: { flex: 1, }, planTitle: { fontSize: 18, fontWeight: '800', color: '#192126', marginBottom: 4, }, planDescription: { fontSize: 13, color: '#5E6468', opacity: 0.8, }, // 操作按钮 actionRow: { flexDirection: 'row', gap: 12, marginBottom: 20, }, primaryBtn: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 12, borderRadius: 12, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 4, }, primaryBtnText: { color: '#FFFFFF', fontSize: 14, fontWeight: '700', }, secondaryBtn: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 12, borderRadius: 12, borderWidth: 1.5, backgroundColor: '#FFFFFF', }, secondaryBtnText: { fontSize: 14, fontWeight: '700', }, // 保存按钮 saveBtn: { backgroundColor: palette.primary, paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20, shadowColor: palette.primary, shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.3, shadowRadius: 4, elevation: 4, }, saveBtnText: { color: palette.ink, fontWeight: '800', fontSize: 14, }, // 列表 listContent: { paddingBottom: 40, }, // 空状态 emptyContainer: { alignItems: 'center', justifyContent: 'center', paddingVertical: 60, }, emptyIcon: { width: 80, height: 80, borderRadius: 40, alignItems: 'center', justifyContent: 'center', marginBottom: 16, }, emptyIconText: { fontSize: 32, }, emptyText: { fontSize: 18, color: '#192126', fontWeight: '600', marginBottom: 4, }, emptySubtext: { fontSize: 14, color: '#5E6468', textAlign: 'center', lineHeight: 20, }, // 动作卡片 exerciseCard: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 16, marginBottom: 12, shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 12, shadowOffset: { width: 0, height: 6 }, elevation: 3, }, exerciseContent: { flexDirection: 'row', alignItems: 'center', }, exerciseInfo: { flex: 1, }, exerciseName: { fontSize: 16, fontWeight: '800', color: '#192126', marginBottom: 4, }, exerciseCategory: { fontSize: 12, color: '#888F92', marginBottom: 4, }, exerciseMeta: { fontSize: 12, color: '#5E6468', }, exerciseActions: { flexDirection: 'row', alignItems: 'center', gap: 12, }, completeBtn: { padding: 4, }, removeBtn: { backgroundColor: '#F3F4F6', paddingHorizontal: 10, paddingVertical: 6, borderRadius: 8, }, removeBtnText: { color: '#384046', fontWeight: '700', fontSize: 12, }, // 内联项目(休息、提示) inlineRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 10, }, inlineBadge: { marginLeft: 6, borderWidth: 1, borderColor: '#E5E7EB', borderRadius: 999, paddingVertical: 6, paddingHorizontal: 10, flex: 1, }, inlineBadgeRest: { backgroundColor: '#F8FAFC', }, inlineBadgeNote: { backgroundColor: '#F9FAFB', }, inlineText: { fontSize: 12, fontWeight: '700', }, inlineTextItalic: { fontSize: 12, fontStyle: 'italic', }, inlineRemoveBtn: { marginLeft: 6, padding: 4, borderRadius: 999, }, // 错误状态 errorContainer: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 20, }, errorText: { fontSize: 16, color: '#ED4747', fontWeight: '600', }, // 弹窗样式 modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.35)', alignItems: 'center', justifyContent: 'flex-end', }, modalSheet: { width: '100%', backgroundColor: '#FFFFFF', borderTopLeftRadius: 16, borderTopRightRadius: 16, paddingHorizontal: 16, paddingTop: 14, paddingBottom: 24, }, modalTitle: { fontSize: 16, fontWeight: '800', marginBottom: 16, color: '#192126', }, modalLabel: { fontSize: 12, color: '#888F92', marginBottom: 8, fontWeight: '600', }, segmentedRow: { flexDirection: 'row', gap: 8, marginBottom: 16, }, segment: { flex: 1, borderRadius: 999, borderWidth: 1, borderColor: '#E5E7EB', paddingVertical: 8, alignItems: 'center', }, segmentText: { fontWeight: '700', color: '#384046', }, switchRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12, }, switchLabel: { fontWeight: '700', color: '#384046', }, inputRow: { marginBottom: 20, }, inputLabel: { fontSize: 12, color: '#888F92', marginBottom: 8, fontWeight: '600', }, input: { height: 40, borderWidth: 1, borderColor: '#E5E7EB', borderRadius: 10, paddingHorizontal: 12, color: '#384046', }, generateBtn: { paddingVertical: 12, borderRadius: 12, alignItems: 'center', }, generateBtnText: { color: '#FFFFFF', fontWeight: '800', fontSize: 14, }, });