From 1c44c3083b6cc164007c008628c8a0b22d00cf9f Mon Sep 17 00:00:00 2001 From: richarjiang Date: Wed, 20 Aug 2025 17:25:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=92=8CBMI=E5=8D=A1=E7=89=87=EF=BC=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4=E5=8E=8B=E5=8A=9B=E5=88=86=E6=9E=90=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在统计页面中移除压力分析模态框及相关状态管理,简化组件逻辑 - 更新BMI卡片,改进弹窗展示方式,增加渐变背景和健康建议 - 新增更新体重的功能,支持将体重数据写入健康数据中 - 优化压力计组件,调整数据展示逻辑,提升用户体验 --- app/(tabs)/statistics.tsx | 23 +-- components/BMICard.tsx | 337 +++++++++++++++++++++++++++---------- components/StressMeter.tsx | 108 +++++++----- utils/health.ts | 23 ++- 4 files changed, 334 insertions(+), 157 deletions(-) diff --git a/app/(tabs)/statistics.tsx b/app/(tabs)/statistics.tsx index 0b8f93d..b38e6d6 100644 --- a/app/(tabs)/statistics.tsx +++ b/app/(tabs)/statistics.tsx @@ -3,7 +3,6 @@ import { BMICard } from '@/components/BMICard'; import { CircularRing } from '@/components/CircularRing'; import { NutritionRadarCard } from '@/components/NutritionRadarCard'; import { ProgressBar } from '@/components/ProgressBar'; -import { StressAnalysisModal } from '@/components/StressAnalysisModal'; import { StressMeter } from '@/components/StressMeter'; import { WeightHistoryCard } from '@/components/WeightHistoryCard'; import { Colors } from '@/constants/Colors'; @@ -87,9 +86,6 @@ export default function ExploreScreen() { const [nutritionSummary, setNutritionSummary] = useState(null); const [isNutritionLoading, setIsNutritionLoading] = useState(false); - // 压力分析浮窗状态 - const [showStressModal, setShowStressModal] = useState(false); - // 记录最近一次请求的“日期键”,避免旧请求覆盖新结果 const latestRequestKeyRef = useRef(null); @@ -209,15 +205,6 @@ export default function ExploreScreen() { } }; - // 处理压力卡片点击 - const handleStressCardPress = () => { - setShowStressModal(true); - }; - - // 关闭压力分析浮窗 - const handleCloseStressModal = () => { - setShowStressModal(false); - }; // 使用统一的渐变背景色 const backgroundGradientColors = [colorTokens.backgroundGradientStart, colorTokens.backgroundGradientEnd] as const; @@ -280,7 +267,7 @@ export default function ExploreScreen() { value={hrvValue} updateTime={hrvUpdateTime} style={styles.masonryCard} - onPress={handleStressCardPress} + hrvValue={hrvValue} /> @@ -371,14 +358,6 @@ export default function ExploreScreen() { - - {/* 压力分析浮窗 */} - ); } diff --git a/components/BMICard.tsx b/components/BMICard.tsx index 37aec24..8c60cd7 100644 --- a/components/BMICard.tsx +++ b/components/BMICard.tsx @@ -1,5 +1,6 @@ import { Colors } from '@/constants/Colors'; import { useAuthGuard } from '@/hooks/useAuthGuard'; +import { useColorScheme } from '@/hooks/useColorScheme'; import { BMI_CATEGORIES, canCalculateBMI, @@ -7,11 +8,12 @@ import { type BMIResult } from '@/utils/bmi'; import { Ionicons } from '@expo/vector-icons'; +import { LinearGradient } from 'expo-linear-gradient'; import React, { useState } from 'react'; import { Dimensions, Modal, - Pressable, + ScrollView, StyleSheet, Text, TouchableOpacity, @@ -177,6 +179,9 @@ export function BMICard({ weight, height, style, compact = false }: BMICardProps ); }; + const colorScheme = useColorScheme(); + const themeColors = Colors[colorScheme ?? 'light']; + return ( <> @@ -186,99 +191,102 @@ export function BMICard({ weight, height, style, compact = false }: BMICardProps {/* BMI 信息弹窗 */} - - e.stopPropagation()} - > - {/* 弹窗头部 */} - - - - - - BMI 指数说明 - - - - - + + {/* 标题 */} + BMI 指数说明 - {/* 内容区域 - 去除滚动,精简设计 */} - - {/* 介绍部分 */} - - - BMI 是评估体重与身高关系的健康指标 - - - - 计算公式:体重(kg) ÷ 身高²(m) - - - - - {/* BMI 分类标准 - 紧凑设计 */} - - 分类标准 - - {BMI_CATEGORIES.map((category, index) => { - const colors = index === 0 ? { bg: '#FEF3C7', text: '#B45309' } : - index === 1 ? { bg: '#E8F5E8', text: Colors.light.accentGreenDark } : - index === 2 ? { bg: '#FEF3C7', text: '#B45309' } : - { bg: '#FEE2E2', text: '#B91C1C' }; - - return ( - - - - {category.name} - - - {category.range} - - - - {category.advice} - - - ); - })} - - - - {/* 健康提示 - 简化版 */} - - - - 健康建议 - - - 保持均衡饮食、规律运动、充足睡眠,定期监测体重变化 - - - - {/* 免责声明 - 精简版 */} - - - - BMI 仅供参考,如有疑问请咨询专业医生 + {/* 介绍部分 */} + + + BMI(身体质量指数)是评估体重与身高关系的国际通用健康指标 + + + + 计算公式:体重(kg) ÷ 身高²(m) - - + + {/* BMI 分类标准 */} + BMI 分类标准 + + + {BMI_CATEGORIES.map((category, index) => { + const colors = [ + { bg: '#FEF3C7', text: '#B45309', border: '#F59E0B' }, // 偏瘦 + { bg: '#E8F5E8', text: Colors.light.accentGreenDark, border: Colors.light.accentGreen }, // 正常 + { bg: '#FEF3C7', text: '#B45309', border: '#F59E0B' }, // 超重 + { bg: '#FEE2E2', text: '#B91C1C', border: '#EF4444' } // 肥胖 + ][index]; + + return ( + + + + {category.name} + + + {category.range} + + + + {category.advice} + + + ); + })} + + + {/* 健康建议 */} + 健康建议 + + + + 保持均衡饮食,控制热量摄入 + + + + 每周至少150分钟中等强度运动 + + + + 保证7-9小时充足睡眠 + + + + 定期监测体重变化,及时调整 + + + + {/* 免责声明 */} + + + + BMI 仅供参考,不能反映肌肉量、骨密度等指标。如有健康疑问,请咨询专业医生。 + + + + + {/* 底部继续按钮 */} + + + + 继续 + + + + + ); @@ -614,4 +622,159 @@ const styles = StyleSheet.create({ flex: 1, }, + // 新样式 - 与StressAnalysisModal保持一致 + newModalContainer: { + flex: 1, + }, + newContent: { + flex: 1, + paddingHorizontal: 20, + }, + newTitle: { + fontSize: 24, + fontWeight: '800', + color: '#111827', + textAlign: 'center', + marginTop: 20, + marginBottom: 32, + }, + newIntroSection: { + marginBottom: 32, + }, + newDescription: { + fontSize: 16, + color: '#374151', + lineHeight: 24, + textAlign: 'center', + marginBottom: 16, + }, + newFormulaContainer: { + backgroundColor: '#F8FAFC', + borderRadius: 12, + padding: 16, + borderLeftWidth: 4, + borderLeftColor: '#3B82F6', + }, + newFormulaText: { + fontSize: 15, + color: '#1F2937', + fontWeight: '600', + textAlign: 'center', + }, + newSectionTitle: { + fontSize: 22, + fontWeight: '800', + color: '#111827', + marginBottom: 20, + }, + newStatsCard: { + backgroundColor: '#FFFFFF', + borderRadius: 16, + padding: 20, + marginBottom: 32, + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.05, + shadowRadius: 8, + elevation: 2, + }, + newStatItem: { + backgroundColor: '#FFFFFF', + borderRadius: 12, + padding: 16, + marginBottom: 12, + borderLeftWidth: 4, + }, + newStatHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 8, + }, + newStatTitle: { + fontSize: 16, + fontWeight: '700', + }, + newStatRange: { + fontSize: 14, + fontWeight: '600', + opacity: 0.8, + }, + newStatAdvice: { + fontSize: 14, + lineHeight: 20, + fontWeight: '500', + opacity: 0.9, + }, + newHealthTips: { + backgroundColor: '#FFFFFF', + borderRadius: 16, + padding: 20, + marginBottom: 32, + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.05, + shadowRadius: 8, + elevation: 2, + }, + newTipsItem: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 12, + }, + newTipsText: { + fontSize: 15, + color: '#374151', + marginLeft: 12, + flex: 1, + }, + newDisclaimer: { + flexDirection: 'row', + alignItems: 'flex-start', + backgroundColor: '#F9FAFB', + borderRadius: 12, + padding: 16, + marginBottom: 32, + }, + newDisclaimerText: { + fontSize: 13, + color: '#6B7280', + lineHeight: 18, + marginLeft: 8, + flex: 1, + }, + newBottomContainer: { + paddingHorizontal: 20, + paddingBottom: 34, + }, + newContinueButton: { + borderRadius: 25, + overflow: 'hidden', + marginBottom: 8, + }, + newButtonBackground: { + backgroundColor: Colors.light.accentGreen, + paddingVertical: 18, + alignItems: 'center', + justifyContent: 'center', + }, + newButtonText: { + fontSize: 18, + fontWeight: '700', + color: '#192126', + }, + newHomeIndicator: { + width: 134, + height: 5, + backgroundColor: '#000', + borderRadius: 3, + alignSelf: 'center', + }, + }); diff --git a/components/StressMeter.tsx b/components/StressMeter.tsx index 5c8cbed..f3b83f2 100644 --- a/components/StressMeter.tsx +++ b/components/StressMeter.tsx @@ -1,27 +1,27 @@ import { Ionicons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; -import React from 'react'; +import React, { useState } from 'react'; import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { StressAnalysisModal } from './StressAnalysisModal'; interface StressMeterProps { value: number | null; updateTime?: Date; style?: any; - onPress?: () => void; + hrvValue: number; } -export function StressMeter({ value, updateTime, style, onPress }: StressMeterProps) { +export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterProps) { // 将HRV值转换为压力指数(0-100) // HRV值范围:30-110ms,映射到压力指数100-0 // HRV值越高,压力越小;HRV值越低,压力越大 - const stressIndex = value ? Math.round(Math.min(100, Math.max(0, 100 - ((value - 30) / 80) * 100))) : null; // 根据压力指数计算状态 const getStressStatus = () => { - if (stressIndex === null) { + if (value === null) { return '未知'; - } else if (stressIndex <= 30) { + } else if (value <= 30) { return '放松'; - } else if (stressIndex <= 70) { + } else if (value <= 70) { return '正常'; } else { return '紧张'; @@ -47,55 +47,71 @@ export function StressMeter({ value, updateTime, style, onPress }: StressMeterPr // 计算进度条位置(0-100%) // 压力指数越高,进度条越满 - const progressPercentage = stressIndex === null ? 0 : stressIndex; + const progressPercentage = value === null ? 0 : value; + // 在组件内部添加状态 + const [showStressModal, setShowStressModal] = useState(false); + + // 修改 onPress 处理函数 + const handlePress = () => { + setShowStressModal(true); + }; return ( - - - {/* 头部区域 */} - - - - + <> + + {/* 头部区域 */} + + + + + + 压力 - 压力 + {getStatusEmoji()} - {getStatusEmoji()} - - {/* 数值显示区域 */} - - {stressIndex === null ? '--' : stressIndex} - 指数 - + {/* 数值显示区域 */} + + {value === null ? '--' : value} + 指数 + - {/* 进度条区域 */} - - - {/* 渐变背景进度条 */} - - + {/* 进度条区域 */} + + + {/* 渐变背景进度条 */} + + + + {/* 白色圆形指示器 */} + - {/* 白色圆形指示器 */} - - - {/* 更新时间 - {updateTime && ( - {formatUpdateTime(updateTime)} - )} */} - + {/* 更新时间 + {updateTime && ( + {formatUpdateTime(updateTime)} + )} */} + + + {/* 压力分析浮窗 */} + setShowStressModal(false)} + hrvValue={hrvValue} + updateTime={updateTime || new Date()} + /> + ); } diff --git a/utils/health.ts b/utils/health.ts index 37555a5..8b3c8dd 100644 --- a/utils/health.ts +++ b/utils/health.ts @@ -10,7 +10,10 @@ const PERMISSIONS: HealthKitPermissions = { AppleHealthKit.Constants.Permissions.SleepAnalysis, AppleHealthKit.Constants.Permissions.HeartRateVariability, ], - write: [], + write: [ + // 支持体重写入 + AppleHealthKit.Constants.Permissions.Weight, + ], }, }; @@ -192,4 +195,20 @@ export async function fetchHRVForDate(date: Date): Promise { // 新增:获取今日HRV数据 export async function fetchTodayHRV(): Promise { return fetchHRVForDate(new Date()); -} \ No newline at end of file +} + +// 更新healthkit中的体重 +export async function updateWeight(weight: number) { + return new Promise((resolve) => { + AppleHealthKit.saveWeight({ + value: weight, + }, (err, res) => { + if (err) { + console.error('更新体重失败:', err); + return resolve(false); + } + console.log('体重更新成功:', res); + resolve(true); + }); + }); +}