import { ROUTES } from '@/constants/Routes'; import { useAppSelector } from '@/hooks/redux'; import { selectUserAge, selectUserProfile } from '@/store/userSlice'; import { fetchBasalEnergyBurned } from '@/utils/health'; import dayjs from 'dayjs'; import { Image } from 'expo-image'; import { router } from 'expo-router'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; interface BasalMetabolismCardProps { selectedDate?: Date; style?: any; } export function BasalMetabolismCard({ selectedDate, style }: BasalMetabolismCardProps) { const [basalMetabolism, setBasalMetabolism] = useState(null); const [loading, setLoading] = useState(false); // 获取用户基本信息 const userProfile = useAppSelector(selectUserProfile); const userAge = useAppSelector(selectUserAge); // 缓存和防抖相关 const cacheRef = useRef>(new Map()); const loadingRef = useRef>>(new Map()); const CACHE_DURATION = 5 * 60 * 1000; // 5分钟缓存 // 使用 useMemo 缓存 BMR 计算,避免每次渲染重复计算 const bmrRange = useMemo(() => { const { gender, weight, height } = userProfile; // 检查是否有足够的信息来计算BMR if (!gender || !weight || !height || !userAge) { return null; } // 将体重和身高转换为数字 const weightNum = parseFloat(weight); const heightNum = parseFloat(height); if (isNaN(weightNum) || isNaN(heightNum) || weightNum <= 0 || heightNum <= 0 || userAge <= 0) { return null; } // 使用Mifflin-St Jeor公式计算BMR let bmr: number; if (gender === 'male') { bmr = 10 * weightNum + 6.25 * heightNum - 5 * userAge + 5; } else { bmr = 10 * weightNum + 6.25 * heightNum - 5 * userAge - 161; } // 计算正常范围(±15%) const minBMR = Math.round(bmr * 0.85); const maxBMR = Math.round(bmr * 1.15); return { min: minBMR, max: maxBMR, base: Math.round(bmr) }; }, [userProfile.gender, userProfile.weight, userProfile.height, userAge]); // 优化的数据获取函数,包含缓存和去重复请求 const fetchBasalMetabolismData = useCallback(async (date: Date): Promise => { 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(() => { if (!selectedDate) return; let isCancelled = false; const loadData = async () => { setLoading(true); try { const result = await fetchBasalMetabolismData(selectedDate); if (!isCancelled) { setBasalMetabolism(result); } } finally { if (!isCancelled) { setLoading(false); } } }; loadData(); // 清理函数,防止组件卸载后的状态更新 return () => { isCancelled = true; }; }, [selectedDate, fetchBasalMetabolismData]); // 使用 useMemo 优化状态描述计算 const status = useMemo(() => { if (basalMetabolism === null || basalMetabolism === 0) { return { text: '未知', color: '#9AA3AE' }; } // 基于常见的基础代谢范围来判断状态 if (basalMetabolism >= 1800) { return { text: '高代谢', color: '#10B981' }; } else if (basalMetabolism >= 1400) { return { text: '正常', color: '#3B82F6' }; } else if (basalMetabolism >= 1000) { return { text: '偏低', color: '#F59E0B' }; } else { return { text: '较低', color: '#EF4444' }; } }, [basalMetabolism]); return ( <> router.push(ROUTES.BASAL_METABOLISM_DETAIL)} activeOpacity={0.8} > {/* 头部区域 */} 基础代谢 {status.text} {/* 数值显示区域 */} {loading ? '加载中...' : (basalMetabolism != null && basalMetabolism > 0 ? Math.round(basalMetabolism).toString() : '--')} 千卡/日 ); } const styles = StyleSheet.create({ container: { backgroundColor: '#FFFFFF', borderRadius: 20, padding: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 4, }, shadowOpacity: 0.08, shadowRadius: 12, elevation: 4, position: 'relative', overflow: 'hidden', }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, zIndex: 1, }, leftSection: { flexDirection: 'row', alignItems: 'center', }, iconContainer: { width: 32, height: 32, borderRadius: 10, backgroundColor: '#FFFFFF', alignItems: 'center', justifyContent: 'center', marginRight: 8, shadowColor: '#000', shadowOffset: { width: 0, height: 1, }, shadowOpacity: 0.1, shadowRadius: 2, elevation: 1, }, fireIcon: { width: 14, height: 18, backgroundColor: '#EF4444', borderTopLeftRadius: 7, borderTopRightRadius: 7, borderBottomLeftRadius: 2, borderBottomRightRadius: 2, }, title: { fontSize: 14, color: '#0F172A', fontWeight: '600', }, titleIcon: { width: 16, height: 16, marginRight: 4, }, statusBadge: { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 12, }, statusText: { fontSize: 11, fontWeight: '600', }, valueSection: { flexDirection: 'row', alignItems: 'center', zIndex: 1, }, value: { fontSize: 16, fontWeight: '600', color: '#0F172A', lineHeight: 28, }, unit: { fontSize: 12, color: '#64748B', marginLeft: 6, }, });