feat: 优化健康数据相关组件及功能

- 在 CoachScreen 中调整键盘高度计算,移除不必要的 insets.bottom
- 更新 Statistics 组件,移除未使用的健康数据相关函数,简化代码
- 修改多个统计卡片,移除不必要的图标属性,提升组件简洁性
- 优化 HealthDataCard 和其他统计卡片的样式,提升视觉一致性
- 更新健康数据获取逻辑,确保数据处理更为准确
- 移除 MoodCard 中的多余元素,简化心情记录展示
- 调整 StressMeter 和其他组件的样式,提升用户体验
This commit is contained in:
richarjiang
2025-08-25 12:44:40 +08:00
parent ee84a801fb
commit be0a8e7393
10 changed files with 83 additions and 197 deletions

View File

@@ -419,7 +419,7 @@ export default function CoachScreen() {
showSub = Keyboard.addListener('keyboardWillChangeFrame', (e: any) => { showSub = Keyboard.addListener('keyboardWillChangeFrame', (e: any) => {
try { try {
if (e?.endCoordinates?.height) { if (e?.endCoordinates?.height) {
const height = Math.max(0, e.endCoordinates.height - insets.bottom); const height = Math.max(0, e.endCoordinates.height);
setKeyboardOffset(height); setKeyboardOffset(height);
} }
} catch (error) { } catch (error) {

View File

@@ -18,7 +18,7 @@ import { useColorScheme } from '@/hooks/useColorScheme';
import { calculateNutritionSummary, getDietRecords, NutritionSummary } from '@/services/dietRecords'; import { calculateNutritionSummary, getDietRecords, NutritionSummary } from '@/services/dietRecords';
import { fetchDailyMoodCheckins, selectLatestMoodRecordByDate } from '@/store/moodSlice'; import { fetchDailyMoodCheckins, selectLatestMoodRecordByDate } from '@/store/moodSlice';
import { getMonthDaysZh, getTodayIndexInMonth } from '@/utils/date'; import { getMonthDaysZh, getTodayIndexInMonth } from '@/utils/date';
import { ensureHealthPermissions, fetchHealthDataForDate, testOxygenSaturationData } from '@/utils/health'; import { ensureHealthPermissions, fetchHealthDataForDate } from '@/utils/health';
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { useFocusEffect } from '@react-navigation/native'; import { useFocusEffect } from '@react-navigation/native';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -124,14 +124,12 @@ export default function ExploreScreen() {
const [oxygenSaturation, setOxygenSaturation] = useState<number | null>(null); const [oxygenSaturation, setOxygenSaturation] = useState<number | null>(null);
const [heartRate, setHeartRate] = useState<number | null>(null); const [heartRate, setHeartRate] = useState<number | null>(null);
const [isLoading, setIsLoading] = useState(false);
// 用于触发动画重置的 token当日期或数据变化时更新 // 用于触发动画重置的 token当日期或数据变化时更新
const [animToken, setAnimToken] = useState(0); const [animToken, setAnimToken] = useState(0);
const [trainingProgress, setTrainingProgress] = useState(0); // 暂定静态80%
// 营养数据状态 // 营养数据状态
const [nutritionSummary, setNutritionSummary] = useState<NutritionSummary | null>(null); const [nutritionSummary, setNutritionSummary] = useState<NutritionSummary | null>(null);
const [isNutritionLoading, setIsNutritionLoading] = useState(false);
const { registerTask } = useBackgroundTasks(); const { registerTask } = useBackgroundTasks();
// 心情相关状态 // 心情相关状态
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@@ -182,7 +180,6 @@ export default function ExploreScreen() {
const loadHealthData = async (targetDate?: Date) => { const loadHealthData = async (targetDate?: Date) => {
try { try {
console.log('=== 开始HealthKit初始化流程 ==='); console.log('=== 开始HealthKit初始化流程 ===');
setIsLoading(true);
const ok = await ensureHealthPermissions(); const ok = await ensureHealthPermissions();
if (!ok) { if (!ok) {
@@ -246,15 +243,12 @@ export default function ExploreScreen() {
// 重置血氧饱和度和心率数据 // 重置血氧饱和度和心率数据
setOxygenSaturation(null); setOxygenSaturation(null);
setHeartRate(null); setHeartRate(null);
} finally {
setIsLoading(false);
} }
}; };
// 加载营养数据 // 加载营养数据
const loadNutritionData = async (targetDate?: Date) => { const loadNutritionData = async (targetDate?: Date) => {
try { try {
setIsNutritionLoading(true);
// 确定要查询的日期:优先使用传入的日期,否则使用当前选中索引对应的日期 // 确定要查询的日期:优先使用传入的日期,否则使用当前选中索引对应的日期
let derivedDate: Date; let derivedDate: Date;
@@ -281,8 +275,6 @@ export default function ExploreScreen() {
} catch (error) { } catch (error) {
console.error('营养数据加载失败:', error); console.error('营养数据加载失败:', error);
setNutritionSummary(null); setNutritionSummary(null);
} finally {
setIsNutritionLoading(false);
} }
}; };
@@ -300,20 +292,20 @@ export default function ExploreScreen() {
}, [selectedIndex]) }, [selectedIndex])
); );
// useEffect(() => { useEffect(() => {
// // 注册任务 // 注册任务
// registerTask({ registerTask({
// id: 'health-data-task', id: 'health-data-task',
// name: 'health-data-task', name: 'health-data-task',
// handler: async () => { handler: async () => {
// try { try {
// await loadHealthData(); await loadHealthData();
// } catch (error) { } catch (error) {
// console.error('健康数据任务执行失败:', error); console.error('健康数据任务执行失败:', error);
// } }
// }, },
// }); });
// }, []); }, []);
// 日期点击时,加载对应日期数据 // 日期点击时,加载对应日期数据
const onSelectDate = (index: number, date: Date) => { const onSelectDate = (index: number, date: Date) => {
@@ -325,16 +317,6 @@ export default function ExploreScreen() {
} }
}; };
// 测试血氧饱和度数据
const testOxygenData = async () => {
console.log('开始测试血氧饱和度数据...');
const currentDate = getCurrentSelectedDate();
await testOxygenSaturationData(currentDate);
};
// 使用统一的渐变背景色
const backgroundGradientColors = [colorTokens.backgroundGradientStart, colorTokens.backgroundGradientEnd] as const;
return ( return (
<View style={styles.container}> <View style={styles.container}>
{/* 背景渐变 */} {/* 背景渐变 */}
@@ -384,8 +366,8 @@ export default function ExploreScreen() {
/> />
</FloatingCard> </FloatingCard>
<FloatingCard style={[styles.masonryCard, styles.caloriesCard]} delay={500}> <FloatingCard style={styles.masonryCard} delay={500}>
<Text style={styles.cardTitleSecondary}></Text> <Text style={styles.cardTitle}></Text>
{activeCalories != null ? ( {activeCalories != null ? (
<AnimatedNumber <AnimatedNumber
value={activeCalories} value={activeCalories}
@@ -398,7 +380,7 @@ export default function ExploreScreen() {
)} )}
</FloatingCard> </FloatingCard>
<FloatingCard style={[styles.masonryCard, styles.stepsCard]} delay={1000}> <FloatingCard style={styles.masonryCard} delay={1000}>
<View style={styles.cardHeaderRow}> <View style={styles.cardHeaderRow}>
<Text style={styles.cardTitle}></Text> <Text style={styles.cardTitle}></Text>
</View> </View>
@@ -414,14 +396,14 @@ export default function ExploreScreen() {
)} )}
<ProgressBar <ProgressBar
progress={Math.min(1, Math.max(0, (stepCount ?? 0) / stepGoal))} progress={Math.min(1, Math.max(0, (stepCount ?? 0) / stepGoal))}
height={16} height={14}
trackColor="#FFEBCB" trackColor="#FFEBCB"
fillColor="#FFC365" fillColor="#FFC365"
showLabel={false} showLabel={false}
/> />
</FloatingCard> </FloatingCard>
{/* 心情卡片 */} {/* 心情卡片 */}
<FloatingCard style={[styles.masonryCard, styles.moodCard]} delay={1500}> <FloatingCard style={styles.masonryCard} delay={1500}>
<MoodCard <MoodCard
moodCheckin={currentMoodCheckin} moodCheckin={currentMoodCheckin}
onPress={() => pushIfAuthedElseLogin('/mood/calendar')} onPress={() => pushIfAuthedElseLogin('/mood/calendar')}
@@ -444,7 +426,7 @@ export default function ExploreScreen() {
/> />
</FloatingCard> </FloatingCard>
<FloatingCard style={[styles.masonryCard, styles.sleepCard]} delay={750}> <FloatingCard style={styles.masonryCard} delay={750}>
<View style={styles.cardHeaderRow}> <View style={styles.cardHeaderRow}>
<Text style={styles.cardTitle}></Text> <Text style={styles.cardTitle}></Text>
</View> </View>
@@ -473,13 +455,6 @@ export default function ExploreScreen() {
style={styles.basalMetabolismCardOverride} style={styles.basalMetabolismCardOverride}
oxygenSaturation={oxygenSaturation} oxygenSaturation={oxygenSaturation}
/> />
{/* 测试按钮 - 开发时使用 */}
<Text
style={styles.testButton}
onPress={testOxygenData}
>
</Text>
</FloatingCard> </FloatingCard>
{/* 心率卡片 */} {/* 心率卡片 */}
@@ -577,22 +552,11 @@ const styles = StyleSheet.create({
borderRadius: 22, borderRadius: 22,
padding: 16, padding: 16,
}, },
caloriesCard: {
backgroundColor: '#FFFFFF',
},
trainingCard: {
backgroundColor: '#EEE9FF',
},
cardTitleSecondary: {
color: '#9AA3AE',
fontSize: 10,
fontWeight: '600',
marginBottom: 10,
},
caloriesValue: { caloriesValue: {
color: '#192126', color: '#192126',
fontSize: 18, fontSize: 18,
fontWeight: '800', fontWeight: '800',
marginTop: 18,
}, },
trainingContent: { trainingContent: {
marginTop: 8, marginTop: 8,
@@ -706,9 +670,6 @@ const styles = StyleSheet.create({
color: '#5B5B5B', color: '#5B5B5B',
fontWeight: '600', fontWeight: '600',
}, },
stepsCard: {
backgroundColor: '#FFE4B8',
},
stepsValue: { stepsValue: {
fontSize: 14, fontSize: 14,
color: '#7A6A42', color: '#7A6A42',
@@ -797,9 +758,6 @@ const styles = StyleSheet.create({
justifyContent: 'space-between', justifyContent: 'space-between',
marginTop: 8, marginTop: 8,
}, },
sleepCard: {
backgroundColor: '#E8F4FD',
},
sleepValue: { sleepValue: {
fontSize: 16, fontSize: 16,
color: '#1E40AF', color: '#1E40AF',
@@ -821,14 +779,5 @@ const styles = StyleSheet.create({
top: 0, top: 0,
padding: 4, padding: 4,
}, },
moodCard: {
backgroundColor: '#F0FDF4',
},
testButton: {
fontSize: 12,
color: '#3B82F6',
textAlign: 'center',
marginTop: 8,
padding: 4,
},
}); });

View File

@@ -1,5 +1,4 @@
import { AnimatedNumber } from '@/components/AnimatedNumber'; import { AnimatedNumber } from '@/components/AnimatedNumber';
import { LinearGradient } from 'expo-linear-gradient';
import React from 'react'; import React from 'react';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
@@ -32,17 +31,6 @@ export function BasalMetabolismCard({ value, resetToken, style }: BasalMetabolis
return ( return (
<View style={[styles.container, style]}> <View style={[styles.container, style]}>
{/* 渐变背景 */}
<LinearGradient
colors={['#F0F9FF', '#E0F2FE']}
style={styles.gradientBackground}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
/>
{/* 装饰性圆圈 */}
<View style={styles.decorativeCircle1} />
<View style={styles.decorativeCircle2} />
{/* 头部区域 */} {/* 头部区域 */}
<View style={styles.header}> <View style={styles.header}>
@@ -88,34 +76,7 @@ const styles = StyleSheet.create({
position: 'relative', position: 'relative',
overflow: 'hidden', overflow: 'hidden',
}, },
gradientBackground: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
opacity: 0.6,
},
decorativeCircle1: {
position: 'absolute',
top: -20,
right: -20,
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: '#0EA5E9',
opacity: 0.1,
},
decorativeCircle2: {
position: 'absolute',
bottom: -15,
left: -15,
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: '#0EA5E9',
opacity: 0.05,
},
header: { header: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',

View File

@@ -67,20 +67,39 @@ export const DateSelector: React.FC<DateSelectorProps> = ({
const maxScrollOffset = Math.max(0, (days.length * itemWidth) - scrollWidth); const maxScrollOffset = Math.max(0, (days.length * itemWidth) - scrollWidth);
const finalOffset = Math.min(centerOffset, maxScrollOffset); const finalOffset = Math.min(centerOffset, maxScrollOffset);
daysScrollRef.current.scrollTo({ x: finalOffset, animated }); if (animated) {
// 使用原生动画API实现更平滑的滚动
requestAnimationFrame(() => {
daysScrollRef.current?.scrollTo({
x: finalOffset,
animated: true
});
});
} else {
// 非动画情况直接跳转
daysScrollRef.current?.scrollTo({
x: finalOffset,
animated: false
});
}
}; };
// 初始化时滚动到选中项 // 初始化时滚动到选中项
useEffect(() => { useEffect(() => {
if (scrollWidth > 0 && autoScrollToSelected) { if (scrollWidth > 0 && autoScrollToSelected) {
scrollToIndex(selectedIndex, false); scrollToIndex(selectedIndex, true);
} }
}, [scrollWidth, selectedIndex, autoScrollToSelected]); }, [scrollWidth, selectedIndex, autoScrollToSelected]);
// 当选中索引变化时,滚动到对应位置 // 当选中索引变化时,滚动到对应位置
useEffect(() => { useEffect(() => {
if (scrollWidth > 0 && autoScrollToSelected) { if (scrollWidth > 0 && autoScrollToSelected) {
scrollToIndex(selectedIndex, true); // 添加微小延迟以确保动画效果更明显
const timer = setTimeout(() => {
scrollToIndex(selectedIndex, true);
}, 50);
return () => clearTimeout(timer);
} }
}, [selectedIndex, autoScrollToSelected]); }, [selectedIndex, autoScrollToSelected]);
@@ -94,6 +113,11 @@ export const DateSelector: React.FC<DateSelectorProps> = ({
return; return;
} }
// 先滚动到目标位置,再更新状态
if (autoScrollToSelected) {
scrollToIndex(index, true);
}
// 更新内部状态(如果使用外部控制则不更新) // 更新内部状态(如果使用外部控制则不更新)
if (externalSelectedIndex === undefined) { if (externalSelectedIndex === undefined) {
setInternalSelectedIndex(index); setInternalSelectedIndex(index);

View File

@@ -14,20 +14,8 @@ export function MoodCard({ moodCheckin, onPress, isLoading = false }: MoodCardPr
return ( return (
<TouchableOpacity onPress={onPress} style={styles.moodCardContent} disabled={isLoading}> <TouchableOpacity onPress={onPress} style={styles.moodCardContent} disabled={isLoading}>
<View style={styles.cardHeaderRow}> <Text style={styles.cardTitle}></Text>
<View style={styles.moodIconContainer}>
{moodCheckin ? (
<Text style={styles.moodIcon}>
{moodConfig?.emoji || '😊'}
</Text>
) : (
<Text style={styles.moodIcon}>😊</Text>
)}
</View>
<Text style={styles.cardTitle}></Text>
</View>
<Text style={styles.moodSubtitle}></Text>
{isLoading ? ( {isLoading ? (
<View style={styles.moodPreview}> <View style={styles.moodPreview}>
@@ -53,39 +41,18 @@ const styles = StyleSheet.create({
moodCardContent: { moodCardContent: {
width: '100%', width: '100%',
}, },
cardHeaderRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
moodIconContainer: {
width: 24,
height: 24,
borderRadius: 8,
backgroundColor: '#DCFCE7',
alignItems: 'center',
justifyContent: 'center',
marginRight: 10,
},
moodIcon: {
fontSize: 14,
},
cardTitle: { cardTitle: {
fontSize: 14, fontSize: 14,
fontWeight: '800', fontWeight: '800',
color: '#192126', color: '#192126',
}, },
moodSubtitle: {
fontSize: 12,
color: '#6B7280',
marginTop: 4,
marginBottom: 8,
},
moodPreview: { moodPreview: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
marginTop: 4, marginTop: 22,
}, },
moodPreviewText: { moodPreviewText: {
fontSize: 14, fontSize: 14,
@@ -100,12 +67,12 @@ const styles = StyleSheet.create({
fontSize: 12, fontSize: 12,
color: '#9CA3AF', color: '#9CA3AF',
fontStyle: 'italic', fontStyle: 'italic',
marginTop: 4, marginTop: 22,
}, },
moodLoadingText: { moodLoadingText: {
fontSize: 12, fontSize: 12,
color: '#9CA3AF', color: '#9CA3AF',
fontStyle: 'italic', fontStyle: 'italic',
marginTop: 4, marginTop: 22,
}, },
}); });

View File

@@ -68,7 +68,6 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
<View style={styles.leftSection}> <View style={styles.leftSection}>
<Text style={styles.title}></Text> <Text style={styles.title}></Text>
</View> </View>
<Text style={styles.emoji}>{getStatusEmoji()}</Text>
</View> </View>
{/* 数值显示区域 */} {/* 数值显示区域 */}
@@ -83,7 +82,7 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
{/* 渐变背景进度条 */} {/* 渐变背景进度条 */}
<View style={[styles.progressBar, { width: '100%' }]}> <View style={[styles.progressBar, { width: '100%' }]}>
<LinearGradient <LinearGradient
colors={['#EF4444', '#FCD34D', '#10B981']} colors={['#EF4444', '#FCD34D', '#10B981']}
start={{ x: 0, y: 0 }} start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }} end={{ x: 1, y: 0 }}
style={styles.gradientBar} style={styles.gradientBar}
@@ -113,8 +112,6 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
backgroundColor: '#FFFFFF',
padding: 2,
shadowColor: '#000', shadowColor: '#000',
shadowOffset: { shadowOffset: {
width: 0, width: 0,
@@ -147,19 +144,16 @@ const styles = StyleSheet.create({
}, },
title: { title: {
fontSize: 14, fontSize: 14,
fontWeight: '700', fontWeight: '800',
color: '#192126', color: '#192126',
}, },
emoji: {
fontSize: 16,
},
valueSection: { valueSection: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'baseline', alignItems: 'baseline',
marginBottom: 12, marginBottom: 12,
}, },
value: { value: {
fontSize: 28, fontSize: 24,
fontWeight: '800', fontWeight: '800',
color: '#192126', color: '#192126',
lineHeight: 32, lineHeight: 32,
@@ -172,7 +166,6 @@ const styles = StyleSheet.create({
}, },
progressContainer: { progressContainer: {
height: 16, height: 16,
marginBottom: 4,
}, },
progressTrack: { progressTrack: {
height: 8, height: 8,

View File

@@ -6,7 +6,6 @@ interface HealthDataCardProps {
title: string; title: string;
value: string; value: string;
unit: string; unit: string;
icon: React.ReactNode;
style?: object; style?: object;
} }
@@ -14,7 +13,6 @@ const HealthDataCard: React.FC<HealthDataCardProps> = ({
title, title,
value, value,
unit, unit,
icon,
style style
}) => { }) => {
return ( return (
@@ -23,10 +21,6 @@ const HealthDataCard: React.FC<HealthDataCardProps> = ({
exiting={FadeOut.duration(300)} exiting={FadeOut.duration(300)}
style={[styles.card, style]} style={[styles.card, style]}
> >
<View style={styles.iconContainer}>
{icon}
</View>
<View style={styles.content}> <View style={styles.content}>
<Text style={styles.title}>{title}</Text> <Text style={styles.title}>{title}</Text>
<View style={styles.valueContainer}> <View style={styles.valueContainer}>
@@ -65,9 +59,9 @@ const styles = StyleSheet.create({
}, },
title: { title: {
fontSize: 14, fontSize: 14,
color: '#666', color: '#192126',
marginBottom: 4, marginBottom: 4,
fontWeight: '600', fontWeight: '800',
}, },
valueContainer: { valueContainer: {
flexDirection: 'row', flexDirection: 'row',

View File

@@ -23,7 +23,6 @@ const HeartRateCard: React.FC<HeartRateCardProps> = ({
title="心率" title="心率"
value={heartRate !== null && heartRate !== undefined ? Math.round(heartRate).toString() : '--'} value={heartRate !== null && heartRate !== undefined ? Math.round(heartRate).toString() : '--'}
unit="bpm" unit="bpm"
icon={heartIcon}
style={style} style={style}
/> />
); );

View File

@@ -23,7 +23,6 @@ const OxygenSaturationCard: React.FC<OxygenSaturationCardProps> = ({
title="血氧饱和度" title="血氧饱和度"
value={oxygenSaturation !== null && oxygenSaturation !== undefined ? oxygenSaturation.toFixed(1) : '--'} value={oxygenSaturation !== null && oxygenSaturation !== undefined ? oxygenSaturation.toFixed(1) : '--'}
unit="%" unit="%"
icon={oxygenIcon}
style={style} style={style}
/> />
); );

View File

@@ -221,13 +221,13 @@ export async function fetchHealthDataForDate(date: Date): Promise<TodayHealthDat
const latestOxygen = res[res.length - 1]; const latestOxygen = res[res.length - 1];
if (latestOxygen && latestOxygen.value !== undefined && latestOxygen.value !== null) { if (latestOxygen && latestOxygen.value !== undefined && latestOxygen.value !== null) {
let value = Number(latestOxygen.value); let value = Number(latestOxygen.value);
// 检查数据格式如果值小于1可能是小数形式0.0-1.0),需要转换为百分比 // 检查数据格式如果值小于1可能是小数形式0.0-1.0),需要转换为百分比
if (value > 0 && value < 1) { if (value > 0 && value < 1) {
value = value * 100; value = value * 100;
console.log('血氧饱和度数据从小数转换为百分比:', latestOxygen.value, '->', value); console.log('血氧饱和度数据从小数转换为百分比:', latestOxygen.value, '->', value);
} }
// 血氧饱和度通常在0-100之间验证数据有效性 // 血氧饱和度通常在0-100之间验证数据有效性
if (value >= 0 && value <= 100) { if (value >= 0 && value <= 100) {
resolve(Number(value.toFixed(1))); resolve(Number(value.toFixed(1)));
@@ -280,12 +280,12 @@ export async function fetchHealthDataForDate(date: Date): Promise<TodayHealthDat
sleepDuration, sleepDuration,
hrv, hrv,
// 健身圆环数据 // 健身圆环数据
activeCalories: activitySummary?.activeEnergyBurned || 0, activeCalories: Math.round(activitySummary?.activeEnergyBurned || 0),
activeCaloriesGoal: activitySummary?.activeEnergyBurnedGoal || 350, activeCaloriesGoal: Math.round(activitySummary?.activeEnergyBurnedGoal || 350),
exerciseMinutes: activitySummary?.appleExerciseTime || 0, exerciseMinutes: Math.round(activitySummary?.appleExerciseTime || 0),
exerciseMinutesGoal: activitySummary?.appleExerciseTimeGoal || 30, exerciseMinutesGoal: Math.round(activitySummary?.appleExerciseTimeGoal || 30),
standHours: activitySummary?.appleStandHours || 0, standHours: Math.round(activitySummary?.appleStandHours || 0),
standHoursGoal: activitySummary?.appleStandHoursGoal || 12, standHoursGoal: Math.round(activitySummary?.appleStandHoursGoal || 12),
// 血氧饱和度和心率数据 // 血氧饱和度和心率数据
oxygenSaturation, oxygenSaturation,
heartRate heartRate
@@ -357,15 +357,15 @@ export async function updateWeight(weight: number) {
// 新增:测试血氧饱和度数据获取 // 新增:测试血氧饱和度数据获取
export async function testOxygenSaturationData(date: Date = new Date()): Promise<void> { export async function testOxygenSaturationData(date: Date = new Date()): Promise<void> {
console.log('=== 开始测试血氧饱和度数据获取 ==='); console.log('=== 开始测试血氧饱和度数据获取 ===');
const start = dayjs(date).startOf('day').toDate(); const start = dayjs(date).startOf('day').toDate();
const end = dayjs(date).endOf('day').toDate(); const end = dayjs(date).endOf('day').toDate();
const options = { const options = {
startDate: start.toISOString(), startDate: start.toISOString(),
endDate: end.toISOString() endDate: end.toISOString()
} as any; } as any;
return new Promise((resolve) => { return new Promise((resolve) => {
AppleHealthKit.getOxygenSaturationSamples(options, (err, res) => { AppleHealthKit.getOxygenSaturationSamples(options, (err, res) => {
if (err) { if (err) {
@@ -373,15 +373,15 @@ export async function testOxygenSaturationData(date: Date = new Date()): Promise
resolve(); resolve();
return; return;
} }
console.log('原始血氧饱和度数据:', res); console.log('原始血氧饱和度数据:', res);
if (!res || !Array.isArray(res) || res.length === 0) { if (!res || !Array.isArray(res) || res.length === 0) {
console.warn('血氧饱和度数据为空'); console.warn('血氧饱和度数据为空');
resolve(); resolve();
return; return;
} }
// 分析所有数据样本 // 分析所有数据样本
res.forEach((sample, index) => { res.forEach((sample, index) => {
console.log(`样本 ${index + 1}:`, { console.log(`样本 ${index + 1}:`, {
@@ -391,26 +391,26 @@ export async function testOxygenSaturationData(date: Date = new Date()): Promise
endDate: sample.endDate endDate: sample.endDate
}); });
}); });
// 获取最新的血氧饱和度值 // 获取最新的血氧饱和度值
const latestOxygen = res[res.length - 1]; const latestOxygen = res[res.length - 1];
if (latestOxygen && latestOxygen.value !== undefined && latestOxygen.value !== null) { if (latestOxygen && latestOxygen.value !== undefined && latestOxygen.value !== null) {
let value = Number(latestOxygen.value); let value = Number(latestOxygen.value);
console.log('处理前的值:', latestOxygen.value); console.log('处理前的值:', latestOxygen.value);
console.log('转换为数字后的值:', value); console.log('转换为数字后的值:', value);
// 检查数据格式如果值小于1可能是小数形式0.0-1.0),需要转换为百分比 // 检查数据格式如果值小于1可能是小数形式0.0-1.0),需要转换为百分比
if (value > 0 && value < 1) { if (value > 0 && value < 1) {
const originalValue = value; const originalValue = value;
value = value * 100; value = value * 100;
console.log('血氧饱和度数据从小数转换为百分比:', originalValue, '->', value); console.log('血氧饱和度数据从小数转换为百分比:', originalValue, '->', value);
} }
console.log('最终处理后的值:', value); console.log('最终处理后的值:', value);
console.log('数据有效性检查:', value >= 0 && value <= 100 ? '有效' : '无效'); console.log('数据有效性检查:', value >= 0 && value <= 100 ? '有效' : '无效');
} }
console.log('=== 血氧饱和度数据测试完成 ==='); console.log('=== 血氧饱和度数据测试完成 ===');
resolve(); resolve();
}); });