feat: 新增基础代谢详情页面并优化HRV数据获取逻辑

- 新增基础代谢详情页面,包含图表展示、数据缓存和防抖机制
- 优化HRV数据获取逻辑,支持实时、近期和历史数据的智能获取
- 移除WaterIntakeCard和WaterSettings中的登录验证逻辑
- 更新饮水数据管理hook,直接使用HealthKit数据
- 添加饮水目标存储和获取功能
- 更新依赖包版本
This commit is contained in:
richarjiang
2025-09-25 14:15:42 +08:00
parent 83e534c4a7
commit 79ab354f31
12 changed files with 1563 additions and 702 deletions

View File

@@ -518,32 +518,70 @@ async function fetchHeartRateVariability(options: HealthDataOptions): Promise<HR
if (result && result.data && Array.isArray(result.data) && result.data.length > 0) {
let selectedSample: any = null;
let bestQuality = -1;
console.log('result~~~', result);
console.log(`获取到 ${result.data.length} 个HRV样本`);
// 优先使用优化后的最佳质量值对应的样本
// 首先尝试使用最佳质量值
if (result.bestQualityValue && typeof result.bestQualityValue === 'number') {
const qualityValue = validateHRVValue(result.bestQualityValue);
if (qualityValue !== null) {
// 找到对应的最佳质量样本
selectedSample = result.data[result.data.length - 1];
// 找到质量分数最高的样本
for (const sample of result.data) {
const sampleQuality = sample.qualityScore || 0;
const sampleValue = validateHRVValue(sample.value);
logSuccess('HRV数据最佳质量', {
value: qualityValue,
totalSamples: result.data.length,
recordedAt: selectedSample.endDate
});
if (sampleValue !== null && sampleQuality > bestQuality) {
bestQuality = sampleQuality;
selectedSample = sample;
}
}
if (selectedSample) {
logSuccess('HRV数据最佳质量', {
value: qualityValue,
qualityScore: bestQuality,
totalSamples: result.data.length,
recordedAt: selectedSample.endDate
});
}
}
}
// 如果没有找到最佳质量样本,使用第一个有效样本
// 如果没有找到最佳质量样本,或者最佳质量值无效,重新评估所有样本
if (!selectedSample) {
for (const sample of result.data) {
console.log('重新评估所有样本以找到最佳数据...');
// 按质量分数、手动测量标志和时间排序
const sortedSamples = result.data.sort((a: any, b: any) => {
const qualityA = a.qualityScore || 0;
const qualityB = b.qualityScore || 0;
const isManualA = a.isManualMeasurement || false;
const isManualB = b.isManualMeasurement || false;
// 手动测量优先
if (isManualA && !isManualB) return -1;
if (!isManualA && isManualB) return 1;
// 质量分数优先
if (qualityA !== qualityB) return qualityB - qualityA;
// 时间优先(最新的优先)
const dateA = new Date(a.endDate || a.startDate).getTime();
const dateB = new Date(b.endDate || b.startDate).getTime();
return dateB - dateA;
});
// 选择第一个有效样本
for (const sample of sortedSamples) {
const sampleValue = validateHRVValue(sample.value);
if (sampleValue !== null) {
selectedSample = sample;
console.log('使用有效HRV样本:', {
bestQuality = sample.qualityScore || 0;
console.log('选择最佳HRV样本:', {
value: sampleValue,
qualityScore: bestQuality,
isManual: sample.isManualMeasurement,
source: sample.source?.name,
recordedAt: sample.endDate
});
@@ -733,8 +771,35 @@ export async function fetchTodayHealthData(): Promise<TodayHealthData> {
export async function fetchHRVForDate(date: Date): Promise<HRVData | null> {
console.log('开始获取指定日期HRV数据...', date);
// 首先尝试获取指定日期的HRV数据
const options = createDateRange(date);
return fetchHeartRateVariability(options);
const hrvData = await fetchHeartRateVariability(options);
// 如果当天没有数据尝试获取最近7天内的最新数据
if (!hrvData) {
console.log('指定日期无HRV数据尝试获取最近7天内的数据...');
const endDate = new Date(date);
const startDate = new Date(date);
startDate.setDate(startDate.getDate() - 7); // 往前推7天
const recentOptions = {
startDate: startDate.toISOString(),
endDate: endDate.toISOString()
};
const recentHrvData = await fetchHeartRateVariability(recentOptions);
if (recentHrvData) {
console.log('获取到最近7天内的HRV数据:', recentHrvData);
return recentHrvData;
} else {
console.log('最近7天内也无HRV数据');
}
}
return hrvData;
}
export async function fetchTodayHRV(): Promise<HRVData | null> {
@@ -1189,3 +1254,117 @@ export async function fetchHRVWithAnalysis(date: Date): Promise<{ hrvData: HRVDa
}
}
// 智能HRV数据获取 - 优先获取实时数据,如果没有则获取历史数据
export async function fetchSmartHRVData(date: Date): Promise<HRVData | null> {
console.log('开始智能HRV数据获取...', date);
try {
// 1. 首先尝试获取最近2小时的实时数据
console.log('1. 尝试获取最近2小时的实时HRV数据...');
const recentHRV = await fetchRecentHRV(2);
if (recentHRV) {
console.log('✅ 成功获取到实时HRV数据:', recentHRV);
// 检查数据是否足够新1小时内
const dataTime = new Date(recentHRV.recordedAt).getTime();
const now = Date.now();
const oneHour = 60 * 60 * 1000;
if (now - dataTime <= oneHour) {
console.log('✅ 实时数据足够新,直接使用');
return recentHRV;
} else {
console.log('⚠️ 实时数据较旧,继续寻找更好的数据');
}
}
// 2. 如果没有实时数据或数据太旧,尝试获取当天的数据
console.log('2. 尝试获取当天的HRV数据...');
const todayHRV = await fetchHRVForDate(date);
if (todayHRV) {
console.log('✅ 成功获取到当天HRV数据:', todayHRV);
return todayHRV;
}
// 3. 如果当天没有数据尝试获取最近3天的数据
console.log('3. 尝试获取最近3天的HRV数据...');
const endDate = new Date(date);
const startDate = new Date(date);
startDate.setDate(startDate.getDate() - 3);
const recentOptions = {
startDate: startDate.toISOString(),
endDate: endDate.toISOString()
};
const recentData = await fetchHeartRateVariability(recentOptions);
if (recentData) {
console.log('✅ 成功获取到最近3天的HRV数据:', recentData);
return recentData;
}
// 4. 如果仍然没有数据返回null
console.log('❌ 未找到任何HRV数据');
return null;
} catch (error) {
console.error('智能HRV数据获取失败:', error);
return null;
}
}
// 获取HRV数据并附带详细的状态信息
export async function fetchHRVWithStatus(date: Date): Promise<{
hrvData: HRVData | null;
status: 'realtime' | 'recent' | 'historical' | 'none';
message: string;
}> {
try {
const hrvData = await fetchSmartHRVData(date);
if (!hrvData) {
return {
hrvData: null,
status: 'none',
message: '未找到HRV数据'
};
}
const dataTime = new Date(hrvData.recordedAt).getTime();
const now = Date.now();
const oneHour = 60 * 60 * 1000;
const oneDay = 24 * 60 * 60 * 1000;
let status: 'realtime' | 'recent' | 'historical';
let message: string;
if (now - dataTime <= oneHour) {
status = 'realtime';
message = '实时HRV数据';
} else if (now - dataTime <= oneDay) {
status = 'recent';
message = '近期HRV数据';
} else {
status = 'historical';
message = '历史HRV数据';
}
return {
hrvData,
status,
message
};
} catch (error) {
console.error('获取HRV状态失败:', error);
return {
hrvData: null,
status: 'none',
message: '获取HRV数据失败'
};
}
}

View File

@@ -3,6 +3,7 @@ import AsyncStorage from '@/utils/kvStore';
// 用户偏好设置的存储键
const PREFERENCES_KEYS = {
QUICK_WATER_AMOUNT: 'user_preference_quick_water_amount',
WATER_GOAL: 'user_preference_water_goal',
NOTIFICATION_ENABLED: 'user_preference_notification_enabled',
FITNESS_EXERCISE_MINUTES_INFO_DISMISSED: 'user_preference_fitness_exercise_minutes_info_dismissed',
FITNESS_ACTIVE_HOURS_INFO_DISMISSED: 'user_preference_fitness_active_hours_info_dismissed',
@@ -11,6 +12,7 @@ const PREFERENCES_KEYS = {
// 用户偏好设置接口
export interface UserPreferences {
quickWaterAmount: number;
waterGoal: number;
notificationEnabled: boolean;
fitnessExerciseMinutesInfoDismissed: boolean;
fitnessActiveHoursInfoDismissed: boolean;
@@ -19,6 +21,7 @@ export interface UserPreferences {
// 默认的用户偏好设置
const DEFAULT_PREFERENCES: UserPreferences = {
quickWaterAmount: 150, // 默认快速添加饮水量为 150ml
waterGoal: 2000, // 默认每日饮水目标为 2000ml
notificationEnabled: true, // 默认开启消息推送
fitnessExerciseMinutesInfoDismissed: false, // 默认显示锻炼分钟说明
fitnessActiveHoursInfoDismissed: false, // 默认显示活动小时说明
@@ -30,12 +33,14 @@ const DEFAULT_PREFERENCES: UserPreferences = {
export const getUserPreferences = async (): Promise<UserPreferences> => {
try {
const quickWaterAmount = await AsyncStorage.getItem(PREFERENCES_KEYS.QUICK_WATER_AMOUNT);
const waterGoal = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_GOAL);
const notificationEnabled = await AsyncStorage.getItem(PREFERENCES_KEYS.NOTIFICATION_ENABLED);
const fitnessExerciseMinutesInfoDismissed = await AsyncStorage.getItem(PREFERENCES_KEYS.FITNESS_EXERCISE_MINUTES_INFO_DISMISSED);
const fitnessActiveHoursInfoDismissed = await AsyncStorage.getItem(PREFERENCES_KEYS.FITNESS_ACTIVE_HOURS_INFO_DISMISSED);
return {
quickWaterAmount: quickWaterAmount ? parseInt(quickWaterAmount, 10) : DEFAULT_PREFERENCES.quickWaterAmount,
waterGoal: waterGoal ? parseInt(waterGoal, 10) : DEFAULT_PREFERENCES.waterGoal,
notificationEnabled: notificationEnabled !== null ? notificationEnabled === 'true' : DEFAULT_PREFERENCES.notificationEnabled,
fitnessExerciseMinutesInfoDismissed: fitnessExerciseMinutesInfoDismissed !== null ? fitnessExerciseMinutesInfoDismissed === 'true' : DEFAULT_PREFERENCES.fitnessExerciseMinutesInfoDismissed,
fitnessActiveHoursInfoDismissed: fitnessActiveHoursInfoDismissed !== null ? fitnessActiveHoursInfoDismissed === 'true' : DEFAULT_PREFERENCES.fitnessActiveHoursInfoDismissed,
@@ -74,6 +79,34 @@ export const getQuickWaterAmount = async (): Promise<number> => {
}
};
/**
* 设置每日饮水目标
* @param goal 饮水目标(毫升)
*/
export const setWaterGoalToStorage = async (goal: number): Promise<void> => {
try {
// 确保值在合理范围内500ml - 5000ml
const validGoal = Math.max(500, Math.min(5000, goal));
await AsyncStorage.setItem(PREFERENCES_KEYS.WATER_GOAL, validGoal.toString());
} catch (error) {
console.error('设置每日饮水目标失败:', error);
throw error;
}
};
/**
* 获取每日饮水目标
*/
export const getWaterGoalFromStorage = async (): Promise<number> => {
try {
const goal = await AsyncStorage.getItem(PREFERENCES_KEYS.WATER_GOAL);
return goal ? parseInt(goal, 10) : DEFAULT_PREFERENCES.waterGoal;
} catch (error) {
console.error('获取每日饮水目标失败:', error);
return DEFAULT_PREFERENCES.waterGoal;
}
};
/**
* 设置消息推送开关
* @param enabled 是否开启消息推送