import { goalsApi } from '@/services/goalsApi'; import { BatchGoalOperationRequest, CompleteGoalRequest, CreateGoalRequest, GetGoalCompletionsQuery, GetGoalsQuery, GoalCompletion, GoalDetailResponse, GoalListItem, GoalStats, GoalStatus, UpdateGoalRequest } from '@/types/goals'; import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; // 目标管理状态类型 export interface GoalsState { // 目标列表 goals: GoalListItem[]; goalsLoading: boolean; goalsError: string | null; goalsPagination: { page: number; pageSize: number; total: number; hasMore: boolean; }; // 当前查看的目标详情 currentGoal: GoalDetailResponse | null; currentGoalLoading: boolean; currentGoalError: string | null; // 目标完成记录 completions: GoalCompletion[]; completionsLoading: boolean; completionsError: string | null; completionsPagination: { page: number; pageSize: number; total: number; hasMore: boolean; }; // 目标统计 stats: GoalStats | null; statsLoading: boolean; statsError: string | null; // 创建/更新目标 createLoading: boolean; createError: string | null; updateLoading: boolean; updateError: string | null; // 批量操作 batchLoading: boolean; batchError: string | null; // 筛选和搜索 filters: GetGoalsQuery; } const initialState: GoalsState = { goals: [], goalsLoading: false, goalsError: null, goalsPagination: { page: 1, pageSize: 20, total: 0, hasMore: false, }, currentGoal: null, currentGoalLoading: false, currentGoalError: null, completions: [], completionsLoading: false, completionsError: null, completionsPagination: { page: 1, pageSize: 20, total: 0, hasMore: false, }, stats: null, statsLoading: false, statsError: null, createLoading: false, createError: null, updateLoading: false, updateError: null, batchLoading: false, batchError: null, filters: { page: 1, pageSize: 20, sortBy: 'createdAt', sortOrder: 'desc', }, }; // 异步操作 /** * 获取目标列表 */ export const fetchGoals = createAsyncThunk( 'goals/fetchGoals', async (query: GetGoalsQuery = {}, { rejectWithValue }) => { try { const response = await goalsApi.getGoals(query); console.log('fetchGoals response', response); return { query, response }; } catch (error: any) { return rejectWithValue(error.message || '获取目标列表失败'); } } ); /** * 加载更多目标 */ export const loadMoreGoals = createAsyncThunk( 'goals/loadMoreGoals', async (_, { getState, rejectWithValue }) => { try { const state = getState() as { goals: GoalsState }; const { filters, goalsPagination } = state.goals; if (!goalsPagination.hasMore) { return { goals: [], pagination: goalsPagination }; } const query = { ...filters, page: goalsPagination.page + 1, }; const response = await goalsApi.getGoals(query); console.log('response', response); return { query, response }; } catch (error: any) { return rejectWithValue(error.message || '加载更多目标失败'); } } ); /** * 获取目标详情 */ export const fetchGoalDetail = createAsyncThunk( 'goals/fetchGoalDetail', async (goalId: string, { rejectWithValue }) => { try { const response = await goalsApi.getGoalById(goalId); return response.data; } catch (error: any) { return rejectWithValue(error.message || '获取目标详情失败'); } } ); /** * 创建目标 */ export const createGoal = createAsyncThunk( 'goals/createGoal', async (goalData: CreateGoalRequest, { rejectWithValue }) => { try { const response = await goalsApi.createGoal(goalData); console.log('createGoal response', response); return response; } catch (error: any) { return rejectWithValue(error.message || '创建目标失败'); } } ); /** * 更新目标 */ export const updateGoal = createAsyncThunk( 'goals/updateGoal', async ({ goalId, goalData }: { goalId: string; goalData: UpdateGoalRequest }, { rejectWithValue }) => { try { const response = await goalsApi.updateGoal(goalId, goalData); return response; } catch (error: any) { return rejectWithValue(error.message || '更新目标失败'); } } ); /** * 删除目标 */ export const deleteGoal = createAsyncThunk( 'goals/deleteGoal', async (goalId: string, { rejectWithValue }) => { try { await goalsApi.deleteGoal(goalId); return goalId; } catch (error: any) { return rejectWithValue(error.message || '删除目标失败'); } } ); /** * 记录目标完成 */ export const completeGoal = createAsyncThunk( 'goals/completeGoal', async ({ goalId, completionData }: { goalId: string; completionData?: CompleteGoalRequest }, { rejectWithValue }) => { try { const response = await goalsApi.completeGoal(goalId, completionData); return { goalId, completion: response.data }; } catch (error: any) { return rejectWithValue(error.message || '记录目标完成失败'); } } ); /** * 获取目标完成记录 */ export const fetchGoalCompletions = createAsyncThunk( 'goals/fetchGoalCompletions', async ({ goalId, query }: { goalId: string; query?: GetGoalCompletionsQuery }, { rejectWithValue }) => { try { const response = await goalsApi.getGoalCompletions(goalId, query); return { query, response: response.data }; } catch (error: any) { return rejectWithValue(error.message || '获取完成记录失败'); } } ); /** * 获取目标统计 */ export const fetchGoalStats = createAsyncThunk( 'goals/fetchGoalStats', async (_, { rejectWithValue }) => { try { const response = await goalsApi.getGoalStats(); return response.data; } catch (error: any) { return rejectWithValue(error.message || '获取目标统计失败'); } } ); /** * 批量操作目标 */ export const batchOperateGoals = createAsyncThunk( 'goals/batchOperateGoals', async (operationData: BatchGoalOperationRequest, { rejectWithValue }) => { try { const response = await goalsApi.batchOperateGoals(operationData); return { operation: operationData, results: response.data }; } catch (error: any) { return rejectWithValue(error.message || '批量操作失败'); } } ); // Redux Slice const goalsSlice = createSlice({ name: 'goals', initialState, reducers: { // 设置筛选条件 setFilters: (state, action: PayloadAction>) => { state.filters = { ...state.filters, ...action.payload }; }, // 重置筛选条件 resetFilters: (state) => { state.filters = { page: 1, pageSize: 20, sortBy: 'createdAt', sortOrder: 'desc', }; }, // 清除错误 clearErrors: (state) => { state.goalsError = null; state.currentGoalError = null; state.completionsError = null; state.statsError = null; state.createError = null; state.updateError = null; state.batchError = null; }, // 清除当前目标详情 clearCurrentGoal: (state) => { state.currentGoal = null; state.currentGoalError = null; }, // 本地更新目标状态(用于乐观更新) updateGoalStatus: (state, action: PayloadAction<{ goalId: string; status: GoalStatus }>) => { const { goalId, status } = action.payload; // 更新目标列表中的状态 const goalIndex = state.goals.findIndex(goal => goal.id === goalId); if (goalIndex !== -1) { state.goals[goalIndex].status = status; } // 更新当前目标详情中的状态 if (state.currentGoal && state.currentGoal.id === goalId) { state.currentGoal.status = status; } }, // 本地增加完成次数(用于乐观更新) incrementGoalCompletion: (state, action: PayloadAction<{ goalId: string; count?: number }>) => { const { goalId, count = 1 } = action.payload; // 更新目标列表中的完成次数 const goalIndex = state.goals.findIndex(goal => goal.id === goalId); if (goalIndex !== -1) { state.goals[goalIndex].completedCount += count; // 重新计算进度百分比 if (state.goals[goalIndex].targetCount && state.goals[goalIndex].targetCount > 0) { state.goals[goalIndex].progressPercentage = Math.round( (state.goals[goalIndex].completedCount / state.goals[goalIndex].targetCount) * 100 ); } } // 更新当前目标详情中的完成次数 if (state.currentGoal && state.currentGoal.id === goalId) { state.currentGoal.completedCount += count; if (state.currentGoal.targetCount && state.currentGoal.targetCount > 0) { state.currentGoal.progressPercentage = Math.round( (state.currentGoal.completedCount / state.currentGoal.targetCount) * 100 ); } } }, }, extraReducers: (builder) => { builder // 获取目标列表 .addCase(fetchGoals.pending, (state) => { state.goalsLoading = true; state.goalsError = null; }) .addCase(fetchGoals.fulfilled, (state, action) => { state.goalsLoading = false; const { query, response } = action.payload; // 如果是第一页,替换数据;否则追加数据 if (query.page === 1) { state.goals = response.list; } else { state.goals = [...state.goals, ...response.list]; } state.goalsPagination = { page: response.page, pageSize: response.pageSize, total: response.total, hasMore: response.page * response.pageSize < response.total, }; }) .addCase(fetchGoals.rejected, (state, action) => { state.goalsLoading = false; state.goalsError = action.payload as string; }) // 加载更多目标 .addCase(loadMoreGoals.pending, (state) => { state.goalsLoading = true; }) .addCase(loadMoreGoals.fulfilled, (state, action) => { state.goalsLoading = false; const { response } = action.payload; if (!response) { return; } state.goals = [...state.goals, ...response.list]; state.goalsPagination = { page: response.page, pageSize: response.pageSize, total: response.total, hasMore: response.page * response.pageSize < response.total, }; }) .addCase(loadMoreGoals.rejected, (state, action) => { state.goalsLoading = false; state.goalsError = action.payload as string; }) // 获取目标详情 .addCase(fetchGoalDetail.pending, (state) => { state.currentGoalLoading = true; state.currentGoalError = null; }) .addCase(fetchGoalDetail.fulfilled, (state, action) => { state.currentGoalLoading = false; state.currentGoal = action.payload; }) .addCase(fetchGoalDetail.rejected, (state, action) => { state.currentGoalLoading = false; state.currentGoalError = action.payload as string; }) // 创建目标 .addCase(createGoal.pending, (state) => { state.createLoading = true; state.createError = null; }) .addCase(createGoal.fulfilled, (state, action) => { state.createLoading = false; // 将新目标添加到列表开头 const newGoal: GoalListItem = { ...action.payload, progressPercentage: action.payload.targetCount && action.payload.targetCount > 0 ? Math.round((action.payload.completedCount / action.payload.targetCount) * 100) : 0, }; state.goals.unshift(newGoal); state.goalsPagination.total += 1; }) .addCase(createGoal.rejected, (state, action) => { state.createLoading = false; state.createError = action.payload as string; }) // 更新目标 .addCase(updateGoal.pending, (state) => { state.updateLoading = true; state.updateError = null; }) .addCase(updateGoal.fulfilled, (state, action) => { state.updateLoading = false; const updatedGoal = action.payload; console.log('updateGoal.fulfilled', updatedGoal); // 更新目标列表中的目标 const goalIndex = state.goals.findIndex(goal => goal.id === updatedGoal.id); if (goalIndex !== -1) { state.goals[goalIndex] = { ...state.goals[goalIndex], ...updatedGoal, }; } // 更新当前目标详情 if (state.currentGoal && state.currentGoal.id === updatedGoal.id) { state.currentGoal = { ...state.currentGoal, ...updatedGoal, }; } }) .addCase(updateGoal.rejected, (state, action) => { state.updateLoading = false; state.updateError = action.payload as string; }) // 删除目标 .addCase(deleteGoal.fulfilled, (state, action) => { const goalId = action.payload; // 从目标列表中移除 state.goals = state.goals.filter(goal => goal.id !== goalId); state.goalsPagination.total -= 1; // 如果删除的是当前查看的目标,清除详情 if (state.currentGoal && state.currentGoal.id === goalId) { state.currentGoal = null; } }) // 记录目标完成 .addCase(completeGoal.fulfilled, (state, action) => { const { goalId, completion } = action.payload; // 增加完成次数 goalsSlice.caseReducers.incrementGoalCompletion(state, { type: 'goals/incrementGoalCompletion', payload: { goalId, count: completion.completionCount }, }); // 将完成记录添加到列表开头 state.completions.unshift(completion); }) // 获取完成记录 .addCase(fetchGoalCompletions.pending, (state) => { state.completionsLoading = true; state.completionsError = null; }) .addCase(fetchGoalCompletions.fulfilled, (state, action) => { state.completionsLoading = false; const { query, response } = action.payload; // 如果是第一页,替换数据;否则追加数据 if (query?.page === 1) { state.completions = response.list; } else { state.completions = [...state.completions, ...response.list]; } state.completionsPagination = { page: response.page, pageSize: response.pageSize, total: response.total, hasMore: response.page * response.pageSize < response.total, }; }) .addCase(fetchGoalCompletions.rejected, (state, action) => { state.completionsLoading = false; state.completionsError = action.payload as string; }) // 获取目标统计 .addCase(fetchGoalStats.pending, (state) => { state.statsLoading = true; state.statsError = null; }) .addCase(fetchGoalStats.fulfilled, (state, action) => { state.statsLoading = false; state.stats = action.payload; }) .addCase(fetchGoalStats.rejected, (state, action) => { state.statsLoading = false; state.statsError = action.payload as string; }) // 批量操作 .addCase(batchOperateGoals.pending, (state) => { state.batchLoading = true; state.batchError = null; }) .addCase(batchOperateGoals.fulfilled, (state, action) => { state.batchLoading = false; const { operation, results } = action.payload; // 根据操作类型更新状态 results.forEach(result => { if (result.success) { const goalIndex = state.goals.findIndex(goal => goal.id === result.goalId); if (goalIndex !== -1) { switch (operation.action) { case 'pause': state.goals[goalIndex].status = 'paused'; break; case 'resume': state.goals[goalIndex].status = 'active'; break; case 'complete': state.goals[goalIndex].status = 'completed'; break; case 'delete': state.goals = state.goals.filter(goal => goal.id !== result.goalId); state.goalsPagination.total -= 1; break; } } } }); }) .addCase(batchOperateGoals.rejected, (state, action) => { state.batchLoading = false; state.batchError = action.payload as string; }); }, }); export const { setFilters, resetFilters, clearErrors, clearCurrentGoal, updateGoalStatus, incrementGoalCompletion, } = goalsSlice.actions; export default goalsSlice.reducer;