feat: 修复健康数据
This commit is contained in:
@@ -5,7 +5,7 @@ import { fetchBasalEnergyBurned } from '@/utils/health';
|
||||
import dayjs from 'dayjs';
|
||||
import { Image } from 'expo-image';
|
||||
import { router } from 'expo-router';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
interface BasalMetabolismCardProps {
|
||||
@@ -22,8 +22,13 @@ export function BasalMetabolismCard({ selectedDate, style }: BasalMetabolismCard
|
||||
const userProfile = useAppSelector(selectUserProfile);
|
||||
const userAge = useAppSelector(selectUserAge);
|
||||
|
||||
// 计算基础代谢率范围
|
||||
const calculateBMRRange = () => {
|
||||
// 缓存和防抖相关
|
||||
const cacheRef = useRef<Map<string, { data: number | null; timestamp: number }>>(new Map());
|
||||
const loadingRef = useRef<Map<string, Promise<number | null>>>(new Map());
|
||||
const CACHE_DURATION = 5 * 60 * 1000; // 5分钟缓存
|
||||
|
||||
// 使用 useMemo 缓存 BMR 计算,避免每次渲染重复计算
|
||||
const bmrRange = useMemo(() => {
|
||||
const { gender, weight, height } = userProfile;
|
||||
|
||||
// 检查是否有足够的信息来计算BMR
|
||||
@@ -52,35 +57,83 @@ export function BasalMetabolismCard({ selectedDate, style }: BasalMetabolismCard
|
||||
const maxBMR = Math.round(bmr * 1.15);
|
||||
|
||||
return { min: minBMR, max: maxBMR, base: Math.round(bmr) };
|
||||
};
|
||||
}, [userProfile.gender, userProfile.weight, userProfile.height, userAge]);
|
||||
|
||||
const bmrRange = calculateBMRRange();
|
||||
// 优化的数据获取函数,包含缓存和去重复请求
|
||||
const fetchBasalMetabolismData = useCallback(async (date: Date): Promise<number | null> => {
|
||||
const dateKey = dayjs(date).format('YYYY-MM-DD');
|
||||
const now = Date.now();
|
||||
|
||||
// 检查缓存
|
||||
const cached = cacheRef.current.get(dateKey);
|
||||
if (cached && (now - cached.timestamp) < CACHE_DURATION) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
// 检查是否已经在请求中(防止重复请求)
|
||||
const existingRequest = loadingRef.current.get(dateKey);
|
||||
if (existingRequest) {
|
||||
return existingRequest;
|
||||
}
|
||||
|
||||
// 创建新的请求
|
||||
const request = (async () => {
|
||||
try {
|
||||
const options = {
|
||||
startDate: dayjs(date).startOf('day').toDate().toISOString(),
|
||||
endDate: dayjs(date).endOf('day').toDate().toISOString()
|
||||
};
|
||||
const basalEnergy = await fetchBasalEnergyBurned(options);
|
||||
const result = basalEnergy || null;
|
||||
|
||||
// 更新缓存
|
||||
cacheRef.current.set(dateKey, { data: result, timestamp: now });
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('BasalMetabolismCard: 获取基础代谢数据失败:', error);
|
||||
return null;
|
||||
} finally {
|
||||
// 清理请求记录
|
||||
loadingRef.current.delete(dateKey);
|
||||
}
|
||||
})();
|
||||
|
||||
// 记录请求
|
||||
loadingRef.current.set(dateKey, request);
|
||||
|
||||
return request;
|
||||
}, []);
|
||||
|
||||
// 获取基础代谢数据
|
||||
useEffect(() => {
|
||||
const loadBasalMetabolismData = async () => {
|
||||
if (!selectedDate) return;
|
||||
if (!selectedDate) return;
|
||||
|
||||
let isCancelled = false;
|
||||
|
||||
const loadData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
setLoading(true);
|
||||
const options = {
|
||||
startDate: dayjs(selectedDate).startOf('day').toDate().toISOString(),
|
||||
endDate: dayjs(selectedDate).endOf('day').toDate().toISOString()
|
||||
};
|
||||
const basalEnergy = await fetchBasalEnergyBurned(options);
|
||||
setBasalMetabolism(basalEnergy || null);
|
||||
} catch (error) {
|
||||
console.error('BasalMetabolismCard: 获取基础代谢数据失败:', error);
|
||||
setBasalMetabolism(null);
|
||||
const result = await fetchBasalMetabolismData(selectedDate);
|
||||
if (!isCancelled) {
|
||||
setBasalMetabolism(result);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (!isCancelled) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadBasalMetabolismData();
|
||||
}, [selectedDate]);
|
||||
// 获取基础代谢状态描述
|
||||
const getMetabolismStatus = () => {
|
||||
loadData();
|
||||
|
||||
// 清理函数,防止组件卸载后的状态更新
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
};
|
||||
}, [selectedDate, fetchBasalMetabolismData]);
|
||||
// 使用 useMemo 优化状态描述计算
|
||||
const status = useMemo(() => {
|
||||
if (basalMetabolism === null || basalMetabolism === 0) {
|
||||
return { text: '未知', color: '#9AA3AE' };
|
||||
}
|
||||
@@ -95,9 +148,7 @@ export function BasalMetabolismCard({ selectedDate, style }: BasalMetabolismCard
|
||||
} else {
|
||||
return { text: '较低', color: '#EF4444' };
|
||||
}
|
||||
};
|
||||
|
||||
const status = getMetabolismStatus();
|
||||
}, [basalMetabolism]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { AnimatedNumber } from '@/components/AnimatedNumber';
|
||||
import { ROUTES } from '@/constants/Routes';
|
||||
import { useActiveCalories } from '@/hooks/useActiveCalories';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { fetchCompleteNutritionCardData, selectNutritionCardDataByDate } from '@/store/nutritionSlice';
|
||||
import { fetchDailyBasalMetabolism, fetchDailyNutritionData, selectBasalMetabolismByDate, selectNutritionSummaryByDate } from '@/store/nutritionSlice';
|
||||
import { triggerLightHaptic } from '@/utils/haptics';
|
||||
import { calculateRemainingCalories } from '@/utils/nutrition';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -102,17 +103,24 @@ export function NutritionRadarCard({
|
||||
return selectedDate ? dayjs(selectedDate).format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD');
|
||||
}, [selectedDate]);
|
||||
|
||||
const cardData = useAppSelector(selectNutritionCardDataByDate(dateKey));
|
||||
const { nutritionSummary, healthData, basalMetabolism } = cardData;
|
||||
// 使用专用的选择器获取营养数据和基础代谢
|
||||
const nutritionSummary = useAppSelector(selectNutritionSummaryByDate(dateKey));
|
||||
const basalMetabolism = useAppSelector(selectBasalMetabolismByDate(dateKey));
|
||||
|
||||
// 获取营养和健康数据
|
||||
// 使用专用的hook获取运动消耗卡路里
|
||||
const { activeCalories: effectiveActiveCalories, loading: activeCaloriesLoading } = useActiveCalories(selectedDate);
|
||||
|
||||
// 获取营养数据和基础代谢数据
|
||||
useEffect(() => {
|
||||
const loadNutritionCardData = async () => {
|
||||
const targetDate = selectedDate || new Date();
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
await dispatch(fetchCompleteNutritionCardData(targetDate)).unwrap();
|
||||
await Promise.all([
|
||||
dispatch(fetchDailyNutritionData(targetDate)).unwrap(),
|
||||
dispatch(fetchDailyBasalMetabolism(targetDate)).unwrap(),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error('NutritionRadarCard: 获取营养卡片数据失败:', error);
|
||||
} finally {
|
||||
@@ -139,7 +147,6 @@ export function NutritionRadarCard({
|
||||
|
||||
// 使用从HealthKit获取的数据,如果没有则使用默认值
|
||||
const effectiveBasalMetabolism = basalMetabolism || 0; // 基础代谢默认值
|
||||
const effectiveActiveCalories = healthData?.activeCalories || 0; // 运动消耗卡路里
|
||||
|
||||
const remainingCalories = calculateRemainingCalories({
|
||||
basalMetabolism: effectiveBasalMetabolism,
|
||||
@@ -171,8 +178,8 @@ export function NutritionRadarCard({
|
||||
<View style={styles.contentContainer}>
|
||||
<View style={styles.radarContainer}>
|
||||
<SimpleRingProgress
|
||||
remainingCalories={loading ? 0 : remainingCalories}
|
||||
totalAvailable={loading ? 0 : effectiveBasalMetabolism + effectiveActiveCalories}
|
||||
remainingCalories={(loading || activeCaloriesLoading) ? 0 : remainingCalories}
|
||||
totalAvailable={(loading || activeCaloriesLoading) ? 0 : effectiveBasalMetabolism + effectiveActiveCalories}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -195,10 +202,10 @@ export function NutritionRadarCard({
|
||||
<Text style={styles.calorieSubtitle}>还能吃</Text>
|
||||
<View style={styles.remainingCaloriesContainer}>
|
||||
<AnimatedNumber
|
||||
value={loading ? 0 : remainingCalories}
|
||||
value={(loading || activeCaloriesLoading) ? 0 : remainingCalories}
|
||||
resetToken={resetToken}
|
||||
style={styles.mainValue}
|
||||
format={(v) => loading ? '--' : Math.round(v).toString()}
|
||||
format={(v) => (loading || activeCaloriesLoading) ? '--' : Math.round(v).toString()}
|
||||
/>
|
||||
<Text style={styles.calorieUnit}>千卡</Text>
|
||||
</View>
|
||||
@@ -217,10 +224,10 @@ export function NutritionRadarCard({
|
||||
<Text style={styles.calculationLabel}>运动</Text>
|
||||
</View>
|
||||
<AnimatedNumber
|
||||
value={loading ? 0 : effectiveActiveCalories}
|
||||
value={activeCaloriesLoading ? 0 : effectiveActiveCalories}
|
||||
resetToken={resetToken}
|
||||
style={styles.calculationValue}
|
||||
format={(v) => loading ? '--' : Math.round(v).toString()}
|
||||
format={(v) => activeCaloriesLoading ? '--' : Math.round(v).toString()}
|
||||
/>
|
||||
<Text style={styles.calculationText}> - </Text>
|
||||
<View style={styles.calculationItem}>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Animated,
|
||||
InteractionManager,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewStyle,
|
||||
InteractionManager
|
||||
ViewStyle
|
||||
} from 'react-native';
|
||||
|
||||
import { fetchHourlyStepSamples, fetchStepCount, HourlyStepData } from '@/utils/health';
|
||||
@@ -39,18 +39,12 @@ const StepsCard: React.FC<StepsCardProps> = ({
|
||||
logger.info('获取步数数据...');
|
||||
|
||||
// 先获取步数,立即更新UI
|
||||
const steps = await fetchStepCount(date);
|
||||
const [steps, hourly] = await Promise.all([
|
||||
fetchStepCount(date),
|
||||
fetchHourlyStepSamples(date)
|
||||
]);
|
||||
setStepCount(steps);
|
||||
|
||||
// 使用 InteractionManager 在空闲时获取更复杂的小时数据
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
try {
|
||||
const hourly = await fetchHourlyStepSamples(date);
|
||||
setHourSteps(hourly);
|
||||
} catch (error) {
|
||||
logger.error('获取小时步数数据失败:', error);
|
||||
}
|
||||
});
|
||||
setHourSteps(hourly);
|
||||
|
||||
} catch (error) {
|
||||
logger.error('获取步数数据失败:', error);
|
||||
|
||||
Reference in New Issue
Block a user