feat: 新增基础代谢详情页面并优化HRV数据获取逻辑
- 新增基础代谢详情页面,包含图表展示、数据缓存和防抖机制 - 优化HRV数据获取逻辑,支持实时、近期和历史数据的智能获取 - 移除WaterIntakeCard和WaterSettings中的登录验证逻辑 - 更新饮水数据管理hook,直接使用HealthKit数据 - 添加饮水目标存储和获取功能 - 更新依赖包版本
This commit is contained in:
205
utils/health.ts
205
utils/health.ts
@@ -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数据失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 是否开启消息推送
|
||||
|
||||
Reference in New Issue
Block a user