- 在训练计划中新增训练项目的添加、更新和删除功能,支持用户灵活管理训练内容 - 优化训练计划排课界面,提升用户体验 - 更新打卡功能,支持按日期加载和展示打卡记录 - 删除不再使用的打卡相关页面,简化代码结构 - 新增今日训练页面,集成今日训练计划和动作展示 - 更新样式以适应新功能的展示和交互
234 lines
7.0 KiB
TypeScript
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;
|