import { workoutsApi, type AddWorkoutExerciseDto, type CompleteWorkoutExerciseDto, type StartWorkoutDto, type StartWorkoutExerciseDto, type UpdateWorkoutExerciseDto, type WorkoutExercise, type WorkoutSession, type WorkoutSessionStatsResponse, } from '@/services/workoutsApi'; import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; export interface WorkoutState { // 当前训练会话 currentSession: WorkoutSession | null; exercises: WorkoutExercise[]; stats: WorkoutSessionStatsResponse | null; // 历史训练会话 sessions: WorkoutSession[]; sessionsPagination: { page: number; limit: number; total: number; totalPages: number; } | null; // 加载状态 loading: boolean; exerciseLoading: string | null; // 正在操作的exercise ID error: string | null; } const initialState: WorkoutState = { currentSession: null, exercises: [], stats: null, sessions: [], sessionsPagination: null, loading: false, exerciseLoading: null, error: null, }; // ==================== 异步Action定义 ==================== // 获取今日训练 export const loadTodayWorkout = createAsyncThunk( 'workout/loadTodayWorkout', async (_, { rejectWithValue }) => { try { const session = await workoutsApi.getTodayWorkout(); return session; } catch (error: any) { return rejectWithValue(error.message || '获取今日训练失败'); } } ); // 开始训练会话 export const startWorkoutSession = createAsyncThunk( 'workout/startSession', async ({ sessionId, dto }: { sessionId: string; dto?: StartWorkoutDto }, { rejectWithValue }) => { try { const session = await workoutsApi.startSession(sessionId, dto); return session; } catch (error: any) { return rejectWithValue(error.message || '开始训练失败'); } } ); // 开始训练动作 export const startWorkoutExercise = createAsyncThunk( 'workout/startExercise', async ({ sessionId, exerciseId, dto }: { sessionId: string; exerciseId: string; dto?: StartWorkoutExerciseDto }, { rejectWithValue }) => { try { const exercise = await workoutsApi.startExercise(sessionId, exerciseId, dto); return exercise; } catch (error: any) { return rejectWithValue(error.message || '开始动作失败'); } } ); // 完成训练动作 export const completeWorkoutExercise = createAsyncThunk( 'workout/completeExercise', async ({ sessionId, exerciseId, dto }: { sessionId: string; exerciseId: string; dto: CompleteWorkoutExerciseDto }, { rejectWithValue, getState }) => { try { const exercise = await workoutsApi.completeExercise(sessionId, exerciseId, dto); // 完成动作后重新获取会话详情(检查是否自动完成) const updatedSession = await workoutsApi.getSessionDetail(sessionId); return { exercise, updatedSession }; } catch (error: any) { return rejectWithValue(error.message || '完成动作失败'); } } ); // 跳过训练动作 export const skipWorkoutExercise = createAsyncThunk( 'workout/skipExercise', async ({ sessionId, exerciseId }: { sessionId: string; exerciseId: string }, { rejectWithValue }) => { try { const exercise = await workoutsApi.skipExercise(sessionId, exerciseId); // 跳过动作后重新获取会话详情(检查是否自动完成) const updatedSession = await workoutsApi.getSessionDetail(sessionId); return { exercise, updatedSession }; } catch (error: any) { return rejectWithValue(error.message || '跳过动作失败'); } } ); // 更新训练动作 export const updateWorkoutExercise = createAsyncThunk( 'workout/updateExercise', async ({ sessionId, exerciseId, dto }: { sessionId: string; exerciseId: string; dto: UpdateWorkoutExerciseDto }, { rejectWithValue }) => { try { const exercise = await workoutsApi.updateExercise(sessionId, exerciseId, dto); return exercise; } catch (error: any) { return rejectWithValue(error.message || '更新动作失败'); } } ); // 获取训练统计 export const loadWorkoutStats = createAsyncThunk( 'workout/loadStats', async (sessionId: string, { rejectWithValue }) => { try { const stats = await workoutsApi.getSessionStats(sessionId); return stats; } catch (error: any) { return rejectWithValue(error.message || '获取统计数据失败'); } } ); // 获取训练会话列表 export const loadWorkoutSessions = createAsyncThunk( 'workout/loadSessions', async ({ page = 1, limit = 10 }: { page?: number; limit?: number } = {}, { rejectWithValue }) => { try { const result = await workoutsApi.getSessions(page, limit); return result; } catch (error: any) { return rejectWithValue(error.message || '获取训练列表失败'); } } ); // 添加动作到训练会话 export const addWorkoutExercise = createAsyncThunk( 'workout/addExercise', async ({ sessionId, dto }: { sessionId: string; dto: AddWorkoutExerciseDto }, { rejectWithValue }) => { try { const exercise = await workoutsApi.addExercise(sessionId, dto); return exercise; } catch (error: any) { return rejectWithValue(error.message || '添加动作失败'); } } ); // 删除训练会话 export const deleteWorkoutSession = createAsyncThunk( 'workout/deleteSession', async (sessionId: string, { rejectWithValue }) => { try { await workoutsApi.deleteSession(sessionId); return sessionId; } catch (error: any) { return rejectWithValue(error.message || '删除训练会话失败'); } } ); // ==================== Slice定义 ==================== const workoutSlice = createSlice({ name: 'workout', initialState, reducers: { clearWorkoutError(state) { state.error = null; }, clearCurrentWorkout(state) { state.currentSession = null; state.exercises = []; state.stats = null; }, // 本地更新exercise状态(用于乐观更新) updateExerciseLocally(state, action: PayloadAction & { id: string }>) { const { id, ...updates } = action.payload; const exerciseIndex = state.exercises.findIndex(ex => ex.id === id); if (exerciseIndex !== -1) { Object.assign(state.exercises[exerciseIndex], updates); } }, }, extraReducers: (builder) => { builder // loadTodayWorkout .addCase(loadTodayWorkout.pending, (state) => { state.loading = true; state.error = null; }) .addCase(loadTodayWorkout.fulfilled, (state, action) => { state.loading = false; if (action.payload) { state.currentSession = action.payload; state.exercises = action.payload.exercises || []; } }) .addCase(loadTodayWorkout.rejected, (state, action) => { state.loading = false; state.error = action.payload as string; }) // startWorkoutSession .addCase(startWorkoutSession.pending, (state) => { state.loading = true; state.error = null; }) .addCase(startWorkoutSession.fulfilled, (state, action) => { state.loading = false; state.currentSession = action.payload; }) .addCase(startWorkoutSession.rejected, (state, action) => { state.loading = false; state.error = action.payload as string; }) // startWorkoutExercise .addCase(startWorkoutExercise.pending, (state, action) => { state.exerciseLoading = action.meta.arg.exerciseId; state.error = null; }) .addCase(startWorkoutExercise.fulfilled, (state, action) => { state.exerciseLoading = null; const exerciseIndex = state.exercises.findIndex(ex => ex.id === action.payload.id); if (exerciseIndex !== -1) { state.exercises[exerciseIndex] = action.payload; } }) .addCase(startWorkoutExercise.rejected, (state, action) => { state.exerciseLoading = null; state.error = action.payload as string; }) // completeWorkoutExercise .addCase(completeWorkoutExercise.pending, (state, action) => { state.exerciseLoading = action.meta.arg.exerciseId; state.error = null; }) .addCase(completeWorkoutExercise.fulfilled, (state, action) => { state.exerciseLoading = null; const { exercise, updatedSession } = action.payload; // 更新exercise const exerciseIndex = state.exercises.findIndex(ex => ex.id === exercise.id); if (exerciseIndex !== -1) { state.exercises[exerciseIndex] = exercise; } // 更新session(可能已自动完成) state.currentSession = updatedSession; }) .addCase(completeWorkoutExercise.rejected, (state, action) => { state.exerciseLoading = null; state.error = action.payload as string; }) // skipWorkoutExercise .addCase(skipWorkoutExercise.pending, (state, action) => { state.exerciseLoading = action.meta.arg.exerciseId; state.error = null; }) .addCase(skipWorkoutExercise.fulfilled, (state, action) => { state.exerciseLoading = null; const { exercise, updatedSession } = action.payload; // 更新exercise const exerciseIndex = state.exercises.findIndex(ex => ex.id === exercise.id); if (exerciseIndex !== -1) { state.exercises[exerciseIndex] = exercise; } // 更新session(可能已自动完成) state.currentSession = updatedSession; }) .addCase(skipWorkoutExercise.rejected, (state, action) => { state.exerciseLoading = null; state.error = action.payload as string; }) // updateWorkoutExercise .addCase(updateWorkoutExercise.pending, (state, action) => { state.exerciseLoading = action.meta.arg.exerciseId; state.error = null; }) .addCase(updateWorkoutExercise.fulfilled, (state, action) => { state.exerciseLoading = null; const exerciseIndex = state.exercises.findIndex(ex => ex.id === action.payload.id); if (exerciseIndex !== -1) { state.exercises[exerciseIndex] = action.payload; } }) .addCase(updateWorkoutExercise.rejected, (state, action) => { state.exerciseLoading = null; state.error = action.payload as string; }) // loadWorkoutStats .addCase(loadWorkoutStats.fulfilled, (state, action) => { state.stats = action.payload; }) // loadWorkoutSessions .addCase(loadWorkoutSessions.pending, (state) => { state.loading = true; state.error = null; }) .addCase(loadWorkoutSessions.fulfilled, (state, action) => { state.loading = false; state.sessions = action.payload.sessions; state.sessionsPagination = action.payload.pagination; }) .addCase(loadWorkoutSessions.rejected, (state, action) => { state.loading = false; state.error = action.payload as string; }) // addWorkoutExercise .addCase(addWorkoutExercise.pending, (state) => { state.loading = true; state.error = null; }) .addCase(addWorkoutExercise.fulfilled, (state, action) => { state.loading = false; // 将新添加的动作添加到exercises列表末尾 state.exercises.push(action.payload); }) .addCase(addWorkoutExercise.rejected, (state, action) => { state.loading = false; state.error = action.payload as string; }) // deleteWorkoutSession .addCase(deleteWorkoutSession.pending, (state) => { state.loading = true; state.error = null; }) .addCase(deleteWorkoutSession.fulfilled, (state, action) => { state.loading = false; const deletedSessionId = action.payload; // 如果删除的是当前会话,清空当前会话数据 if (state.currentSession?.id === deletedSessionId) { state.currentSession = null; state.exercises = []; state.stats = null; } // 从会话列表中移除已删除的会话 state.sessions = state.sessions.filter(session => session.id !== deletedSessionId); }) .addCase(deleteWorkoutSession.rejected, (state, action) => { state.loading = false; state.error = action.payload as string; }); }, }); export const { clearWorkoutError, clearCurrentWorkout, updateExerciseLocally } = workoutSlice.actions; export default workoutSlice.reducer;