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

@@ -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',
},
});

View File

@@ -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);
}
}

View File

@@ -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);