Files
digital-pilates/hooks/useWaterData.ts
2025-09-02 15:50:35 +08:00

494 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 dayjs from 'dayjs';
import { useCallback, useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
export const useWaterData = () => {
const dispatch = useDispatch<AppDispatch>();
// 选择器
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();
// 重新获取今日统计
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]);
// 更新喝水记录
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 {
await dispatch(deleteWaterRecordAction(id)).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 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]);
// 计算总喝水量
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]);
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<AppDispatch>();
// 如果没有传入日期,默认使用今天
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();
// 重新获取当前日期的数据
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 {
await dispatch(deleteWaterRecordAction(id)).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 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,
};
};