- 新增基础代谢详情页面,包含图表展示、数据缓存和防抖机制 - 优化HRV数据获取逻辑,支持实时、近期和历史数据的智能获取 - 移除WaterIntakeCard和WaterSettings中的登录验证逻辑 - 更新饮水数据管理hook,直接使用HealthKit数据 - 添加饮水目标存储和获取功能 - 更新依赖包版本
697 lines
22 KiB
TypeScript
697 lines
22 KiB
TypeScript
import { deleteWaterIntakeFromHealthKit, getWaterIntakeFromHealthKit, saveWaterIntakeToHealthKit } from '@/utils/health';
|
||
import { Toast } from '@/utils/toast.utils';
|
||
import { getQuickWaterAmount, getWaterGoalFromStorage, setWaterGoalToStorage } from '@/utils/userPreferences';
|
||
import { refreshWidget, syncWaterDataToWidget } from '@/utils/widgetDataSync';
|
||
import dayjs from 'dayjs';
|
||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||
|
||
// 水分记录数据结构
|
||
export interface WaterRecord {
|
||
id: string;
|
||
amount: number;
|
||
recordedAt: string;
|
||
createdAt: string;
|
||
note?: string;
|
||
}
|
||
|
||
// 水分统计数据结构
|
||
export interface WaterStats {
|
||
totalAmount: number;
|
||
completionRate: number;
|
||
recordCount: number;
|
||
}
|
||
|
||
// 将 HealthKit 数据转换为应用数据格式
|
||
const convertHealthKitToWaterRecord = (healthKitRecord: any): WaterRecord => {
|
||
return {
|
||
id: healthKitRecord.id || `${healthKitRecord.startDate}_${healthKitRecord.value}`,
|
||
amount: Math.round(healthKitRecord.value), // HealthKit 已经返回毫升数值
|
||
recordedAt: healthKitRecord.startDate,
|
||
createdAt: healthKitRecord.endDate,
|
||
note: healthKitRecord.metadata?.note || undefined,
|
||
};
|
||
};
|
||
|
||
// 创建日期范围选项
|
||
function createDateRange(date: string): { startDate: string; endDate: string } {
|
||
return {
|
||
startDate: dayjs(date).startOf('day').toDate().toISOString(),
|
||
endDate: dayjs(date).endOf('day').toDate().toISOString()
|
||
};
|
||
}
|
||
|
||
export const useWaterData = () => {
|
||
// 本地状态管理
|
||
const [loading, setLoading] = useState({
|
||
records: false,
|
||
stats: false,
|
||
goal: false
|
||
});
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [dailyWaterGoal, setDailyWaterGoal] = useState<number>(2000);
|
||
const [waterRecords, setWaterRecords] = useState<{ [date: string]: WaterRecord[] }>({});
|
||
const [selectedDate, setSelectedDate] = useState<string>(dayjs().format('YYYY-MM-DD'));
|
||
|
||
// 获取指定日期的记录
|
||
const getWaterRecordsByDate = useCallback(async (date: string, page = 1, limit = 20) => {
|
||
setLoading(prev => ({ ...prev, records: true }));
|
||
setError(null);
|
||
|
||
try {
|
||
const options = createDateRange(date);
|
||
const healthKitRecords = await getWaterIntakeFromHealthKit(options);
|
||
|
||
// 转换数据格式并按时间排序
|
||
const convertedRecords = healthKitRecords
|
||
.map(convertHealthKitToWaterRecord)
|
||
.sort((a, b) => new Date(b.recordedAt).getTime() - new Date(a.recordedAt).getTime());
|
||
|
||
// 应用分页逻辑
|
||
const startIndex = (page - 1) * limit;
|
||
const endIndex = startIndex + limit;
|
||
const paginatedRecords = convertedRecords.slice(startIndex, endIndex);
|
||
|
||
setWaterRecords(prev => ({
|
||
...prev,
|
||
[date]: page === 1 ? paginatedRecords : [...(prev[date] || []), ...paginatedRecords]
|
||
}));
|
||
|
||
return paginatedRecords;
|
||
} catch (error) {
|
||
console.error('获取饮水记录失败:', error);
|
||
setError('获取饮水记录失败');
|
||
Toast.error('获取饮水记录失败');
|
||
return [];
|
||
} finally {
|
||
setLoading(prev => ({ ...prev, records: false }));
|
||
}
|
||
}, []);
|
||
|
||
// 加载更多记录(占位符,HealthKit一次性返回所有数据)
|
||
const loadMoreWaterRecords = useCallback(async () => {
|
||
// HealthKit通常一次性返回所有数据,这里保持接口一致性
|
||
return;
|
||
}, []);
|
||
|
||
// 获取日期范围的记录
|
||
const getWaterRecordsByDateRange = useCallback(async (
|
||
startDate: string,
|
||
endDate: string,
|
||
page = 1,
|
||
limit = 20
|
||
) => {
|
||
setLoading(prev => ({ ...prev, records: true }));
|
||
setError(null);
|
||
|
||
try {
|
||
const options = {
|
||
startDate: dayjs(startDate).startOf('day').toDate().toISOString(),
|
||
endDate: dayjs(endDate).endOf('day').toDate().toISOString()
|
||
};
|
||
|
||
const healthKitRecords = await getWaterIntakeFromHealthKit(options);
|
||
|
||
// 转换数据格式并按时间排序
|
||
const convertedRecords = healthKitRecords
|
||
.map(convertHealthKitToWaterRecord)
|
||
.sort((a, b) => new Date(b.recordedAt).getTime() - new Date(a.recordedAt).getTime());
|
||
|
||
// 按日期分组
|
||
const recordsByDate: { [date: string]: WaterRecord[] } = {};
|
||
convertedRecords.forEach(record => {
|
||
const date = dayjs(record.recordedAt).format('YYYY-MM-DD');
|
||
if (!recordsByDate[date]) {
|
||
recordsByDate[date] = [];
|
||
}
|
||
recordsByDate[date].push(record);
|
||
});
|
||
|
||
setWaterRecords(prev => ({ ...prev, ...recordsByDate }));
|
||
return recordsByDate;
|
||
} catch (error) {
|
||
console.error('获取日期范围饮水记录失败:', error);
|
||
setError('获取日期范围饮水记录失败');
|
||
Toast.error('获取日期范围饮水记录失败');
|
||
return {};
|
||
} finally {
|
||
setLoading(prev => ({ ...prev, records: false }));
|
||
}
|
||
}, []);
|
||
|
||
// 加载今日数据
|
||
const loadTodayData = useCallback(() => {
|
||
const today = dayjs().format('YYYY-MM-DD');
|
||
getWaterRecordsByDate(today);
|
||
}, [getWaterRecordsByDate]);
|
||
|
||
// 加载指定日期数据
|
||
const loadDataByDate = useCallback((date: string) => {
|
||
setSelectedDate(date);
|
||
getWaterRecordsByDate(date);
|
||
}, [getWaterRecordsByDate]);
|
||
|
||
// 创建喝水记录
|
||
const addWaterRecord = useCallback(async (amount: number, recordedAt?: string) => {
|
||
try {
|
||
const recordTime = recordedAt || dayjs().toISOString();
|
||
|
||
// 保存到 HealthKit
|
||
const healthKitSuccess = await saveWaterIntakeToHealthKit(amount, recordTime);
|
||
if (!healthKitSuccess) {
|
||
Toast.error('保存到 HealthKit 失败');
|
||
return false;
|
||
}
|
||
|
||
// 重新获取当前日期的数据以刷新界面
|
||
const date = dayjs(recordTime).format('YYYY-MM-DD');
|
||
await getWaterRecordsByDate(date);
|
||
|
||
// 如果是今天的数据,更新Widget
|
||
if (date === dayjs().format('YYYY-MM-DD')) {
|
||
const todayRecords = waterRecords[date] || [];
|
||
const totalAmount = todayRecords.reduce((sum, record) => sum + record.amount, 0);
|
||
const quickAddAmount = await getQuickWaterAmount();
|
||
|
||
try {
|
||
await syncWaterDataToWidget({
|
||
currentIntake: totalAmount,
|
||
dailyGoal: dailyWaterGoal,
|
||
quickAddAmount,
|
||
});
|
||
await refreshWidget();
|
||
} catch (widgetError) {
|
||
console.error('Widget 同步错误:', widgetError);
|
||
}
|
||
}
|
||
|
||
Toast.success('添加饮水记录成功');
|
||
return true;
|
||
} catch (error: any) {
|
||
console.error('添加喝水记录失败:', error);
|
||
Toast.error(error?.message || '添加喝水记录失败');
|
||
return false;
|
||
}
|
||
}, [getWaterRecordsByDate, waterRecords, dailyWaterGoal]);
|
||
|
||
// 更新喝水记录(HealthKit不支持更新,只能删除后重新添加)
|
||
const updateWaterRecord = useCallback(async (id: string, amount?: number, note?: string, recordedAt?: string) => {
|
||
try {
|
||
// 找到要更新的记录
|
||
let recordToUpdate: WaterRecord | null = null;
|
||
let recordDate = '';
|
||
|
||
for (const [date, records] of Object.entries(waterRecords)) {
|
||
const record = records.find(r => r.id === id);
|
||
if (record) {
|
||
recordToUpdate = record;
|
||
recordDate = date;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!recordToUpdate) {
|
||
Toast.error('找不到要更新的记录');
|
||
return false;
|
||
}
|
||
|
||
// 先删除旧记录
|
||
await deleteWaterIntakeFromHealthKit(id, recordToUpdate.recordedAt);
|
||
|
||
// 添加新记录
|
||
const newAmount = amount ?? recordToUpdate.amount;
|
||
const newRecordedAt = recordedAt ?? recordToUpdate.recordedAt;
|
||
|
||
const success = await addWaterRecord(newAmount, newRecordedAt);
|
||
|
||
if (success) {
|
||
Toast.success('更新饮水记录成功');
|
||
}
|
||
|
||
return success;
|
||
} catch (error: any) {
|
||
console.error('更新喝水记录失败:', error);
|
||
Toast.error(error?.message || '更新喝水记录失败');
|
||
return false;
|
||
}
|
||
}, [waterRecords, addWaterRecord]);
|
||
|
||
// 删除喝水记录
|
||
const removeWaterRecord = useCallback(async (id: string) => {
|
||
try {
|
||
// 找到要删除的记录
|
||
let recordToDelete: WaterRecord | null = null;
|
||
let recordDate = '';
|
||
|
||
for (const [date, records] of Object.entries(waterRecords)) {
|
||
const record = records.find(r => r.id === id);
|
||
if (record) {
|
||
recordToDelete = record;
|
||
recordDate = date;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!recordToDelete) {
|
||
Toast.error('找不到要删除的记录');
|
||
return false;
|
||
}
|
||
|
||
// 从 HealthKit 删除
|
||
const healthKitSuccess = await deleteWaterIntakeFromHealthKit(id, recordToDelete.recordedAt);
|
||
if (!healthKitSuccess) {
|
||
console.warn('从 HealthKit 删除记录失败,但继续更新本地数据');
|
||
}
|
||
|
||
// 更新本地状态
|
||
setWaterRecords(prev => ({
|
||
...prev,
|
||
[recordDate]: prev[recordDate]?.filter(record => record.id !== id) || []
|
||
}));
|
||
|
||
// 如果是今天的数据,更新Widget
|
||
if (recordDate === dayjs().format('YYYY-MM-DD')) {
|
||
const updatedTodayRecords = waterRecords[recordDate]?.filter(record => record.id !== id) || [];
|
||
const totalAmount = updatedTodayRecords.reduce((sum, record) => sum + record.amount, 0);
|
||
const quickAddAmount = await getQuickWaterAmount();
|
||
|
||
try {
|
||
await syncWaterDataToWidget({
|
||
currentIntake: totalAmount,
|
||
dailyGoal: dailyWaterGoal,
|
||
quickAddAmount,
|
||
});
|
||
await refreshWidget();
|
||
} catch (widgetError) {
|
||
console.error('Widget 删除同步错误:', widgetError);
|
||
}
|
||
}
|
||
|
||
Toast.success('删除饮水记录成功');
|
||
return true;
|
||
} catch (error: any) {
|
||
console.error('删除喝水记录失败:', error);
|
||
Toast.error(error?.message || '删除喝水记录失败');
|
||
return false;
|
||
}
|
||
}, [waterRecords, dailyWaterGoal]);
|
||
|
||
// 更新喝水目标
|
||
const updateWaterGoal = useCallback(async (goal: number) => {
|
||
try {
|
||
await setWaterGoalToStorage(goal);
|
||
setDailyWaterGoal(goal);
|
||
|
||
// 更新Widget
|
||
const today = dayjs().format('YYYY-MM-DD');
|
||
const todayRecords = waterRecords[today] || [];
|
||
const totalAmount = todayRecords.reduce((sum, record) => sum + record.amount, 0);
|
||
|
||
try {
|
||
const quickAddAmount = await getQuickWaterAmount();
|
||
await syncWaterDataToWidget({
|
||
dailyGoal: goal,
|
||
currentIntake: totalAmount,
|
||
quickAddAmount,
|
||
});
|
||
await refreshWidget();
|
||
} catch (widgetError) {
|
||
console.error('Widget 目标同步错误:', widgetError);
|
||
}
|
||
|
||
Toast.success('更新饮水目标成功');
|
||
return true;
|
||
} catch (error: any) {
|
||
console.error('更新喝水目标失败:', error);
|
||
Toast.error(error?.message || '更新喝水目标失败');
|
||
return false;
|
||
}
|
||
}, [waterRecords]);
|
||
|
||
// 计算总喝水量
|
||
const getTotalAmount = useCallback((records: WaterRecord[]) => {
|
||
return records.reduce((total, record) => total + record.amount, 0);
|
||
}, []);
|
||
|
||
// 按小时分组数据
|
||
const getHourlyData = useCallback((records: WaterRecord[]) => {
|
||
const hourlyData: { hour: number; amount: number }[] = Array.from({ length: 24 }, (_, i) => ({
|
||
hour: i,
|
||
amount: 0,
|
||
}));
|
||
|
||
records.forEach(record => {
|
||
const hour = dayjs(record.recordedAt).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(() => {
|
||
const loadInitialData = async () => {
|
||
try {
|
||
// 加载饮水目标
|
||
const goal = await getWaterGoalFromStorage();
|
||
setDailyWaterGoal(goal);
|
||
|
||
// 加载今日数据
|
||
loadTodayData();
|
||
} catch (error) {
|
||
console.error('加载初始数据失败:', error);
|
||
}
|
||
};
|
||
|
||
loadInitialData();
|
||
}, [loadTodayData]);
|
||
|
||
// 计算今日统计数据
|
||
const todayStats = useMemo(() => {
|
||
const today = dayjs().format('YYYY-MM-DD');
|
||
const todayRecords = waterRecords[today] || [];
|
||
const totalAmount = getTotalAmount(todayRecords);
|
||
|
||
return {
|
||
totalAmount,
|
||
completionRate: calculateCompletionRate(totalAmount, dailyWaterGoal),
|
||
recordCount: todayRecords.length
|
||
};
|
||
}, [waterRecords, dailyWaterGoal, getTotalAmount, calculateCompletionRate]);
|
||
|
||
// 同步初始数据到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: waterRecords[selectedDate] || [],
|
||
waterRecordsMeta: {
|
||
total: waterRecords[selectedDate]?.length || 0,
|
||
page: 1,
|
||
limit: 20,
|
||
hasMore: false
|
||
},
|
||
selectedDate,
|
||
loading,
|
||
error,
|
||
|
||
// 方法
|
||
loadTodayData,
|
||
loadDataByDate,
|
||
getWaterRecordsByDate,
|
||
loadMoreWaterRecords,
|
||
getWaterRecordsByDateRange,
|
||
addWaterRecord,
|
||
updateWaterRecord,
|
||
removeWaterRecord,
|
||
updateWaterGoal,
|
||
getTotalAmount,
|
||
getHourlyData,
|
||
calculateCompletionRate,
|
||
};
|
||
};
|
||
|
||
// 简化的Hook,只返回今日数据
|
||
export const useTodayWaterData = () => {
|
||
const waterData = useWaterData();
|
||
|
||
const todayRecords = useMemo(() => {
|
||
const today = dayjs().format('YYYY-MM-DD');
|
||
return waterData.waterRecords || [];
|
||
}, [waterData.waterRecords]);
|
||
|
||
const todayMeta = useMemo(() => ({
|
||
total: todayRecords.length,
|
||
page: 1,
|
||
limit: 20,
|
||
hasMore: false
|
||
}), [todayRecords.length]);
|
||
|
||
const fetchTodayWaterRecords = useCallback(async (page = 1, limit = 20) => {
|
||
const today = dayjs().format('YYYY-MM-DD');
|
||
await waterData.getWaterRecordsByDate(today, page, limit);
|
||
}, [waterData.getWaterRecordsByDate]);
|
||
|
||
return {
|
||
todayStats: waterData.todayStats,
|
||
dailyWaterGoal: waterData.dailyWaterGoal,
|
||
waterRecords: todayRecords,
|
||
waterRecordsMeta: todayMeta,
|
||
selectedDate: waterData.selectedDate,
|
||
loading: waterData.loading,
|
||
error: waterData.error,
|
||
fetchTodayWaterRecords,
|
||
loadMoreWaterRecords: waterData.loadMoreWaterRecords,
|
||
getWaterRecordsByDateRange: waterData.getWaterRecordsByDateRange,
|
||
addWaterRecord: waterData.addWaterRecord,
|
||
updateWaterRecord: waterData.updateWaterRecord,
|
||
removeWaterRecord: waterData.removeWaterRecord,
|
||
updateWaterGoal: waterData.updateWaterGoal,
|
||
};
|
||
};
|
||
|
||
// 按日期获取饮水数据的 hook
|
||
export const useWaterDataByDate = (targetDate?: string) => {
|
||
const dateToUse = targetDate || dayjs().format('YYYY-MM-DD');
|
||
|
||
// 本地状态管理
|
||
const [loading, setLoading] = useState({
|
||
records: false,
|
||
stats: false,
|
||
goal: false
|
||
});
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [dailyWaterGoal, setDailyWaterGoal] = useState<number>(2000);
|
||
const [waterRecords, setWaterRecords] = useState<WaterRecord[]>([]);
|
||
|
||
// 获取指定日期的记录
|
||
const getWaterRecordsByDate = useCallback(async (date: string, page = 1, limit = 20) => {
|
||
setLoading(prev => ({ ...prev, records: true }));
|
||
setError(null);
|
||
|
||
try {
|
||
const options = createDateRange(date);
|
||
const healthKitRecords = await getWaterIntakeFromHealthKit(options);
|
||
|
||
// 转换数据格式并按时间排序
|
||
const convertedRecords = healthKitRecords
|
||
.map(convertHealthKitToWaterRecord)
|
||
.sort((a, b) => new Date(b.recordedAt).getTime() - new Date(a.recordedAt).getTime());
|
||
|
||
setWaterRecords(convertedRecords);
|
||
return convertedRecords;
|
||
} catch (error) {
|
||
console.error('获取饮水记录失败:', error);
|
||
setError('获取饮水记录失败');
|
||
Toast.error('获取饮水记录失败');
|
||
return [];
|
||
} finally {
|
||
setLoading(prev => ({ ...prev, records: false }));
|
||
}
|
||
}, []);
|
||
|
||
// 创建喝水记录
|
||
const addWaterRecord = useCallback(async (amount: number, recordedAt?: string) => {
|
||
try {
|
||
const recordTime = recordedAt || dayjs().toISOString();
|
||
|
||
// 保存到 HealthKit
|
||
const healthKitSuccess = await saveWaterIntakeToHealthKit(amount, recordTime);
|
||
if (!healthKitSuccess) {
|
||
Toast.error('保存到 HealthKit 失败');
|
||
return false;
|
||
}
|
||
|
||
// 重新获取当前日期的数据以刷新界面
|
||
await getWaterRecordsByDate(dateToUse);
|
||
|
||
// 如果是今天的数据,更新Widget
|
||
if (dateToUse === dayjs().format('YYYY-MM-DD')) {
|
||
const totalAmount = waterRecords.reduce((sum, record) => sum + record.amount, 0) + amount;
|
||
const quickAddAmount = await getQuickWaterAmount();
|
||
|
||
try {
|
||
await syncWaterDataToWidget({
|
||
currentIntake: totalAmount,
|
||
dailyGoal: dailyWaterGoal,
|
||
quickAddAmount,
|
||
});
|
||
await refreshWidget();
|
||
} catch (widgetError) {
|
||
console.error('Widget 同步错误:', widgetError);
|
||
}
|
||
}
|
||
|
||
Toast.success('添加饮水记录成功');
|
||
return true;
|
||
} catch (error: any) {
|
||
console.error('添加喝水记录失败:', error);
|
||
Toast.error(error?.message || '添加喝水记录失败');
|
||
return false;
|
||
}
|
||
}, [getWaterRecordsByDate, dateToUse, waterRecords, dailyWaterGoal]);
|
||
|
||
// 更新喝水记录
|
||
const updateWaterRecord = useCallback(async (id: string, amount?: number, note?: string, recordedAt?: string) => {
|
||
try {
|
||
const recordToUpdate = waterRecords.find(r => r.id === id);
|
||
if (!recordToUpdate) {
|
||
Toast.error('找不到要更新的记录');
|
||
return false;
|
||
}
|
||
|
||
// 先删除旧记录
|
||
await deleteWaterIntakeFromHealthKit(id, recordToUpdate.recordedAt);
|
||
|
||
// 添加新记录
|
||
const newAmount = amount ?? recordToUpdate.amount;
|
||
const newRecordedAt = recordedAt ?? recordToUpdate.recordedAt;
|
||
|
||
const success = await addWaterRecord(newAmount, newRecordedAt);
|
||
return success;
|
||
} catch (error: any) {
|
||
console.error('更新喝水记录失败:', error);
|
||
Toast.error(error?.message || '更新喝水记录失败');
|
||
return false;
|
||
}
|
||
}, [waterRecords, addWaterRecord]);
|
||
|
||
// 删除喝水记录
|
||
const removeWaterRecord = useCallback(async (id: string) => {
|
||
try {
|
||
const recordToDelete = waterRecords.find(r => r.id === id);
|
||
if (!recordToDelete) {
|
||
Toast.error('找不到要删除的记录');
|
||
return false;
|
||
}
|
||
|
||
// 从 HealthKit 删除
|
||
await deleteWaterIntakeFromHealthKit(id, recordToDelete.recordedAt);
|
||
|
||
// 重新获取数据
|
||
await getWaterRecordsByDate(dateToUse);
|
||
|
||
// 如果是今天的数据,更新Widget
|
||
if (dateToUse === dayjs().format('YYYY-MM-DD')) {
|
||
const updatedRecords = waterRecords.filter(record => record.id !== id);
|
||
const totalAmount = updatedRecords.reduce((sum, record) => sum + record.amount, 0);
|
||
const quickAddAmount = await getQuickWaterAmount();
|
||
|
||
try {
|
||
await syncWaterDataToWidget({
|
||
currentIntake: totalAmount,
|
||
dailyGoal: dailyWaterGoal,
|
||
quickAddAmount,
|
||
});
|
||
await refreshWidget();
|
||
} catch (widgetError) {
|
||
console.error('Widget 删除同步错误:', widgetError);
|
||
}
|
||
}
|
||
|
||
Toast.success('删除饮水记录成功');
|
||
return true;
|
||
} catch (error: any) {
|
||
console.error('删除喝水记录失败:', error);
|
||
Toast.error(error?.message || '删除喝水记录失败');
|
||
return false;
|
||
}
|
||
}, [waterRecords, getWaterRecordsByDate, dateToUse, dailyWaterGoal]);
|
||
|
||
// 更新喝水目标
|
||
const updateWaterGoal = useCallback(async (goal: number) => {
|
||
try {
|
||
await setWaterGoalToStorage(goal);
|
||
setDailyWaterGoal(goal);
|
||
Toast.success('更新饮水目标成功');
|
||
return true;
|
||
} catch (error: any) {
|
||
console.error('更新喝水目标失败:', error);
|
||
Toast.error(error?.message || '更新喝水目标失败');
|
||
return false;
|
||
}
|
||
}, []);
|
||
|
||
// 计算指定日期的统计数据
|
||
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]);
|
||
|
||
// 初始化加载指定日期的数据
|
||
useEffect(() => {
|
||
const loadInitialData = async () => {
|
||
try {
|
||
// 加载饮水目标
|
||
const goal = await getWaterGoalFromStorage();
|
||
setDailyWaterGoal(goal);
|
||
|
||
// 加载指定日期数据
|
||
await getWaterRecordsByDate(dateToUse);
|
||
} catch (error) {
|
||
console.error('加载初始数据失败:', error);
|
||
}
|
||
};
|
||
|
||
loadInitialData();
|
||
}, [dateToUse, getWaterRecordsByDate]);
|
||
|
||
return {
|
||
waterStats,
|
||
dailyWaterGoal,
|
||
waterRecords,
|
||
waterRecordsMeta: {
|
||
total: waterRecords.length,
|
||
page: 1,
|
||
limit: 20,
|
||
hasMore: false
|
||
},
|
||
loading,
|
||
error,
|
||
addWaterRecord,
|
||
updateWaterRecord,
|
||
removeWaterRecord,
|
||
updateWaterGoal,
|
||
getWaterRecordsByDate,
|
||
};
|
||
}; |