feat: 更新训练计划和打卡功能
- 在训练计划中新增训练项目的添加、更新和删除功能,支持用户灵活管理训练内容 - 优化训练计划排课界面,提升用户体验 - 更新打卡功能,支持按日期加载和展示打卡记录 - 删除不再使用的打卡相关页面,简化代码结构 - 新增今日训练页面,集成今日训练计划和动作展示 - 更新样式以适应新功能的展示和交互
This commit is contained in:
233
store/scheduleExerciseSlice.ts
Normal file
233
store/scheduleExerciseSlice.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user