feat: 添加最大心率功能,更新用户资料编辑页面以显示最大心率数据,优化相关组件和服务
This commit is contained in:
@@ -3,7 +3,6 @@ import { DateSelector } from '@/components/DateSelector';
|
||||
import { FitnessRingsCard } from '@/components/FitnessRingsCard';
|
||||
import { MoodCard } from '@/components/MoodCard';
|
||||
import { NutritionRadarCard } from '@/components/NutritionRadarCard';
|
||||
import HeartRateCard from '@/components/statistic/HeartRateCard';
|
||||
import OxygenSaturationCard from '@/components/statistic/OxygenSaturationCard';
|
||||
import StepsCard from '@/components/StepsCard';
|
||||
import { StressMeter } from '@/components/StressMeter';
|
||||
@@ -655,13 +654,13 @@ export default function ExploreScreen() {
|
||||
</FloatingCard>
|
||||
|
||||
{/* 心率卡片 */}
|
||||
<FloatingCard style={styles.masonryCard} delay={2000}>
|
||||
{/* <FloatingCard style={styles.masonryCard} delay={2000}>
|
||||
<HeartRateCard
|
||||
resetToken={animToken}
|
||||
style={styles.basalMetabolismCardOverride}
|
||||
heartRate={heartRate}
|
||||
/>
|
||||
</FloatingCard>
|
||||
</FloatingCard> */}
|
||||
|
||||
<FloatingCard style={styles.masonryCard}>
|
||||
<View style={styles.cardHeaderRow}>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { useCosUpload } from '@/hooks/useCosUpload';
|
||||
import { fetchMyProfile, updateUserProfile } from '@/store/userSlice';
|
||||
import { fetchMaximumHeartRate } from '@/utils/health';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import DateTimePicker from '@react-native-community/datetimepicker';
|
||||
@@ -41,6 +42,7 @@ interface UserProfile {
|
||||
avatarUri?: string | null;
|
||||
avatarBase64?: string | null; // 兼容旧逻辑(不再上报)
|
||||
activityLevel?: number; // 活动水平 1-4
|
||||
maxHeartRate?: number; // 最大心率
|
||||
}
|
||||
|
||||
const STORAGE_KEY = '@user_profile';
|
||||
@@ -68,6 +70,7 @@ export default function EditProfileScreen() {
|
||||
height: undefined,
|
||||
avatarUri: null,
|
||||
activityLevel: undefined,
|
||||
maxHeartRate: undefined,
|
||||
});
|
||||
|
||||
// 出生日期选择器
|
||||
@@ -93,6 +96,7 @@ export default function EditProfileScreen() {
|
||||
height: undefined,
|
||||
avatarUri: null,
|
||||
activityLevel: undefined,
|
||||
maxHeartRate: undefined,
|
||||
};
|
||||
if (fromOnboarding) {
|
||||
try {
|
||||
@@ -122,6 +126,29 @@ export default function EditProfileScreen() {
|
||||
loadLocalProfile();
|
||||
}, []);
|
||||
|
||||
// 获取最大心率数据
|
||||
useEffect(() => {
|
||||
const loadMaximumHeartRate = async () => {
|
||||
try {
|
||||
const today = new Date();
|
||||
const startDate = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000); // 过去7天
|
||||
|
||||
const maxHeartRate = await fetchMaximumHeartRate({
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: today.toISOString(),
|
||||
});
|
||||
|
||||
if (maxHeartRate !== null) {
|
||||
setProfile(prev => ({ ...prev, maxHeartRate }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('获取最大心率失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
loadMaximumHeartRate();
|
||||
}, []);
|
||||
|
||||
// 页面聚焦时拉取最新用户信息,并刷新本地 UI
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
@@ -152,6 +179,8 @@ export default function EditProfileScreen() {
|
||||
weight: accountProfile?.weight ?? prev.weight ?? undefined,
|
||||
height: accountProfile?.height ?? prev.height ?? undefined,
|
||||
activityLevel: accountProfile?.activityLevel ?? prev.activityLevel ?? undefined,
|
||||
// maxHeartRate 不从后端获取,保持本地状态
|
||||
maxHeartRate: prev.maxHeartRate,
|
||||
}));
|
||||
}, [accountProfile]);
|
||||
|
||||
@@ -366,6 +395,20 @@ export default function EditProfileScreen() {
|
||||
openDatePicker();
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 最大心率 */}
|
||||
<ProfileCard
|
||||
icon="heart"
|
||||
iconColor="#FF6B9D"
|
||||
title="最大心率"
|
||||
value={profile.maxHeartRate ? `${Math.round(profile.maxHeartRate)}次/分钟` : '未获取'}
|
||||
onPress={() => {
|
||||
// 最大心率不可编辑,只显示
|
||||
Alert.alert('提示', '最大心率数据从健康应用自动获取');
|
||||
}}
|
||||
disabled={true}
|
||||
hideArrow={true}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 编辑弹窗 */}
|
||||
@@ -460,16 +503,20 @@ export default function EditProfileScreen() {
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileCard({ icon, iconUri, iconColor, title, value, onPress }: {
|
||||
function ProfileCard({ icon, iconUri, iconColor, title, value, onPress, disabled, hideArrow }: {
|
||||
icon?: keyof typeof Ionicons.glyphMap;
|
||||
iconUri?: string;
|
||||
iconColor?: string;
|
||||
title: string;
|
||||
value: string;
|
||||
onPress: () => void;
|
||||
disabled?: boolean;
|
||||
hideArrow?: boolean;
|
||||
}) {
|
||||
const Container = disabled ? View : TouchableOpacity;
|
||||
|
||||
return (
|
||||
<TouchableOpacity onPress={onPress} style={styles.profileCard} activeOpacity={0.8}>
|
||||
<Container onPress={disabled ? undefined : onPress} style={styles.profileCard} {...(disabled ? {} : { activeOpacity: 0.8 })}>
|
||||
<View style={styles.profileCardLeft}>
|
||||
<View style={[styles.iconContainer]}>
|
||||
{iconUri ? <Image
|
||||
@@ -481,9 +528,9 @@ function ProfileCard({ icon, iconUri, iconColor, title, value, onPress }: {
|
||||
</View>
|
||||
<View style={styles.profileCardRight}>
|
||||
<Text style={styles.profileCardValue}>{value}</Text>
|
||||
<Ionicons name="chevron-forward" size={16} color="#C7C7CC" />
|
||||
{!hideArrow && <Ionicons name="chevron-forward" size={16} color="#C7C7CC" />}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user