import AsyncStorage from '@react-native-async-storage/async-storage'; import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; export type PlanMode = 'daysOfWeek' | 'sessionsPerWeek'; export type PlanGoal = | 'postpartum_recovery' // 产后恢复 | 'fat_loss' // 减脂塑形 | 'posture_correction' // 体态矫正 | 'core_strength' // 核心力量 | 'flexibility' // 柔韧灵活 | 'rehab' // 康复保健 | 'stress_relief'; // 释压放松 export type TrainingPlan = { id: string; createdAt: string; // ISO startDate: string; // ISO (当天或下周一) mode: PlanMode; daysOfWeek: number[]; // 0(日) - 6(六) sessionsPerWeek: number; // 1..7 goal: PlanGoal | ''; startWeightKg?: number; preferredTimeOfDay?: 'morning' | 'noon' | 'evening' | ''; }; export type TrainingPlanState = { current?: TrainingPlan | null; draft: Omit; }; const STORAGE_KEY = '@training_plan'; function nextMondayISO(): string { const now = new Date(); const day = now.getDay(); const diff = (8 - day) % 7 || 7; // 距下周一的天数 const next = new Date(now); next.setDate(now.getDate() + diff); next.setHours(0, 0, 0, 0); return next.toISOString(); } const initialState: TrainingPlanState = { current: null, draft: { startDate: new Date(new Date().setHours(0, 0, 0, 0)).toISOString(), mode: 'daysOfWeek', daysOfWeek: [1, 3, 5], sessionsPerWeek: 3, goal: '', startWeightKg: undefined, preferredTimeOfDay: '', }, }; export const loadTrainingPlan = createAsyncThunk('trainingPlan/load', async () => { const str = await AsyncStorage.getItem(STORAGE_KEY); if (!str) return null; try { return JSON.parse(str) as TrainingPlan; } catch { return null; } }); export const saveTrainingPlan = createAsyncThunk( 'trainingPlan/save', async (_: void, { getState }) => { const s = (getState() as any).trainingPlan as TrainingPlanState; const draft = s.draft; const plan: TrainingPlan = { id: `plan_${Date.now()}`, createdAt: new Date().toISOString(), ...draft, }; await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(plan)); return plan; } ); const trainingPlanSlice = createSlice({ name: 'trainingPlan', initialState, reducers: { setMode(state, action: PayloadAction) { state.draft.mode = action.payload; }, toggleDayOfWeek(state, action: PayloadAction) { const d = action.payload; const set = new Set(state.draft.daysOfWeek); if (set.has(d)) set.delete(d); else set.add(d); state.draft.daysOfWeek = Array.from(set).sort(); }, setSessionsPerWeek(state, action: PayloadAction) { const n = Math.min(7, Math.max(1, action.payload)); state.draft.sessionsPerWeek = n; }, setGoal(state, action: PayloadAction) { state.draft.goal = action.payload; }, setStartWeight(state, action: PayloadAction) { state.draft.startWeightKg = action.payload; }, setStartDate(state, action: PayloadAction) { state.draft.startDate = action.payload; }, setPreferredTime(state, action: PayloadAction) { state.draft.preferredTimeOfDay = action.payload; }, setStartDateNextMonday(state) { state.draft.startDate = nextMondayISO(); }, resetDraft(state) { state.draft = initialState.draft; }, }, extraReducers: (builder) => { builder .addCase(loadTrainingPlan.fulfilled, (state, action) => { state.current = action.payload; // 若存在历史计划,初始化 draft 基于该计划(便于编辑) if (action.payload) { const { id, createdAt, ...rest } = action.payload; state.draft = { ...rest }; } }) .addCase(saveTrainingPlan.fulfilled, (state, action) => { state.current = action.payload; }); }, }); export const { setMode, toggleDayOfWeek, setSessionsPerWeek, setGoal, setStartWeight, setStartDate, setPreferredTime, setStartDateNextMonday, resetDraft, } = trainingPlanSlice.actions; export default trainingPlanSlice.reducer;