feat: 更新健康数据管理功能及相关组件
- 新增 healthSlice,用于管理健康数据的 Redux 状态 - 在 Statistics 组件中整合健康数据获取逻辑,优化数据展示 - 更新 NutritionRadarCard 组件,调整卡路里计算区域,提升用户体验 - 移除不必要的状态管理,简化组件逻辑 - 优化代码结构,提升可读性和维护性
This commit is contained in:
@@ -14,8 +14,8 @@ import { getTabBarBottomPadding } from '@/constants/TabBar';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||||
import { useBackgroundTasks } from '@/hooks/useBackgroundTasks';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { calculateNutritionSummary, getDietRecords, NutritionSummary } from '@/services/dietRecords';
|
||||
import { selectHealthDataByDate, setHealthData } from '@/store/healthSlice';
|
||||
import { fetchDailyMoodCheckins, selectLatestMoodRecordByDate } from '@/store/moodSlice';
|
||||
import { getMonthDaysZh, getTodayIndexInMonth } from '@/utils/date';
|
||||
import { ensureHealthPermissions, fetchHealthDataForDate } from '@/utils/health';
|
||||
@@ -85,11 +85,7 @@ const FloatingCard = ({ children, delay = 0, style }: {
|
||||
};
|
||||
|
||||
export default function ExploreScreen() {
|
||||
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
|
||||
const colorTokens = Colors[theme];
|
||||
|
||||
const stepGoal = useAppSelector((s) => s.user.profile?.dailyStepsGoal) ?? 2000;
|
||||
const userProfile = useAppSelector((s) => s.user.profile);
|
||||
|
||||
const { pushIfAuthedElseLogin, isLoggedIn } = useAuthGuard();
|
||||
|
||||
@@ -101,28 +97,46 @@ export default function ExploreScreen() {
|
||||
return getTabBarBottomPadding(tabBarHeight) + (insets?.bottom ?? 0);
|
||||
}, [tabBarHeight, insets?.bottom]);
|
||||
|
||||
// HealthKit: 每次页面聚焦都拉取今日数据
|
||||
const [stepCount, setStepCount] = useState<number | null>(null);
|
||||
const [activeCalories, setActiveCalories] = useState<number | null>(null);
|
||||
// 基础代谢率(千卡)
|
||||
const [basalMetabolism, setBasalMetabolism] = useState<number | null>(null);
|
||||
// 睡眠时长(分钟)
|
||||
const [sleepDuration, setSleepDuration] = useState<number | null>(null);
|
||||
// HRV数据
|
||||
const [hrvValue, setHrvValue] = useState<number>(0);
|
||||
const [hrvUpdateTime, setHrvUpdateTime] = useState<Date>(new Date());
|
||||
// 健身圆环数据
|
||||
const [fitnessRingsData, setFitnessRingsData] = useState({
|
||||
// 获取当前选中日期
|
||||
const getCurrentSelectedDate = () => {
|
||||
const days = getMonthDaysZh();
|
||||
return days[selectedIndex]?.date?.toDate() ?? new Date();
|
||||
};
|
||||
|
||||
|
||||
// 获取当前选中日期
|
||||
const currentSelectedDate = getCurrentSelectedDate();
|
||||
const currentSelectedDateString = dayjs(currentSelectedDate).format('YYYY-MM-DD');
|
||||
|
||||
// 从 Redux 获取指定日期的健康数据
|
||||
const healthData = useAppSelector(selectHealthDataByDate(currentSelectedDateString));
|
||||
|
||||
// 解构健康数据
|
||||
const stepCount = healthData?.steps ?? null;
|
||||
const activeCalories = healthData?.activeEnergyBurned ?? null;
|
||||
const basalMetabolism = healthData?.basalEnergyBurned ?? null;
|
||||
const sleepDuration = healthData?.sleepDuration ?? null;
|
||||
const hrvValue = healthData?.hrv ?? 0;
|
||||
const oxygenSaturation = healthData?.oxygenSaturation ?? null;
|
||||
const heartRate = healthData?.heartRate ?? null;
|
||||
const fitnessRingsData = healthData ? {
|
||||
activeCalories: healthData.activeEnergyBurned,
|
||||
activeCaloriesGoal: healthData.activeCaloriesGoal,
|
||||
exerciseMinutes: healthData.exerciseMinutes,
|
||||
exerciseMinutesGoal: healthData.exerciseMinutesGoal,
|
||||
standHours: healthData.standHours,
|
||||
standHoursGoal: healthData.standHoursGoal,
|
||||
} : {
|
||||
activeCalories: 0,
|
||||
activeCaloriesGoal: 350,
|
||||
exerciseMinutes: 0,
|
||||
exerciseMinutesGoal: 30,
|
||||
standHours: 0,
|
||||
standHoursGoal: 12
|
||||
});
|
||||
// 血氧饱和度和心率数据
|
||||
const [oxygenSaturation, setOxygenSaturation] = useState<number | null>(null);
|
||||
const [heartRate, setHeartRate] = useState<number | null>(null);
|
||||
standHoursGoal: 12,
|
||||
};
|
||||
|
||||
// HRV更新时间
|
||||
const [hrvUpdateTime, setHrvUpdateTime] = useState<Date>(new Date());
|
||||
|
||||
// 用于触发动画重置的 token(当日期或数据变化时更新)
|
||||
const [animToken, setAnimToken] = useState(0);
|
||||
@@ -140,15 +154,10 @@ export default function ExploreScreen() {
|
||||
|
||||
const getDateKey = (d: Date) => `${dayjs(d).year()}-${dayjs(d).month() + 1}-${dayjs(d).date()}`;
|
||||
|
||||
// 获取当前选中日期
|
||||
const getCurrentSelectedDate = () => {
|
||||
const days = getMonthDaysZh();
|
||||
return days[selectedIndex]?.date?.toDate() ?? new Date();
|
||||
};
|
||||
|
||||
// 从 Redux 获取当前日期的心情记录
|
||||
const currentMoodCheckin = useAppSelector(selectLatestMoodRecordByDate(
|
||||
dayjs(getCurrentSelectedDate()).format('YYYY-MM-DD')
|
||||
currentSelectedDateString
|
||||
));
|
||||
|
||||
// 加载心情数据
|
||||
@@ -205,33 +214,17 @@ export default function ExploreScreen() {
|
||||
console.log('设置UI状态:', data);
|
||||
// 仅当该请求仍是最新时,才应用结果
|
||||
if (latestRequestKeyRef.current === requestKey) {
|
||||
setStepCount(data.steps);
|
||||
setActiveCalories(Math.round(data.activeEnergyBurned));
|
||||
setBasalMetabolism(Math.round(data.basalEnergyBurned));
|
||||
setSleepDuration(data.sleepDuration);
|
||||
// 更新健身圆环数据
|
||||
setFitnessRingsData({
|
||||
activeCalories: data.activeEnergyBurned,
|
||||
activeCaloriesGoal: data.activeCaloriesGoal,
|
||||
exerciseMinutes: data.exerciseMinutes,
|
||||
exerciseMinutesGoal: data.exerciseMinutesGoal,
|
||||
standHours: data.standHours,
|
||||
standHoursGoal: data.standHoursGoal
|
||||
});
|
||||
const dateString = dayjs(derivedDate).format('YYYY-MM-DD');
|
||||
|
||||
const hrv = data.hrv ?? 0;
|
||||
setHrvValue(hrv);
|
||||
// 使用 Redux 存储健康数据
|
||||
dispatch(setHealthData({
|
||||
date: dateString,
|
||||
data: data
|
||||
}));
|
||||
|
||||
// 更新HRV数据时间
|
||||
setHrvUpdateTime(new Date());
|
||||
|
||||
// 设置血氧饱和度和心率数据
|
||||
setOxygenSaturation(data.oxygenSaturation ?? null);
|
||||
setHeartRate(data.heartRate ?? null);
|
||||
|
||||
console.log('血氧饱和度数据:', data.oxygenSaturation);
|
||||
console.log('心率数据:', data.heartRate);
|
||||
|
||||
setAnimToken((t) => t + 1);
|
||||
} else {
|
||||
console.log('忽略过期健康数据请求结果,key=', requestKey, '最新key=', latestRequestKeyRef.current);
|
||||
@@ -240,9 +233,6 @@ export default function ExploreScreen() {
|
||||
|
||||
} catch (error) {
|
||||
console.error('HealthKit流程出现异常:', error);
|
||||
// 重置血氧饱和度和心率数据
|
||||
setOxygenSaturation(null);
|
||||
setHeartRate(null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -281,7 +271,7 @@ export default function ExploreScreen() {
|
||||
useFocusEffect(
|
||||
React.useCallback(() => {
|
||||
// 聚焦时按当前选中的日期加载,避免与用户手动选择的日期不一致
|
||||
const currentDate = getCurrentSelectedDate();
|
||||
const currentDate = currentSelectedDate;
|
||||
if (currentDate) {
|
||||
loadHealthData(currentDate);
|
||||
if (isLoggedIn) {
|
||||
@@ -317,6 +307,7 @@ export default function ExploreScreen() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{/* 背景渐变 */}
|
||||
|
||||
Reference in New Issue
Block a user