feat: 新增基础代谢详情页面并优化HRV数据获取逻辑
- 新增基础代谢详情页面,包含图表展示、数据缓存和防抖机制 - 优化HRV数据获取逻辑,支持实时、近期和历史数据的智能获取 - 移除WaterIntakeCard和WaterSettings中的登录验证逻辑 - 更新饮水数据管理hook,直接使用HealthKit数据 - 添加饮水目标存储和获取功能 - 更新依赖包版本
This commit is contained in:
@@ -6,7 +6,7 @@ import dayjs from 'dayjs';
|
||||
import { Image } from 'expo-image';
|
||||
import { router } from 'expo-router';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Modal, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
interface BasalMetabolismCardProps {
|
||||
selectedDate?: Date;
|
||||
@@ -16,7 +16,6 @@ interface BasalMetabolismCardProps {
|
||||
export function BasalMetabolismCard({ selectedDate, style }: BasalMetabolismCardProps) {
|
||||
const [basalMetabolism, setBasalMetabolism] = useState<number | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
|
||||
// 获取用户基本信息
|
||||
const userProfile = useAppSelector(selectUserProfile);
|
||||
@@ -154,7 +153,7 @@ export function BasalMetabolismCard({ selectedDate, style }: BasalMetabolismCard
|
||||
<>
|
||||
<TouchableOpacity
|
||||
style={[styles.container, style]}
|
||||
onPress={() => setModalVisible(true)}
|
||||
onPress={() => router.push(ROUTES.BASAL_METABOLISM_DETAIL)}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
{/* 头部区域 */}
|
||||
@@ -179,86 +178,6 @@ export function BasalMetabolismCard({ selectedDate, style }: BasalMetabolismCard
|
||||
<Text style={styles.unit}>千卡/日</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* 基础代谢详情弹窗 */}
|
||||
<Modal
|
||||
animationType="fade"
|
||||
transparent={true}
|
||||
visible={modalVisible}
|
||||
onRequestClose={() => setModalVisible(false)}
|
||||
>
|
||||
<View style={styles.modalOverlay}>
|
||||
<View style={styles.modalContent}>
|
||||
{/* 关闭按钮 */}
|
||||
<TouchableOpacity
|
||||
style={styles.closeButton}
|
||||
onPress={() => setModalVisible(false)}
|
||||
>
|
||||
<Text style={styles.closeButtonText}>×</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* 标题 */}
|
||||
<Text style={styles.modalTitle}>基础代谢</Text>
|
||||
|
||||
{/* 基础代谢定义 */}
|
||||
<Text style={styles.modalDescription}>
|
||||
基础代谢,也称基础代谢率(BMR),是指人体在完全静息状态下维持基本生命功能(心跳、呼吸、体温调节等)所需的最低能量消耗,通常以卡路里为单位。
|
||||
</Text>
|
||||
|
||||
{/* 为什么重要 */}
|
||||
<Text style={styles.sectionTitle}>为什么重要?</Text>
|
||||
<Text style={styles.sectionContent}>
|
||||
基础代谢占总能量消耗的60-75%,是能量平衡的基础。了解您的基础代谢有助于制定科学的营养计划、优化体重管理策略,以及评估代谢健康状态。
|
||||
</Text>
|
||||
|
||||
{/* 正常范围 */}
|
||||
<Text style={styles.sectionTitle}>正常范围</Text>
|
||||
<Text style={styles.formulaText}>
|
||||
- 男性:BMR = 10 × 体重(kg) + 6.25 × 身高(cm) - 5 × 年龄 + 5
|
||||
</Text>
|
||||
<Text style={styles.formulaText}>
|
||||
- 女性:BMR = 10 × 体重(kg) + 6.25 × 身高(cm) - 5 × 年龄 - 161
|
||||
</Text>
|
||||
|
||||
{bmrRange ? (
|
||||
<>
|
||||
<Text style={styles.rangeText}>您的正常区间:{bmrRange.min}-{bmrRange.max}千卡/天</Text>
|
||||
<Text style={styles.rangeNote}>
|
||||
(在公式基础计算值上下浮动15%都属于正常范围)
|
||||
</Text>
|
||||
<Text style={styles.userInfoText}>
|
||||
基于您的信息:{userProfile.gender === 'male' ? '男性' : '女性'},{userAge}岁,{userProfile.height}cm,{userProfile.weight}kg
|
||||
</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Text style={styles.rangeText}>请完善基本信息以计算您的代谢率</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.completeInfoButton}
|
||||
onPress={() => {
|
||||
setModalVisible(false);
|
||||
router.push(ROUTES.PROFILE_EDIT);
|
||||
}}
|
||||
>
|
||||
<Text style={styles.completeInfoButtonText}>前往完善资料</Text>
|
||||
</TouchableOpacity>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 提高代谢率的策略 */}
|
||||
<Text style={styles.sectionTitle}>提高代谢率的策略</Text>
|
||||
<Text style={styles.strategyText}>科学研究支持以下方法:</Text>
|
||||
|
||||
<View style={styles.strategyList}>
|
||||
<Text style={styles.strategyItem}>1.增加肌肉量 (每周2-3次力量训练)</Text>
|
||||
<Text style={styles.strategyItem}>2.高强度间歇训练 (HIIT)</Text>
|
||||
<Text style={styles.strategyItem}>3.充分蛋白质摄入 (体重每公斤1.6-2.2g)</Text>
|
||||
<Text style={styles.strategyItem}>4.保证充足睡眠 (7-9小时/晚)</Text>
|
||||
<Text style={styles.strategyItem}>5.避免过度热量限制 (不低于BMR的80%)</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -352,128 +271,4 @@ const styles = StyleSheet.create({
|
||||
color: '#64748B',
|
||||
marginLeft: 6,
|
||||
},
|
||||
|
||||
// Modal styles
|
||||
modalOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
modalContent: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
padding: 24,
|
||||
maxHeight: '90%',
|
||||
width: '100%',
|
||||
shadowColor: '#000',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: -5,
|
||||
},
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 20,
|
||||
elevation: 10,
|
||||
},
|
||||
closeButton: {
|
||||
position: 'absolute',
|
||||
top: 16,
|
||||
right: 16,
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 16,
|
||||
backgroundColor: '#F1F5F9',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 1,
|
||||
},
|
||||
closeButtonText: {
|
||||
fontSize: 20,
|
||||
color: '#64748B',
|
||||
fontWeight: '600',
|
||||
},
|
||||
modalTitle: {
|
||||
fontSize: 24,
|
||||
fontWeight: '700',
|
||||
color: '#0F172A',
|
||||
marginBottom: 16,
|
||||
textAlign: 'center',
|
||||
},
|
||||
modalDescription: {
|
||||
fontSize: 15,
|
||||
color: '#475569',
|
||||
lineHeight: 22,
|
||||
marginBottom: 24,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '700',
|
||||
color: '#0F172A',
|
||||
marginBottom: 12,
|
||||
marginTop: 8,
|
||||
},
|
||||
sectionContent: {
|
||||
fontSize: 15,
|
||||
color: '#475569',
|
||||
lineHeight: 22,
|
||||
marginBottom: 20,
|
||||
},
|
||||
formulaText: {
|
||||
fontSize: 14,
|
||||
color: '#64748B',
|
||||
fontFamily: 'monospace',
|
||||
marginBottom: 4,
|
||||
paddingLeft: 8,
|
||||
},
|
||||
rangeText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#059669',
|
||||
marginTop: 12,
|
||||
marginBottom: 4,
|
||||
textAlign: 'center',
|
||||
},
|
||||
rangeNote: {
|
||||
fontSize: 12,
|
||||
color: '#9CA3AF',
|
||||
textAlign: 'center',
|
||||
marginBottom: 20,
|
||||
},
|
||||
userInfoText: {
|
||||
fontSize: 13,
|
||||
color: '#6B7280',
|
||||
textAlign: 'center',
|
||||
marginTop: 8,
|
||||
marginBottom: 16,
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
strategyText: {
|
||||
fontSize: 15,
|
||||
color: '#475569',
|
||||
marginBottom: 12,
|
||||
},
|
||||
strategyList: {
|
||||
marginBottom: 20,
|
||||
},
|
||||
strategyItem: {
|
||||
fontSize: 14,
|
||||
color: '#64748B',
|
||||
lineHeight: 20,
|
||||
marginBottom: 8,
|
||||
paddingLeft: 8,
|
||||
},
|
||||
completeInfoButton: {
|
||||
backgroundColor: '#7a5af8',
|
||||
borderRadius: 12,
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 24,
|
||||
marginTop: 16,
|
||||
alignItems: 'center',
|
||||
alignSelf: 'center',
|
||||
},
|
||||
completeInfoButtonText: {
|
||||
color: '#FFFFFF',
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fetchHRVForDate } from '@/utils/health';
|
||||
import { fetchHRVWithStatus } from '@/utils/health';
|
||||
import { Image } from 'expo-image';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
@@ -34,13 +34,24 @@ export function StressMeter({ curDate }: StressMeterProps) {
|
||||
|
||||
const getHrvData = async () => {
|
||||
try {
|
||||
const data = await fetchHRVForDate(curDate)
|
||||
console.log('StressMeter: 开始获取HRV数据...', curDate);
|
||||
|
||||
if (data) {
|
||||
setHrvValue(Math.round(data.value))
|
||||
// 使用智能HRV数据获取功能
|
||||
const result = await fetchHRVWithStatus(curDate);
|
||||
|
||||
console.log('StressMeter: HRV数据获取结果:', result);
|
||||
|
||||
if (result.hrvData) {
|
||||
setHrvValue(Math.round(result.hrvData.value));
|
||||
console.log(`StressMeter: 使用${result.message},HRV值: ${result.hrvData.value}ms`);
|
||||
} else {
|
||||
console.log('StressMeter: 未获取到HRV数据');
|
||||
// 可以设置一个默认值或者显示无数据状态
|
||||
setHrvValue(0);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
console.error('StressMeter: 获取HRV数据失败:', error);
|
||||
setHrvValue(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { useWaterDataByDate } from '@/hooks/useWaterData';
|
||||
import { getQuickWaterAmount } from '@/utils/userPreferences';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
@@ -28,7 +27,6 @@ const WaterIntakeCard: React.FC<WaterIntakeCardProps> = ({
|
||||
selectedDate
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { ensureLoggedIn } = useAuthGuard();
|
||||
const { waterStats, dailyWaterGoal, waterRecords, addWaterRecord } = useWaterDataByDate(selectedDate);
|
||||
const [quickWaterAmount, setQuickWaterAmount] = useState(150); // 默认值,将从用户偏好中加载
|
||||
|
||||
@@ -123,12 +121,6 @@ const WaterIntakeCard: React.FC<WaterIntakeCardProps> = ({
|
||||
|
||||
// 处理添加喝水 - 右上角按钮直接添加
|
||||
const handleQuickAddWater = async () => {
|
||||
// 检查用户是否已登录
|
||||
const isLoggedIn = await ensureLoggedIn();
|
||||
if (!isLoggedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 触发震动反馈
|
||||
if (process.env.EXPO_OS === 'ios') {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
|
||||
@@ -145,12 +137,6 @@ const WaterIntakeCard: React.FC<WaterIntakeCardProps> = ({
|
||||
|
||||
// 处理卡片点击 - 跳转到饮水设置页面
|
||||
const handleCardPress = async () => {
|
||||
// 检查用户是否已登录
|
||||
const isLoggedIn = await ensureLoggedIn();
|
||||
if (!isLoggedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 触发震动反馈
|
||||
if (process.env.EXPO_OS === 'ios') {
|
||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
||||
|
||||
Reference in New Issue
Block a user