Files
digital-pilates/store/scheduleExerciseSlice.ts
richarjiang dacbee197c feat: 更新训练计划和打卡功能
- 在训练计划中新增训练项目的添加、更新和删除功能,支持用户灵活管理训练内容
- 优化训练计划排课界面,提升用户体验
- 更新打卡功能,支持按日期加载和展示打卡记录
- 删除不再使用的打卡相关页面,简化代码结构
- 新增今日训练页面,集成今日训练计划和动作展示
- 更新样式以适应新功能的展示和交互
2025-08-15 17:01:33 +08:00

234 lines
7.0 KiB
TypeScript

import {
scheduleExerciseApi,
type CreateScheduleExerciseDto,
type ReorderExercisesDto,
type ScheduleExercise,
type UpdateScheduleExerciseDto
} from '@/services/scheduleExerciseApi';
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
export type ScheduleExerciseState = {
exercises: ScheduleExercise[];
loading: boolean;
error: string | null;
currentPlanId: string | null;
};
const initialState: ScheduleExerciseState = {
exercises: [],
loading: false,
error: null,
currentPlanId: null,
};
// 加载训练计划的所有项目
export const loadExercises = createAsyncThunk(
'scheduleExercise/loadExercises',
async (planId: string, { rejectWithValue }) => {
try {
const exercises = await scheduleExerciseApi.list(planId);
return { exercises, planId };
} catch (error: any) {
return rejectWithValue(error.message || '加载训练项目失败');
}
}
);
// 添加训练项目
export const addExercise = createAsyncThunk(
'scheduleExercise/addExercise',
async ({ planId, dto }: { planId: string; dto: CreateScheduleExerciseDto }, { rejectWithValue }) => {
try {
const exercise = await scheduleExerciseApi.create(planId, dto);
return { exercise };
} catch (error: any) {
return rejectWithValue(error.message || '添加训练项目失败');
}
}
);
// 更新训练项目
export const updateExercise = createAsyncThunk(
'scheduleExercise/updateExercise',
async ({
planId,
exerciseId,
dto
}: {
planId: string;
exerciseId: string;
dto: UpdateScheduleExerciseDto;
}, { rejectWithValue }) => {
try {
const exercise = await scheduleExerciseApi.update(planId, exerciseId, dto);
return { exercise };
} catch (error: any) {
return rejectWithValue(error.message || '更新训练项目失败');
}
}
);
// 删除训练项目
export const deleteExercise = createAsyncThunk(
'scheduleExercise/deleteExercise',
async ({ planId, exerciseId }: { planId: string; exerciseId: string }, { rejectWithValue }) => {
try {
await scheduleExerciseApi.delete(planId, exerciseId);
return { exerciseId };
} catch (error: any) {
return rejectWithValue(error.message || '删除训练项目失败');
}
}
);
// 重新排序训练项目
export const reorderExercises = createAsyncThunk(
'scheduleExercise/reorderExercises',
async ({ planId, dto }: { planId: string; dto: ReorderExercisesDto }, { rejectWithValue }) => {
try {
await scheduleExerciseApi.reorder(planId, dto);
// 重新加载排序后的列表
const exercises = await scheduleExerciseApi.list(planId);
return { exercises };
} catch (error: any) {
return rejectWithValue(error.message || '重新排序失败');
}
}
);
// 更新完成状态
export const toggleCompletion = createAsyncThunk(
'scheduleExercise/toggleCompletion',
async ({
planId,
exerciseId,
completed
}: {
planId: string;
exerciseId: string;
completed: boolean;
}, { rejectWithValue }) => {
try {
const exercise = await scheduleExerciseApi.updateCompletion(planId, exerciseId, completed);
return { exercise };
} catch (error: any) {
return rejectWithValue(error.message || '更新完成状态失败');
}
}
);
const scheduleExerciseSlice = createSlice({
name: 'scheduleExercise',
initialState,
reducers: {
clearError(state) {
state.error = null;
},
clearExercises(state) {
state.exercises = [];
state.currentPlanId = null;
},
// 本地更新排序(用于拖拽等即时反馈)
updateLocalOrder(state, action: PayloadAction<string[]>) {
const newOrder = action.payload;
const orderedExercises = newOrder.map(id =>
state.exercises.find(ex => ex.id === id)
).filter(Boolean) as ScheduleExercise[];
state.exercises = orderedExercises;
},
},
extraReducers: (builder) => {
builder
// loadExercises
.addCase(loadExercises.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(loadExercises.fulfilled, (state, action) => {
state.loading = false;
state.exercises = action.payload.exercises;
state.currentPlanId = action.payload.planId;
})
.addCase(loadExercises.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
})
// addExercise
.addCase(addExercise.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(addExercise.fulfilled, (state, action) => {
state.loading = false;
state.exercises.push(action.payload.exercise);
})
.addCase(addExercise.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
})
// updateExercise
.addCase(updateExercise.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(updateExercise.fulfilled, (state, action) => {
state.loading = false;
const index = state.exercises.findIndex(ex => ex.id === action.payload.exercise.id);
if (index !== -1) {
state.exercises[index] = action.payload.exercise;
}
})
.addCase(updateExercise.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
})
// deleteExercise
.addCase(deleteExercise.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(deleteExercise.fulfilled, (state, action) => {
state.loading = false;
state.exercises = state.exercises.filter(ex => ex.id !== action.payload.exerciseId);
})
.addCase(deleteExercise.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
})
// reorderExercises
.addCase(reorderExercises.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(reorderExercises.fulfilled, (state, action) => {
state.loading = false;
state.exercises = action.payload.exercises;
})
.addCase(reorderExercises.rejected, (state, action) => {
state.loading = false;
state.error = action.payload as string;
})
// toggleCompletion
.addCase(toggleCompletion.pending, (state) => {
state.error = null;
})
.addCase(toggleCompletion.fulfilled, (state, action) => {
const index = state.exercises.findIndex(ex => ex.id === action.payload.exercise.id);
if (index !== -1) {
state.exercises[index] = action.payload.exercise;
}
})
.addCase(toggleCompletion.rejected, (state, action) => {
state.error = action.payload as string;
});
},
});
export const { clearError, clearExercises, updateLocalOrder } = scheduleExerciseSlice.actions;
export default scheduleExerciseSlice.reducer;