feat: 优化健康数据相关组件及功能
- 在 CoachScreen 中调整键盘高度计算,移除不必要的 insets.bottom - 更新 Statistics 组件,移除未使用的健康数据相关函数,简化代码 - 修改多个统计卡片,移除不必要的图标属性,提升组件简洁性 - 优化 HealthDataCard 和其他统计卡片的样式,提升视觉一致性 - 更新健康数据获取逻辑,确保数据处理更为准确 - 移除 MoodCard 中的多余元素,简化心情记录展示 - 调整 StressMeter 和其他组件的样式,提升用户体验
This commit is contained in:
@@ -419,7 +419,7 @@ export default function CoachScreen() {
|
||||
showSub = Keyboard.addListener('keyboardWillChangeFrame', (e: any) => {
|
||||
try {
|
||||
if (e?.endCoordinates?.height) {
|
||||
const height = Math.max(0, e.endCoordinates.height - insets.bottom);
|
||||
const height = Math.max(0, e.endCoordinates.height);
|
||||
setKeyboardOffset(height);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { calculateNutritionSummary, getDietRecords, NutritionSummary } from '@/services/dietRecords';
|
||||
import { fetchDailyMoodCheckins, selectLatestMoodRecordByDate } from '@/store/moodSlice';
|
||||
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 { useFocusEffect } from '@react-navigation/native';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -124,14 +124,12 @@ export default function ExploreScreen() {
|
||||
const [oxygenSaturation, setOxygenSaturation] = useState<number | null>(null);
|
||||
const [heartRate, setHeartRate] = useState<number | null>(null);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
// 用于触发动画重置的 token(当日期或数据变化时更新)
|
||||
const [animToken, setAnimToken] = useState(0);
|
||||
const [trainingProgress, setTrainingProgress] = useState(0); // 暂定静态80%
|
||||
|
||||
// 营养数据状态
|
||||
const [nutritionSummary, setNutritionSummary] = useState<NutritionSummary | null>(null);
|
||||
const [isNutritionLoading, setIsNutritionLoading] = useState(false);
|
||||
|
||||
const { registerTask } = useBackgroundTasks();
|
||||
// 心情相关状态
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -182,7 +180,6 @@ export default function ExploreScreen() {
|
||||
const loadHealthData = async (targetDate?: Date) => {
|
||||
try {
|
||||
console.log('=== 开始HealthKit初始化流程 ===');
|
||||
setIsLoading(true);
|
||||
|
||||
const ok = await ensureHealthPermissions();
|
||||
if (!ok) {
|
||||
@@ -246,15 +243,12 @@ export default function ExploreScreen() {
|
||||
// 重置血氧饱和度和心率数据
|
||||
setOxygenSaturation(null);
|
||||
setHeartRate(null);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 加载营养数据
|
||||
const loadNutritionData = async (targetDate?: Date) => {
|
||||
try {
|
||||
setIsNutritionLoading(true);
|
||||
|
||||
// 确定要查询的日期:优先使用传入的日期,否则使用当前选中索引对应的日期
|
||||
let derivedDate: Date;
|
||||
@@ -281,8 +275,6 @@ export default function ExploreScreen() {
|
||||
} catch (error) {
|
||||
console.error('营养数据加载失败:', error);
|
||||
setNutritionSummary(null);
|
||||
} finally {
|
||||
setIsNutritionLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -300,20 +292,20 @@ export default function ExploreScreen() {
|
||||
}, [selectedIndex])
|
||||
);
|
||||
|
||||
// useEffect(() => {
|
||||
// // 注册任务
|
||||
// registerTask({
|
||||
// id: 'health-data-task',
|
||||
// name: 'health-data-task',
|
||||
// handler: async () => {
|
||||
// try {
|
||||
// await loadHealthData();
|
||||
// } catch (error) {
|
||||
// console.error('健康数据任务执行失败:', error);
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
// }, []);
|
||||
useEffect(() => {
|
||||
// 注册任务
|
||||
registerTask({
|
||||
id: 'health-data-task',
|
||||
name: 'health-data-task',
|
||||
handler: async () => {
|
||||
try {
|
||||
await loadHealthData();
|
||||
} catch (error) {
|
||||
console.error('健康数据任务执行失败:', error);
|
||||
}
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 日期点击时,加载对应日期数据
|
||||
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 (
|
||||
<View style={styles.container}>
|
||||
{/* 背景渐变 */}
|
||||
@@ -384,8 +366,8 @@ export default function ExploreScreen() {
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
<FloatingCard style={[styles.masonryCard, styles.caloriesCard]} delay={500}>
|
||||
<Text style={styles.cardTitleSecondary}>消耗卡路里</Text>
|
||||
<FloatingCard style={styles.masonryCard} delay={500}>
|
||||
<Text style={styles.cardTitle}>消耗卡路里</Text>
|
||||
{activeCalories != null ? (
|
||||
<AnimatedNumber
|
||||
value={activeCalories}
|
||||
@@ -398,7 +380,7 @@ export default function ExploreScreen() {
|
||||
)}
|
||||
</FloatingCard>
|
||||
|
||||
<FloatingCard style={[styles.masonryCard, styles.stepsCard]} delay={1000}>
|
||||
<FloatingCard style={styles.masonryCard} delay={1000}>
|
||||
<View style={styles.cardHeaderRow}>
|
||||
<Text style={styles.cardTitle}>步数</Text>
|
||||
</View>
|
||||
@@ -414,14 +396,14 @@ export default function ExploreScreen() {
|
||||
)}
|
||||
<ProgressBar
|
||||
progress={Math.min(1, Math.max(0, (stepCount ?? 0) / stepGoal))}
|
||||
height={16}
|
||||
height={14}
|
||||
trackColor="#FFEBCB"
|
||||
fillColor="#FFC365"
|
||||
showLabel={false}
|
||||
/>
|
||||
</FloatingCard>
|
||||
{/* 心情卡片 */}
|
||||
<FloatingCard style={[styles.masonryCard, styles.moodCard]} delay={1500}>
|
||||
<FloatingCard style={styles.masonryCard} delay={1500}>
|
||||
<MoodCard
|
||||
moodCheckin={currentMoodCheckin}
|
||||
onPress={() => pushIfAuthedElseLogin('/mood/calendar')}
|
||||
@@ -444,7 +426,7 @@ export default function ExploreScreen() {
|
||||
/>
|
||||
</FloatingCard>
|
||||
|
||||
<FloatingCard style={[styles.masonryCard, styles.sleepCard]} delay={750}>
|
||||
<FloatingCard style={styles.masonryCard} delay={750}>
|
||||
<View style={styles.cardHeaderRow}>
|
||||
<Text style={styles.cardTitle}>睡眠</Text>
|
||||
</View>
|
||||
@@ -473,13 +455,6 @@ export default function ExploreScreen() {
|
||||
style={styles.basalMetabolismCardOverride}
|
||||
oxygenSaturation={oxygenSaturation}
|
||||
/>
|
||||
{/* 测试按钮 - 开发时使用 */}
|
||||
<Text
|
||||
style={styles.testButton}
|
||||
onPress={testOxygenData}
|
||||
>
|
||||
测试血氧数据
|
||||
</Text>
|
||||
</FloatingCard>
|
||||
|
||||
{/* 心率卡片 */}
|
||||
@@ -577,22 +552,11 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 22,
|
||||
padding: 16,
|
||||
},
|
||||
caloriesCard: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
trainingCard: {
|
||||
backgroundColor: '#EEE9FF',
|
||||
},
|
||||
cardTitleSecondary: {
|
||||
color: '#9AA3AE',
|
||||
fontSize: 10,
|
||||
fontWeight: '600',
|
||||
marginBottom: 10,
|
||||
},
|
||||
caloriesValue: {
|
||||
color: '#192126',
|
||||
fontSize: 18,
|
||||
fontWeight: '800',
|
||||
marginTop: 18,
|
||||
},
|
||||
trainingContent: {
|
||||
marginTop: 8,
|
||||
@@ -706,9 +670,6 @@ const styles = StyleSheet.create({
|
||||
color: '#5B5B5B',
|
||||
fontWeight: '600',
|
||||
},
|
||||
stepsCard: {
|
||||
backgroundColor: '#FFE4B8',
|
||||
},
|
||||
stepsValue: {
|
||||
fontSize: 14,
|
||||
color: '#7A6A42',
|
||||
@@ -797,9 +758,6 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'space-between',
|
||||
marginTop: 8,
|
||||
},
|
||||
sleepCard: {
|
||||
backgroundColor: '#E8F4FD',
|
||||
},
|
||||
sleepValue: {
|
||||
fontSize: 16,
|
||||
color: '#1E40AF',
|
||||
@@ -821,14 +779,5 @@ const styles = StyleSheet.create({
|
||||
top: 0,
|
||||
padding: 4,
|
||||
},
|
||||
moodCard: {
|
||||
backgroundColor: '#F0FDF4',
|
||||
},
|
||||
testButton: {
|
||||
fontSize: 12,
|
||||
color: '#3B82F6',
|
||||
textAlign: 'center',
|
||||
marginTop: 8,
|
||||
padding: 4,
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { AnimatedNumber } from '@/components/AnimatedNumber';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View } from 'react-native';
|
||||
|
||||
@@ -32,17 +31,6 @@ export function BasalMetabolismCard({ value, resetToken, style }: BasalMetabolis
|
||||
|
||||
return (
|
||||
<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}>
|
||||
@@ -88,34 +76,7 @@ const styles = StyleSheet.create({
|
||||
position: 'relative',
|
||||
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: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
|
||||
@@ -67,20 +67,39 @@ export const DateSelector: React.FC<DateSelectorProps> = ({
|
||||
const maxScrollOffset = Math.max(0, (days.length * itemWidth) - scrollWidth);
|
||||
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(() => {
|
||||
if (scrollWidth > 0 && autoScrollToSelected) {
|
||||
scrollToIndex(selectedIndex, false);
|
||||
scrollToIndex(selectedIndex, true);
|
||||
}
|
||||
}, [scrollWidth, selectedIndex, autoScrollToSelected]);
|
||||
|
||||
// 当选中索引变化时,滚动到对应位置
|
||||
useEffect(() => {
|
||||
if (scrollWidth > 0 && autoScrollToSelected) {
|
||||
scrollToIndex(selectedIndex, true);
|
||||
// 添加微小延迟以确保动画效果更明显
|
||||
const timer = setTimeout(() => {
|
||||
scrollToIndex(selectedIndex, true);
|
||||
}, 50);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [selectedIndex, autoScrollToSelected]);
|
||||
|
||||
@@ -94,6 +113,11 @@ export const DateSelector: React.FC<DateSelectorProps> = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// 先滚动到目标位置,再更新状态
|
||||
if (autoScrollToSelected) {
|
||||
scrollToIndex(index, true);
|
||||
}
|
||||
|
||||
// 更新内部状态(如果使用外部控制则不更新)
|
||||
if (externalSelectedIndex === undefined) {
|
||||
setInternalSelectedIndex(index);
|
||||
|
||||
@@ -14,20 +14,8 @@ export function MoodCard({ moodCheckin, onPress, isLoading = false }: MoodCardPr
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={onPress} style={styles.moodCardContent} disabled={isLoading}>
|
||||
<View style={styles.cardHeaderRow}>
|
||||
<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.cardTitle}>心情</Text>
|
||||
|
||||
<Text style={styles.moodSubtitle}>记录你的每日心情</Text>
|
||||
|
||||
{isLoading ? (
|
||||
<View style={styles.moodPreview}>
|
||||
@@ -53,39 +41,18 @@ const styles = StyleSheet.create({
|
||||
moodCardContent: {
|
||||
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: {
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
color: '#192126',
|
||||
},
|
||||
moodSubtitle: {
|
||||
fontSize: 12,
|
||||
color: '#6B7280',
|
||||
marginTop: 4,
|
||||
marginBottom: 8,
|
||||
},
|
||||
|
||||
moodPreview: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginTop: 4,
|
||||
marginTop: 22,
|
||||
},
|
||||
moodPreviewText: {
|
||||
fontSize: 14,
|
||||
@@ -100,12 +67,12 @@ const styles = StyleSheet.create({
|
||||
fontSize: 12,
|
||||
color: '#9CA3AF',
|
||||
fontStyle: 'italic',
|
||||
marginTop: 4,
|
||||
marginTop: 22,
|
||||
},
|
||||
moodLoadingText: {
|
||||
fontSize: 12,
|
||||
color: '#9CA3AF',
|
||||
fontStyle: 'italic',
|
||||
marginTop: 4,
|
||||
marginTop: 22,
|
||||
},
|
||||
});
|
||||
@@ -68,7 +68,6 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
|
||||
<View style={styles.leftSection}>
|
||||
<Text style={styles.title}>压力</Text>
|
||||
</View>
|
||||
<Text style={styles.emoji}>{getStatusEmoji()}</Text>
|
||||
</View>
|
||||
|
||||
{/* 数值显示区域 */}
|
||||
@@ -83,7 +82,7 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
|
||||
{/* 渐变背景进度条 */}
|
||||
<View style={[styles.progressBar, { width: '100%' }]}>
|
||||
<LinearGradient
|
||||
colors={['#EF4444', '#FCD34D', '#10B981']}
|
||||
colors={['#EF4444', '#FCD34D', '#10B981']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 0 }}
|
||||
style={styles.gradientBar}
|
||||
@@ -113,8 +112,6 @@ export function StressMeter({ value, updateTime, style, hrvValue }: StressMeterP
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: '#FFFFFF',
|
||||
padding: 2,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
@@ -147,19 +144,16 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
fontWeight: '700',
|
||||
fontWeight: '800',
|
||||
color: '#192126',
|
||||
},
|
||||
emoji: {
|
||||
fontSize: 16,
|
||||
},
|
||||
valueSection: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'baseline',
|
||||
marginBottom: 12,
|
||||
},
|
||||
value: {
|
||||
fontSize: 28,
|
||||
fontSize: 24,
|
||||
fontWeight: '800',
|
||||
color: '#192126',
|
||||
lineHeight: 32,
|
||||
@@ -172,7 +166,6 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
progressContainer: {
|
||||
height: 16,
|
||||
marginBottom: 4,
|
||||
},
|
||||
progressTrack: {
|
||||
height: 8,
|
||||
|
||||
@@ -6,7 +6,6 @@ interface HealthDataCardProps {
|
||||
title: string;
|
||||
value: string;
|
||||
unit: string;
|
||||
icon: React.ReactNode;
|
||||
style?: object;
|
||||
}
|
||||
|
||||
@@ -14,7 +13,6 @@ const HealthDataCard: React.FC<HealthDataCardProps> = ({
|
||||
title,
|
||||
value,
|
||||
unit,
|
||||
icon,
|
||||
style
|
||||
}) => {
|
||||
return (
|
||||
@@ -23,10 +21,6 @@ const HealthDataCard: React.FC<HealthDataCardProps> = ({
|
||||
exiting={FadeOut.duration(300)}
|
||||
style={[styles.card, style]}
|
||||
>
|
||||
|
||||
<View style={styles.iconContainer}>
|
||||
{icon}
|
||||
</View>
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.title}>{title}</Text>
|
||||
<View style={styles.valueContainer}>
|
||||
@@ -65,9 +59,9 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
title: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
color: '#192126',
|
||||
marginBottom: 4,
|
||||
fontWeight: '600',
|
||||
fontWeight: '800',
|
||||
},
|
||||
valueContainer: {
|
||||
flexDirection: 'row',
|
||||
|
||||
@@ -23,7 +23,6 @@ const HeartRateCard: React.FC<HeartRateCardProps> = ({
|
||||
title="心率"
|
||||
value={heartRate !== null && heartRate !== undefined ? Math.round(heartRate).toString() : '--'}
|
||||
unit="bpm"
|
||||
icon={heartIcon}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,6 @@ const OxygenSaturationCard: React.FC<OxygenSaturationCardProps> = ({
|
||||
title="血氧饱和度"
|
||||
value={oxygenSaturation !== null && oxygenSaturation !== undefined ? oxygenSaturation.toFixed(1) : '--'}
|
||||
unit="%"
|
||||
icon={oxygenIcon}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -221,13 +221,13 @@ export async function fetchHealthDataForDate(date: Date): Promise<TodayHealthDat
|
||||
const latestOxygen = res[res.length - 1];
|
||||
if (latestOxygen && latestOxygen.value !== undefined && latestOxygen.value !== null) {
|
||||
let value = Number(latestOxygen.value);
|
||||
|
||||
|
||||
// 检查数据格式:如果值小于1,可能是小数形式(0.0-1.0),需要转换为百分比
|
||||
if (value > 0 && value < 1) {
|
||||
value = value * 100;
|
||||
console.log('血氧饱和度数据从小数转换为百分比:', latestOxygen.value, '->', value);
|
||||
}
|
||||
|
||||
|
||||
// 血氧饱和度通常在0-100之间,验证数据有效性
|
||||
if (value >= 0 && value <= 100) {
|
||||
resolve(Number(value.toFixed(1)));
|
||||
@@ -280,12 +280,12 @@ export async function fetchHealthDataForDate(date: Date): Promise<TodayHealthDat
|
||||
sleepDuration,
|
||||
hrv,
|
||||
// 健身圆环数据
|
||||
activeCalories: activitySummary?.activeEnergyBurned || 0,
|
||||
activeCaloriesGoal: activitySummary?.activeEnergyBurnedGoal || 350,
|
||||
exerciseMinutes: activitySummary?.appleExerciseTime || 0,
|
||||
exerciseMinutesGoal: activitySummary?.appleExerciseTimeGoal || 30,
|
||||
standHours: activitySummary?.appleStandHours || 0,
|
||||
standHoursGoal: activitySummary?.appleStandHoursGoal || 12,
|
||||
activeCalories: Math.round(activitySummary?.activeEnergyBurned || 0),
|
||||
activeCaloriesGoal: Math.round(activitySummary?.activeEnergyBurnedGoal || 350),
|
||||
exerciseMinutes: Math.round(activitySummary?.appleExerciseTime || 0),
|
||||
exerciseMinutesGoal: Math.round(activitySummary?.appleExerciseTimeGoal || 30),
|
||||
standHours: Math.round(activitySummary?.appleStandHours || 0),
|
||||
standHoursGoal: Math.round(activitySummary?.appleStandHoursGoal || 12),
|
||||
// 血氧饱和度和心率数据
|
||||
oxygenSaturation,
|
||||
heartRate
|
||||
@@ -357,15 +357,15 @@ export async function updateWeight(weight: number) {
|
||||
// 新增:测试血氧饱和度数据获取
|
||||
export async function testOxygenSaturationData(date: Date = new Date()): Promise<void> {
|
||||
console.log('=== 开始测试血氧饱和度数据获取 ===');
|
||||
|
||||
|
||||
const start = dayjs(date).startOf('day').toDate();
|
||||
const end = dayjs(date).endOf('day').toDate();
|
||||
|
||||
|
||||
const options = {
|
||||
startDate: start.toISOString(),
|
||||
endDate: end.toISOString()
|
||||
} as any;
|
||||
|
||||
|
||||
return new Promise((resolve) => {
|
||||
AppleHealthKit.getOxygenSaturationSamples(options, (err, res) => {
|
||||
if (err) {
|
||||
@@ -373,15 +373,15 @@ export async function testOxygenSaturationData(date: Date = new Date()): Promise
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
console.log('原始血氧饱和度数据:', res);
|
||||
|
||||
|
||||
if (!res || !Array.isArray(res) || res.length === 0) {
|
||||
console.warn('血氧饱和度数据为空');
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 分析所有数据样本
|
||||
res.forEach((sample, index) => {
|
||||
console.log(`样本 ${index + 1}:`, {
|
||||
@@ -391,26 +391,26 @@ export async function testOxygenSaturationData(date: Date = new Date()): Promise
|
||||
endDate: sample.endDate
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// 获取最新的血氧饱和度值
|
||||
const latestOxygen = res[res.length - 1];
|
||||
if (latestOxygen && latestOxygen.value !== undefined && latestOxygen.value !== null) {
|
||||
let value = Number(latestOxygen.value);
|
||||
|
||||
|
||||
console.log('处理前的值:', latestOxygen.value);
|
||||
console.log('转换为数字后的值:', value);
|
||||
|
||||
|
||||
// 检查数据格式:如果值小于1,可能是小数形式(0.0-1.0),需要转换为百分比
|
||||
if (value > 0 && value < 1) {
|
||||
const originalValue = value;
|
||||
value = value * 100;
|
||||
console.log('血氧饱和度数据从小数转换为百分比:', originalValue, '->', value);
|
||||
}
|
||||
|
||||
|
||||
console.log('最终处理后的值:', value);
|
||||
console.log('数据有效性检查:', value >= 0 && value <= 100 ? '有效' : '无效');
|
||||
}
|
||||
|
||||
|
||||
console.log('=== 血氧饱和度数据测试完成 ===');
|
||||
resolve();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user