import DateTimePicker from '@react-native-community/datetimepicker'; import { useRouter } from 'expo-router'; import React, { useEffect, useMemo, useState } from 'react'; import { Modal, Platform, Pressable, SafeAreaView, ScrollView, StyleSheet, TextInput, View } from 'react-native'; import { ThemedText } from '@/components/ThemedText'; import { ThemedView } from '@/components/ThemedView'; import { HeaderBar } from '@/components/ui/HeaderBar'; import { palette } from '@/constants/Colors'; import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { clearError, loadPlans, saveDraftAsPlan, setGoal, setMode, setName, setPreferredTime, setSessionsPerWeek, setStartDate, setStartDateNextMonday, setStartWeight, toggleDayOfWeek, type PlanGoal } from '@/store/trainingPlanSlice'; const WEEK_DAYS = ['日', '一', '二', '三', '四', '五', '六']; const GOALS: { key: PlanGoal; title: string; desc: string }[] = [ { key: 'postpartum_recovery', title: '产后恢复', desc: '温和激活,核心重建' }, { key: 'posture_correction', title: '体态矫正', desc: '打开胸肩,改善圆肩驼背' }, { key: 'fat_loss', title: '减脂塑形', desc: '全身燃脂,线条雕刻' }, { key: 'core_strength', title: '核心力量', desc: '核心稳定,提升运动表现' }, { key: 'flexibility', title: '柔韧灵活', desc: '拉伸延展,释放紧张' }, { key: 'rehab', title: '康复保健', desc: '循序渐进,科学修复' }, { key: 'stress_relief', title: '释压放松', desc: '舒缓身心,改善睡眠' }, ]; export default function TrainingPlanCreateScreen() { const router = useRouter(); const dispatch = useAppDispatch(); const { draft, loading, error } = useAppSelector((s) => s.trainingPlan); const [weightInput, setWeightInput] = useState(''); const [datePickerVisible, setDatePickerVisible] = useState(false); const [pickerDate, setPickerDate] = useState(new Date()); useEffect(() => { dispatch(loadPlans()); }, [dispatch]); useEffect(() => { if (draft.startWeightKg && !weightInput) setWeightInput(String(draft.startWeightKg)); }, [draft.startWeightKg]); const selectedCount = draft.mode === 'daysOfWeek' ? draft.daysOfWeek.length : draft.sessionsPerWeek; const canSave = useMemo(() => { if (!draft.goal) return false; if (draft.mode === 'daysOfWeek' && draft.daysOfWeek.length === 0) return false; if (draft.mode === 'sessionsPerWeek' && draft.sessionsPerWeek <= 0) return false; return true; }, [draft]); const formattedStartDate = useMemo(() => { const d = new Date(draft.startDate); try { return new Intl.DateTimeFormat('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'short', }).format(d); } catch { return d.toLocaleDateString('zh-CN'); } }, [draft.startDate]); const handleSave = async () => { try { await dispatch(saveDraftAsPlan()).unwrap(); router.back(); } catch (error) { // 错误已经在Redux中处理,这里可以显示额外的用户反馈 console.error('保存训练计划失败:', error); } }; useEffect(() => { if (error) { // 3秒后自动清除错误 const timer = setTimeout(() => { dispatch(clearError()); }, 3000); return () => clearTimeout(timer); } }, [error, dispatch]); const openDatePicker = () => { const base = draft.startDate ? new Date(draft.startDate) : new Date(); base.setHours(0, 0, 0, 0); setPickerDate(base); setDatePickerVisible(true); }; const closeDatePicker = () => setDatePickerVisible(false); const onConfirmDate = (date: Date) => { const today = new Date(); today.setHours(0, 0, 0, 0); const picked = new Date(date); picked.setHours(0, 0, 0, 0); const finalDate = picked < today ? today : picked; dispatch(setStartDate(finalDate.toISOString())); closeDatePicker(); }; return ( router.back()} withSafeTop={false} transparent /> 制定你的训练计划 选择你的训练节奏与目标,我们将为你生成合适的普拉提安排。 {error && ( ⚠️ {error} )} 计划名称 dispatch(setName(text))} style={styles.nameInput} maxLength={50} /> 训练频率 dispatch(setMode('daysOfWeek'))} style={[styles.segmentItem, draft.mode === 'daysOfWeek' && styles.segmentItemActive]} > 按星期选择 dispatch(setMode('sessionsPerWeek'))} style={[styles.segmentItem, draft.mode === 'sessionsPerWeek' && styles.segmentItemActive]} > 每周次数 {draft.mode === 'daysOfWeek' ? ( {WEEK_DAYS.map((d, i) => { const active = draft.daysOfWeek.includes(i); return ( dispatch(toggleDayOfWeek(i))} style={[styles.dayChip, active && styles.dayChipActive]}> {d} ); })} ) : ( 每周训练 dispatch(setSessionsPerWeek(Math.max(1, draft.sessionsPerWeek - 1)))} style={styles.counterBtn}> - {draft.sessionsPerWeek} dispatch(setSessionsPerWeek(Math.min(7, draft.sessionsPerWeek + 1)))} style={styles.counterBtn}> + )} 已选择:{selectedCount} 次/周 训练目标 {GOALS.map((g) => { const active = draft.goal === g.key; return ( dispatch(setGoal(g.key))} style={[styles.goalItem, active && styles.goalItemActive]}> {g.title} {g.desc} ); })} 更多选项 开始日期 选择日期 dispatch(setStartDateNextMonday())} style={[styles.linkBtn, { marginLeft: 8 }]}> 下周一 {formattedStartDate} 开始体重 (kg) { setWeightInput(t); const v = Number(t); dispatch(setStartWeight(Number.isFinite(v) ? v : undefined)); }} style={styles.input} /> 偏好时间段 {(['morning', 'noon', 'evening', ''] as const).map((k) => ( dispatch(setPreferredTime(k))} style={[styles.segmentItemSmall, draft.preferredTimeOfDay === k && styles.segmentItemActiveSmall]}> {k === 'morning' ? '晨练' : k === 'noon' ? '午间' : k === 'evening' ? '晚间' : '不限'} ))} {loading ? '创建中...' : canSave ? '生成计划' : '请先选择目标/频率'} { if (Platform.OS === 'ios') { if (date) setPickerDate(date); } else { if (event.type === 'set' && date) { onConfirmDate(date); } else { closeDatePicker(); } } }} /> {Platform.OS === 'ios' && ( 取消 { onConfirmDate(pickerDate); }} style={[styles.modalBtn, styles.modalBtnPrimary]}> 确定 )} ); } const styles = StyleSheet.create({ safeArea: { flex: 1, backgroundColor: '#F7F8FA', }, container: { flex: 1, backgroundColor: '#F7F8FA', }, content: { paddingHorizontal: 20, paddingTop: 16, }, title: { fontSize: 28, fontWeight: '800', color: '#1A1A1A', lineHeight: 36, }, subtitle: { fontSize: 14, color: '#5E6468', marginTop: 6, marginBottom: 16, lineHeight: 20, }, card: { backgroundColor: '#FFFFFF', borderRadius: 16, padding: 16, marginTop: 14, shadowColor: '#000', shadowOpacity: 0.06, shadowRadius: 12, shadowOffset: { width: 0, height: 6 }, elevation: 3, }, cardTitle: { fontSize: 18, fontWeight: '700', color: '#0F172A', marginBottom: 12, }, segment: { flexDirection: 'row', backgroundColor: '#F1F5F9', padding: 4, borderRadius: 999, }, segmentItem: { flex: 1, borderRadius: 999, paddingVertical: 10, alignItems: 'center', }, segmentItemActive: { backgroundColor: palette.primary, }, segmentText: { fontSize: 14, color: '#475569', fontWeight: '600', }, segmentTextActive: { color: palette.ink, }, weekRow: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 14, }, dayChip: { width: 44, height: 44, borderRadius: 12, backgroundColor: '#F1F5F9', alignItems: 'center', justifyContent: 'center', }, dayChipActive: { backgroundColor: '#E0F8A2', borderWidth: 2, borderColor: palette.primary, }, dayChipText: { fontSize: 16, color: '#334155', fontWeight: '700', }, dayChipTextActive: { color: '#0F172A', }, sliderRow: { flexDirection: 'row', alignItems: 'center', marginTop: 16, }, sliderLabel: { fontSize: 16, color: '#334155', fontWeight: '700', }, counter: { flexDirection: 'row', alignItems: 'center', marginLeft: 12, }, counterBtn: { width: 36, height: 36, borderRadius: 999, backgroundColor: '#F1F5F9', alignItems: 'center', justifyContent: 'center', }, counterBtnText: { fontSize: 18, fontWeight: '800', color: '#0F172A', }, counterValue: { width: 44, textAlign: 'center', fontSize: 18, fontWeight: '800', color: '#0F172A', }, sliderSuffix: { marginLeft: 8, color: '#475569', }, helper: { marginTop: 10, color: '#5E6468', }, goalGrid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between', }, goalItem: { width: '48%', backgroundColor: '#F8FAFC', borderRadius: 14, padding: 12, marginBottom: 12, }, goalItemActive: { backgroundColor: '#E0F8A2', borderColor: palette.primary, borderWidth: 2, }, goalTitle: { fontSize: 16, fontWeight: '800', color: '#0F172A', }, goalTitleActive: { color: '#0F172A', }, goalDesc: { marginTop: 6, fontSize: 12, color: '#5E6468', lineHeight: 16, }, rowBetween: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginTop: 6, }, rowRight: { flexDirection: 'row', alignItems: 'center', }, label: { fontSize: 14, color: '#0F172A', fontWeight: '700', }, linkBtn: { paddingHorizontal: 10, paddingVertical: 6, borderRadius: 999, backgroundColor: '#F1F5F9', }, linkText: { color: '#334155', fontWeight: '700', }, dateHint: { marginTop: 6, color: '#5E6468', }, input: { marginLeft: 12, backgroundColor: '#F1F5F9', paddingHorizontal: 10, paddingVertical: 8, borderRadius: 8, minWidth: 88, textAlign: 'right', color: '#0F172A', }, segmentSmall: { flexDirection: 'row', backgroundColor: '#F1F5F9', padding: 3, borderRadius: 999, }, segmentItemSmall: { borderRadius: 999, paddingVertical: 6, paddingHorizontal: 10, marginHorizontal: 3, }, segmentItemActiveSmall: { backgroundColor: palette.primary, }, segmentTextSmall: { fontSize: 12, color: '#475569', fontWeight: '700', }, segmentTextActiveSmall: { color: palette.ink, }, primaryBtn: { marginTop: 18, backgroundColor: palette.primary, paddingVertical: 14, borderRadius: 14, alignItems: 'center', }, primaryBtnDisabled: { opacity: 0.5, }, primaryBtnText: { color: palette.ink, fontSize: 16, fontWeight: '800', }, modalBackdrop: { ...StyleSheet.absoluteFillObject, backgroundColor: 'rgba(0,0,0,0.35)', }, modalSheet: { position: 'absolute', left: 0, right: 0, bottom: 0, padding: 16, backgroundColor: '#FFFFFF', borderTopLeftRadius: 16, borderTopRightRadius: 16, }, modalActions: { flexDirection: 'row', justifyContent: 'flex-end', marginTop: 8, gap: 12, }, modalBtn: { paddingHorizontal: 14, paddingVertical: 10, borderRadius: 10, backgroundColor: '#F1F5F9', }, modalBtnPrimary: { backgroundColor: palette.primary, }, modalBtnText: { color: '#334155', fontWeight: '700', }, modalBtnTextPrimary: { color: palette.ink, }, // 计划名称输入框 nameInput: { backgroundColor: '#F1F5F9', paddingHorizontal: 12, paddingVertical: 12, borderRadius: 8, fontSize: 16, color: '#0F172A', marginTop: 8, }, // 错误状态 errorContainer: { backgroundColor: 'rgba(237,71,71,0.1)', borderRadius: 12, padding: 16, marginTop: 16, borderWidth: 1, borderColor: 'rgba(237,71,71,0.2)', }, errorText: { fontSize: 14, color: '#ED4747', fontWeight: '600', textAlign: 'center', }, });