import { createWaterRecord, CreateWaterRecordDto, deleteWaterRecord, getTodayWaterStats, getWaterRecords, TodayWaterStats, updateWaterGoal, updateWaterRecord, UpdateWaterRecordDto, WaterRecord, } from '@/services/waterRecords'; import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'; import dayjs from 'dayjs'; import { RootState } from './index'; // 状态接口 interface WaterState { // 按日期存储的喝水记录 waterRecords: Record; // 分页元数据 waterRecordsMeta: Record; // 今日喝水统计 todayStats: TodayWaterStats | null; // 每日喝水目标 dailyWaterGoal: number | null; // 当前选中的日期 selectedDate: string; // 加载状态 loading: { records: boolean; stats: boolean; goal: boolean; create: boolean; update: boolean; delete: boolean; }; // 错误信息 error: string | null; } // 初始状态 const initialState: WaterState = { waterRecords: {}, waterRecordsMeta: {}, todayStats: null, dailyWaterGoal: null, selectedDate: dayjs().format('YYYY-MM-DD'), loading: { records: false, stats: false, goal: false, create: false, update: false, delete: false, }, error: null, }; // 异步 actions // 获取指定日期的喝水记录 export const fetchWaterRecords = createAsyncThunk( 'water/fetchWaterRecords', async ({ date, page = 1, limit = 20 }: { date: string; page?: number; limit?: number }) => { const response = await getWaterRecords({ date, page, limit }); return { date, records: response.records, total: response.total, page: response.page, limit: response.limit, hasMore: response.hasMore }; } ); // 获取指定日期范围的喝水记录 export const fetchWaterRecordsByDateRange = createAsyncThunk( 'water/fetchWaterRecordsByDateRange', async ({ startDate, endDate, page = 1, limit = 20 }: { startDate: string; endDate: string; page?: number; limit?: number; }) => { const response = await getWaterRecords({ startDate, endDate, page, limit }); return response; } ); // 获取今日喝水统计 export const fetchTodayWaterStats = createAsyncThunk( 'water/fetchTodayWaterStats', async () => { const stats = await getTodayWaterStats(); return stats; } ); // 创建喝水记录 export const createWaterRecordAction = createAsyncThunk( 'water/createWaterRecord', async (dto: CreateWaterRecordDto) => { const newRecord = await createWaterRecord(dto); return newRecord; } ); // 更新喝水记录 export const updateWaterRecordAction = createAsyncThunk( 'water/updateWaterRecord', async (dto: UpdateWaterRecordDto) => { const updatedRecord = await updateWaterRecord(dto); return updatedRecord; } ); // 删除喝水记录 export const deleteWaterRecordAction = createAsyncThunk( 'water/deleteWaterRecord', async (id: string) => { await deleteWaterRecord(id); return id; } ); // 更新喝水目标 export const updateWaterGoalAction = createAsyncThunk( 'water/updateWaterGoal', async (dailyWaterGoal: number) => { const result = await updateWaterGoal({ dailyWaterGoal }); return result.dailyWaterGoal; } ); // 创建 slice const waterSlice = createSlice({ name: 'water', initialState, reducers: { // 设置选中的日期 setSelectedDate: (state, action: PayloadAction) => { state.selectedDate = action.payload; }, // 清除错误 clearError: (state) => { state.error = null; }, // 清除所有数据 clearWaterData: (state) => { state.waterRecords = {}; state.todayStats = null; state.error = null; }, // 清除喝水记录 clearWaterRecords: (state) => { state.waterRecords = {}; state.waterRecordsMeta = {}; }, // 设置每日喝水目标(本地) setDailyWaterGoal: (state, action: PayloadAction) => { state.dailyWaterGoal = action.payload; if (state.todayStats) { state.todayStats.dailyGoal = action.payload; state.todayStats.completionRate = (state.todayStats.totalAmount / action.payload) * 100; } }, // 添加本地喝水记录(用于离线场景) addLocalWaterRecord: (state, action: PayloadAction) => { const record = action.payload; const date = dayjs(record.recordedAt || record.createdAt).format('YYYY-MM-DD'); if (!state.waterRecords[date]) { state.waterRecords[date] = []; } // 检查是否已存在相同ID的记录 const existingIndex = state.waterRecords[date].findIndex(r => r.id === record.id); if (existingIndex >= 0) { state.waterRecords[date][existingIndex] = record; } else { state.waterRecords[date].push(record); } // 更新今日统计 if (date === dayjs().format('YYYY-MM-DD') && state.todayStats) { state.todayStats.totalAmount += record.amount; state.todayStats.recordCount += 1; state.todayStats.completionRate = Math.min((state.todayStats.totalAmount / state.todayStats.dailyGoal) * 100, 100); } }, // 更新本地喝水记录 updateLocalWaterRecord: (state, action: PayloadAction) => { const updatedRecord = action.payload; const date = dayjs(updatedRecord.recordedAt || updatedRecord.createdAt).format('YYYY-MM-DD'); if (state.waterRecords[date]) { const index = state.waterRecords[date].findIndex(r => r.id === updatedRecord.id); if (index >= 0) { const oldRecord = state.waterRecords[date][index]; const amountDiff = updatedRecord.amount - oldRecord.amount; state.waterRecords[date][index] = updatedRecord; // 更新今日统计 if (date === dayjs().format('YYYY-MM-DD') && state.todayStats) { state.todayStats.totalAmount += amountDiff; state.todayStats.completionRate = Math.min((state.todayStats.totalAmount / state.todayStats.dailyGoal) * 100, 100); } } } }, // 删除本地喝水记录 deleteLocalWaterRecord: (state, action: PayloadAction<{ id: string; date: string }>) => { const { id, date } = action.payload; if (state.waterRecords[date]) { const recordIndex = state.waterRecords[date].findIndex(r => r.id === id); if (recordIndex >= 0) { const deletedRecord = state.waterRecords[date][recordIndex]; // 从记录中删除 state.waterRecords[date] = state.waterRecords[date].filter(r => r.id !== id); // 更新今日统计 if (date === dayjs().format('YYYY-MM-DD') && state.todayStats) { state.todayStats.totalAmount -= deletedRecord.amount; state.todayStats.recordCount -= 1; state.todayStats.completionRate = Math.max(Math.min(state.todayStats.totalAmount / state.todayStats.dailyGoal, 1), 0); } } } }, }, extraReducers: (builder) => { // fetchWaterRecords builder .addCase(fetchWaterRecords.pending, (state) => { state.loading.records = true; state.error = null; }) .addCase(fetchWaterRecords.fulfilled, (state, action) => { const { date, records, total, page, limit, hasMore } = action.payload; // 如果是第一页,直接替换数据;如果是分页加载,则追加数据 if (page === 1) { state.waterRecords[date] = records; } else { const existingRecords = state.waterRecords[date] || []; state.waterRecords[date] = [...existingRecords, ...records]; } // 更新分页元数据 state.waterRecordsMeta[date] = { total, page, limit, hasMore }; state.loading.records = false; }) .addCase(fetchWaterRecords.rejected, (state, action) => { state.loading.records = false; state.error = action.error.message || '获取喝水记录失败'; }); // fetchWaterRecordsByDateRange builder .addCase(fetchWaterRecordsByDateRange.pending, (state) => { state.loading.records = true; state.error = null; }) .addCase(fetchWaterRecordsByDateRange.fulfilled, (state, action) => { state.loading.records = false; // 这里可以根据需要处理日期范围的记录 }) .addCase(fetchWaterRecordsByDateRange.rejected, (state, action) => { state.loading.records = false; state.error = action.error.message || '获取喝水记录失败'; }); // fetchTodayWaterStats builder .addCase(fetchTodayWaterStats.pending, (state) => { state.loading.stats = true; state.error = null; }) .addCase(fetchTodayWaterStats.fulfilled, (state, action) => { state.loading.stats = false; state.todayStats = action.payload; state.dailyWaterGoal = action.payload.dailyGoal; }) .addCase(fetchTodayWaterStats.rejected, (state, action) => { state.loading.stats = false; state.error = action.error.message || '获取喝水统计失败'; }); // createWaterRecord builder .addCase(createWaterRecordAction.pending, (state) => { state.loading.create = true; state.error = null; }) .addCase(createWaterRecordAction.fulfilled, (state, action) => { state.loading.create = false; const newRecord = action.payload; const date = dayjs(newRecord.recordedAt || newRecord.createdAt).format('YYYY-MM-DD'); // 添加到对应日期的记录中 if (!state.waterRecords[date]) { state.waterRecords[date] = []; } // 检查是否已存在相同ID的记录 const existingIndex = state.waterRecords[date].findIndex(r => r.id === newRecord.id); if (existingIndex >= 0) { state.waterRecords[date][existingIndex] = newRecord; } else { state.waterRecords[date].push(newRecord); } // 更新今日统计 if (date === dayjs().format('YYYY-MM-DD') && state.todayStats) { state.todayStats.totalAmount += newRecord.amount; state.todayStats.recordCount += 1; state.todayStats.completionRate = Math.min((state.todayStats.totalAmount / state.todayStats.dailyGoal) * 100, 100); } }) .addCase(createWaterRecordAction.rejected, (state, action) => { state.loading.create = false; state.error = action.error.message || '创建喝水记录失败'; }); // updateWaterRecord builder .addCase(updateWaterRecordAction.pending, (state) => { state.loading.update = true; state.error = null; }) .addCase(updateWaterRecordAction.fulfilled, (state, action) => { state.loading.update = false; const updatedRecord = action.payload; const date = dayjs(updatedRecord.recordedAt || updatedRecord.createdAt).format('YYYY-MM-DD'); if (state.waterRecords[date]) { const index = state.waterRecords[date].findIndex(r => r.id === updatedRecord.id); if (index >= 0) { const oldRecord = state.waterRecords[date][index]; const amountDiff = updatedRecord.amount - oldRecord.amount; state.waterRecords[date][index] = updatedRecord; // 更新今日统计 if (date === dayjs().format('YYYY-MM-DD') && state.todayStats) { state.todayStats.totalAmount += amountDiff; state.todayStats.completionRate = Math.min((state.todayStats.totalAmount / state.todayStats.dailyGoal) * 100, 100); } } } }) .addCase(updateWaterRecordAction.rejected, (state, action) => { state.loading.update = false; state.error = action.error.message || '更新喝水记录失败'; }); // deleteWaterRecord builder .addCase(deleteWaterRecordAction.pending, (state) => { state.loading.delete = true; state.error = null; }) .addCase(deleteWaterRecordAction.fulfilled, (state, action) => { state.loading.delete = false; const deletedId = action.payload; // 从所有日期的记录中删除 Object.keys(state.waterRecords).forEach(date => { const recordIndex = state.waterRecords[date].findIndex(r => r.id === deletedId); if (recordIndex >= 0) { const deletedRecord = state.waterRecords[date][recordIndex]; // 更新今日统计 if (date === dayjs().format('YYYY-MM-DD') && state.todayStats) { state.todayStats.totalAmount -= deletedRecord.amount; state.todayStats.recordCount -= 1; state.todayStats.completionRate = Math.max(Math.min((state.todayStats.totalAmount / state.todayStats.dailyGoal) * 100, 100), 0); } state.waterRecords[date] = state.waterRecords[date].filter(r => r.id !== deletedId); } }); }) .addCase(deleteWaterRecordAction.rejected, (state, action) => { state.loading.delete = false; state.error = action.error.message || '删除喝水记录失败'; }); // updateWaterGoal builder .addCase(updateWaterGoalAction.pending, (state) => { state.loading.goal = true; state.error = null; }) .addCase(updateWaterGoalAction.fulfilled, (state, action) => { state.loading.goal = false; state.dailyWaterGoal = action.payload; if (state.todayStats) { state.todayStats.dailyGoal = action.payload; state.todayStats.completionRate = Math.min((state.todayStats.totalAmount / action.payload) * 100, 100); } }) .addCase(updateWaterGoalAction.rejected, (state, action) => { state.loading.goal = false; state.error = action.error.message || '更新喝水目标失败'; }); }, }); // 导出 actions export const { setSelectedDate, clearError, clearWaterData, clearWaterRecords, setDailyWaterGoal, addLocalWaterRecord, updateLocalWaterRecord, deleteLocalWaterRecord, } = waterSlice.actions; // 选择器函数 export const selectWaterState = (state: RootState) => state.water; // 选择今日统计 export const selectTodayStats = (state: RootState) => selectWaterState(state).todayStats; // 选择每日喝水目标 export const selectDailyWaterGoal = (state: RootState) => selectWaterState(state).dailyWaterGoal; // 选择指定日期的喝水记录 export const selectWaterRecordsByDate = (date: string) => (state: RootState) => { return selectWaterState(state).waterRecords[date] || []; }; // 选择当前选中日期的喝水记录 export const selectSelectedDateWaterRecords = (state: RootState) => { const selectedDate = selectWaterState(state).selectedDate; return selectWaterRecordsByDate(selectedDate)(state); }; // 选择加载状态 export const selectWaterLoading = (state: RootState) => selectWaterState(state).loading; // 选择错误信息 export const selectWaterError = (state: RootState) => selectWaterState(state).error; // 选择当前选中日期 export const selectSelectedDate = (state: RootState) => selectWaterState(state).selectedDate; // 导出 reducer export default waterSlice.reducer;