import { CreateWaterRecordDto, UpdateWaterRecordDto, WaterRecordSource } from '@/services/waterRecords'; import { AppDispatch, RootState } from '@/store'; import { createWaterRecordAction, deleteWaterRecordAction, fetchTodayWaterStats, fetchWaterRecords, fetchWaterRecordsByDateRange, setSelectedDate, updateWaterGoalAction, updateWaterRecordAction, } from '@/store/waterSlice'; import { Toast } from '@/utils/toast.utils'; import { saveWaterIntakeToHealthKit, deleteWaterIntakeFromHealthKit } from '@/utils/health'; import { syncWaterDataToWidget, refreshWidget } from '@/utils/widgetDataSync'; import { getQuickWaterAmount } from '@/utils/userPreferences'; import dayjs from 'dayjs'; import { useCallback, useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; export const useWaterData = () => { const dispatch = useDispatch(); // 选择器 const todayStats = useSelector((state: RootState) => state.water.todayStats); const dailyWaterGoal = useSelector((state: RootState) => state.water.dailyWaterGoal); const waterRecords = useSelector((state: RootState) => state.water.waterRecords[dayjs().format('YYYY-MM-DD')] || [] ); const waterRecordsMeta = useSelector((state: RootState) => state.water.waterRecordsMeta[dayjs().format('YYYY-MM-DD')] || { total: 0, page: 1, limit: 20, hasMore: false } ); const selectedDate = useSelector((state: RootState) => state.water.selectedDate); const loading = useSelector((state: RootState) => state.water.loading); const error = useSelector((state: RootState) => state.water.error); // 获取指定日期的记录(支持分页) const getWaterRecordsByDate = useCallback(async (date: string, page = 1, limit = 20) => { await dispatch(fetchWaterRecords({ date, page, limit })); }, [dispatch]); // 加载更多记录 const loadMoreWaterRecords = useCallback(async () => { const currentMeta = waterRecordsMeta; if (currentMeta.hasMore && !loading.records) { const nextPage = currentMeta.page + 1; await dispatch(fetchWaterRecords({ date: selectedDate, page: nextPage, limit: currentMeta.limit })); } }, [dispatch, waterRecordsMeta, loading.records, selectedDate]); // 获取日期范围的记录 const getWaterRecordsByDateRange = useCallback(async ( startDate: string, endDate: string, page = 1, limit = 20 ) => { await dispatch(fetchWaterRecordsByDateRange({ startDate, endDate, page, limit })); }, [dispatch]); // 加载今日数据 const loadTodayData = useCallback(() => { dispatch(fetchTodayWaterStats()); dispatch(fetchWaterRecords({ date: dayjs().format('YYYY-MM-DD') })); }, [dispatch]); // 加载指定日期数据 const loadDataByDate = useCallback((date: string) => { dispatch(setSelectedDate(date)); dispatch(fetchWaterRecords({ date })); }, [dispatch]); // 创建喝水记录 const addWaterRecord = useCallback(async (amount: number, recordedAt?: string) => { const dto: CreateWaterRecordDto = { amount, source: WaterRecordSource.Manual, recordedAt, }; try { await dispatch(createWaterRecordAction(dto)).unwrap(); // 同步到 HealthKit try { const healthKitSuccess = await saveWaterIntakeToHealthKit(amount, recordedAt); if (!healthKitSuccess) { console.warn('同步饮水记录到 HealthKit 失败,但应用内记录已保存'); } } catch (healthError) { console.error('HealthKit 同步错误:', healthError); // HealthKit 同步失败不影响主要功能 } // 重新获取今日统计 dispatch(fetchTodayWaterStats()); // 同步数据到Widget try { const newCurrentIntake = (todayStats?.totalAmount || 0) + amount; const quickAddAmount = await getQuickWaterAmount(); await syncWaterDataToWidget({ currentIntake: newCurrentIntake, dailyGoal: dailyWaterGoal || 2000, quickAddAmount, }); // 刷新Widget await refreshWidget(); } catch (widgetError) { console.error('Widget 同步错误:', widgetError); // Widget 同步失败不影响主要功能 } return true; } catch (error: any) { console.error('添加喝水记录失败:', error); // 根据错误类型显示不同的提示信息 let errorMessage = '添加喝水记录失败'; if (error?.message) { if (error.message.includes('网络')) { errorMessage = '网络连接失败,请检查网络后重试'; } else if (error.message.includes('参数')) { errorMessage = '参数错误,请重新输入'; } else if (error.message.includes('权限')) { errorMessage = '权限不足,请重新登录'; } else if (error.message.includes('服务器')) { errorMessage = '服务器繁忙,请稍后重试'; } else { errorMessage = error.message; } } Toast.error(errorMessage); return false; } }, [dispatch]); // 更新喝水记录 const updateWaterRecord = useCallback(async (id: string, amount?: number, note?: string, recordedAt?: string) => { const dto: UpdateWaterRecordDto = { id, amount, note, recordedAt, }; try { await dispatch(updateWaterRecordAction(dto)).unwrap(); // 重新获取今日统计 dispatch(fetchTodayWaterStats()); return true; } catch (error: any) { console.error('更新喝水记录失败:', error); let errorMessage = '更新喝水记录失败'; if (error?.message) { errorMessage = error.message; } Toast.error(errorMessage); return false; } }, [dispatch]); // 删除喝水记录 const removeWaterRecord = useCallback(async (id: string) => { try { // 在删除前,尝试获取记录信息用于 HealthKit 同步 const recordToDelete = waterRecords.find(record => record.id === id); await dispatch(deleteWaterRecordAction(id)).unwrap(); // 同步删除到 HealthKit if (recordToDelete) { try { const healthKitSuccess = await deleteWaterIntakeFromHealthKit( id, recordToDelete.recordedAt || recordToDelete.createdAt ); if (!healthKitSuccess) { console.warn('从 HealthKit 删除饮水记录失败,但应用内记录已删除'); } } catch (healthError) { console.error('HealthKit 删除同步错误:', healthError); // HealthKit 同步失败不影响主要功能 } } // 重新获取今日统计 dispatch(fetchTodayWaterStats()); return true; } catch (error: any) { console.error('删除喝水记录失败:', error); let errorMessage = '删除喝水记录失败'; if (error?.message) { errorMessage = error.message; } Toast.error(errorMessage); return false; } }, [dispatch, waterRecords]); // 更新喝水目标 const updateWaterGoal = useCallback(async (goal: number) => { try { await dispatch(updateWaterGoalAction(goal)).unwrap(); // 同步目标到Widget try { const quickAddAmount = await getQuickWaterAmount(); await syncWaterDataToWidget({ dailyGoal: goal, currentIntake: todayStats?.totalAmount || 0, quickAddAmount, }); await refreshWidget(); } catch (widgetError) { console.error('Widget 目标同步错误:', widgetError); } return true; } catch (error: any) { console.error('更新喝水目标失败:', error); let errorMessage = '更新喝水目标失败'; if (error?.message) { errorMessage = error.message; } Toast.error(errorMessage); return false; } }, [dispatch, todayStats]); // 计算总喝水量 const getTotalAmount = useCallback((records: any[]) => { return records.reduce((total, record) => total + record.amount, 0); }, []); // 按小时分组数据 const getHourlyData = useCallback((records: any[]) => { const hourlyData: { hour: number; amount: number }[] = Array.from({ length: 24 }, (_, i) => ({ hour: i, amount: 0, })); records.forEach(record => { // 优先使用 recordedAt,如果没有则使用 createdAt const dateTime = record.recordedAt || record.createdAt; const hour = dayjs(dateTime).hour(); if (hour >= 0 && hour < 24) { hourlyData[hour].amount += record.amount; } }); return hourlyData; }, []); // 计算完成率(返回百分比) const calculateCompletionRate = useCallback((totalAmount: number, goal: number) => { if (goal <= 0) return 0; return Math.min((totalAmount / goal) * 100, 100); }, []); // 初始化加载 useEffect(() => { loadTodayData(); }, [loadTodayData]); // 同步初始数据到Widget useEffect(() => { const syncInitialDataToWidget = async () => { if (todayStats && dailyWaterGoal) { try { const quickAddAmount = await getQuickWaterAmount(); await syncWaterDataToWidget({ currentIntake: todayStats.totalAmount, dailyGoal: dailyWaterGoal, quickAddAmount, }); } catch (error) { console.error('初始Widget数据同步失败:', error); } } }; syncInitialDataToWidget(); }, [todayStats, dailyWaterGoal]); return { // 数据 todayStats, dailyWaterGoal, waterRecords, waterRecordsMeta, selectedDate, loading, error, // 方法 loadTodayData, loadDataByDate, getWaterRecordsByDate, loadMoreWaterRecords, getWaterRecordsByDateRange, addWaterRecord, updateWaterRecord, removeWaterRecord, updateWaterGoal, getTotalAmount, getHourlyData, calculateCompletionRate, }; }; // 简化的Hook,只返回今日数据 export const useTodayWaterData = () => { const { todayStats, dailyWaterGoal, waterRecords, waterRecordsMeta, selectedDate, loading, error, getWaterRecordsByDate, loadMoreWaterRecords, getWaterRecordsByDateRange, addWaterRecord, updateWaterRecord, removeWaterRecord, updateWaterGoal, } = useWaterData(); // 获取今日记录(默认第一页) const todayWaterRecords = useSelector((state: RootState) => state.water.waterRecords[dayjs().format('YYYY-MM-DD')] || [] ); const todayMeta = useSelector((state: RootState) => state.water.waterRecordsMeta[dayjs().format('YYYY-MM-DD')] || { total: 0, page: 1, limit: 20, hasMore: false } ); // 获取今日记录(向后兼容) const fetchTodayWaterRecords = useCallback(async (page = 1, limit = 20) => { const today = dayjs().format('YYYY-MM-DD'); await getWaterRecordsByDate(today, page, limit); }, [getWaterRecordsByDate]); return { todayStats, dailyWaterGoal, waterRecords: todayWaterRecords, waterRecordsMeta: todayMeta, selectedDate, loading, error, fetchTodayWaterRecords, loadMoreWaterRecords, getWaterRecordsByDateRange, addWaterRecord, updateWaterRecord, removeWaterRecord, updateWaterGoal, }; }; // 新增:按日期获取饮水数据的 hook export const useWaterDataByDate = (targetDate?: string) => { const dispatch = useDispatch(); // 如果没有传入日期,默认使用今天 const dateToUse = targetDate || dayjs().format('YYYY-MM-DD'); // 选择器 - 获取指定日期的数据 const dailyWaterGoal = useSelector((state: RootState) => state.water.dailyWaterGoal) || 0; const waterRecords = useSelector((state: RootState) => state.water.waterRecords[dateToUse] || [] ); const waterRecordsMeta = useSelector((state: RootState) => state.water.waterRecordsMeta[dateToUse] || { total: 0, page: 1, limit: 20, hasMore: false } ); const loading = useSelector((state: RootState) => state.water.loading); const error = useSelector((state: RootState) => state.water.error); // 计算指定日期的统计数据 const waterStats = useMemo(() => { if (!waterRecords || waterRecords.length === 0) { return { totalAmount: 0, completionRate: 0, recordCount: 0 }; } const totalAmount = waterRecords.reduce((total, record) => total + record.amount, 0); const completionRate = dailyWaterGoal > 0 ? Math.min((totalAmount / dailyWaterGoal) * 100, 100) : 0; return { totalAmount, completionRate, recordCount: waterRecords.length }; }, [waterRecords, dailyWaterGoal]); // 获取指定日期的记录 const getWaterRecordsByDate = useCallback(async (date: string, page = 1, limit = 20) => { await dispatch(fetchWaterRecords({ date, page, limit })); }, [dispatch]); // 创建喝水记录 const addWaterRecord = useCallback(async (amount: number, recordedAt?: string) => { const dto: CreateWaterRecordDto = { amount, source: WaterRecordSource.Manual, recordedAt: recordedAt || dayjs().toISOString(), }; try { await dispatch(createWaterRecordAction(dto)).unwrap(); // 同步到 HealthKit try { const healthKitSuccess = await saveWaterIntakeToHealthKit(amount, dto.recordedAt); if (!healthKitSuccess) { console.warn('同步饮水记录到 HealthKit 失败,但应用内记录已保存'); } } catch (healthError) { console.error('HealthKit 同步错误:', healthError); // HealthKit 同步失败不影响主要功能 } // 重新获取当前日期的数据 await getWaterRecordsByDate(dateToUse); // 如果是今天的数据,也更新今日统计 if (dateToUse === dayjs().format('YYYY-MM-DD')) { dispatch(fetchTodayWaterStats()); } return true; } catch (error: any) { console.error('添加喝水记录失败:', error); // 根据错误类型显示不同的提示信息 let errorMessage = '添加喝水记录失败'; if (error?.message) { if (error.message.includes('网络')) { errorMessage = '网络连接失败,请检查网络后重试'; } else if (error.message.includes('参数')) { errorMessage = '参数错误,请重新输入'; } else if (error.message.includes('权限')) { errorMessage = '权限不足,请重新登录'; } else if (error.message.includes('服务器')) { errorMessage = '服务器繁忙,请稍后重试'; } else { errorMessage = error.message; } } Toast.error(errorMessage); return false; } }, [dispatch, dateToUse, getWaterRecordsByDate]); // 更新喝水记录 const updateWaterRecord = useCallback(async (id: string, amount?: number, note?: string, recordedAt?: string) => { const dto: UpdateWaterRecordDto = { id, amount, note, recordedAt, }; try { await dispatch(updateWaterRecordAction(dto)).unwrap(); // 重新获取当前日期的数据 await getWaterRecordsByDate(dateToUse); // 如果是今天的数据,也更新今日统计 if (dateToUse === dayjs().format('YYYY-MM-DD')) { dispatch(fetchTodayWaterStats()); } return true; } catch (error: any) { console.error('更新喝水记录失败:', error); let errorMessage = '更新喝水记录失败'; if (error?.message) { errorMessage = error.message; } Toast.error(errorMessage); return false; } }, [dispatch, dateToUse, getWaterRecordsByDate]); // 删除喝水记录 const removeWaterRecord = useCallback(async (id: string) => { try { // 在删除前,尝试获取记录信息用于 HealthKit 同步 const recordToDelete = waterRecords.find(record => record.id === id); await dispatch(deleteWaterRecordAction(id)).unwrap(); // 同步删除到 HealthKit if (recordToDelete) { try { const healthKitSuccess = await deleteWaterIntakeFromHealthKit( id, recordToDelete.recordedAt || recordToDelete.createdAt ); if (!healthKitSuccess) { console.warn('从 HealthKit 删除饮水记录失败,但应用内记录已删除'); } } catch (healthError) { console.error('HealthKit 删除同步错误:', healthError); // HealthKit 同步失败不影响主要功能 } } // 重新获取当前日期的数据 await getWaterRecordsByDate(dateToUse); // 如果是今天的数据,也更新今日统计 if (dateToUse === dayjs().format('YYYY-MM-DD')) { dispatch(fetchTodayWaterStats()); } return true; } catch (error: any) { console.error('删除喝水记录失败:', error); let errorMessage = '删除喝水记录失败'; if (error?.message) { errorMessage = error.message; } Toast.error(errorMessage); return false; } }, [dispatch, dateToUse, getWaterRecordsByDate, waterRecords]); // 更新喝水目标 const updateWaterGoal = useCallback(async (goal: number) => { try { await dispatch(updateWaterGoalAction(goal)).unwrap(); return true; } catch (error: any) { console.error('更新喝水目标失败:', error); let errorMessage = '更新喝水目标失败'; if (error?.message) { errorMessage = error.message; } Toast.error(errorMessage); return false; } }, [dispatch]); // 初始化加载指定日期的数据 useEffect(() => { if (dateToUse) { getWaterRecordsByDate(dateToUse); } }, [dateToUse, getWaterRecordsByDate]); return { waterStats, dailyWaterGoal, waterRecords, waterRecordsMeta, loading, error, addWaterRecord, updateWaterRecord, removeWaterRecord, updateWaterGoal, getWaterRecordsByDate, }; };