feat: 添加最大心率功能,更新用户资料编辑页面以显示最大心率数据,优化相关组件和服务

This commit is contained in:
2025-09-05 21:58:46 +08:00
parent aee291bb69
commit 3c416545db
6 changed files with 100 additions and 71 deletions

View File

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