diff --git a/app/(tabs)/statistics.tsx b/app/(tabs)/statistics.tsx
index d050b08..a825f3e 100644
--- a/app/(tabs)/statistics.tsx
+++ b/app/(tabs)/statistics.tsx
@@ -98,7 +98,6 @@ export default function ExploreScreen() {
const hourlySteps = useMockData ? (mockData?.hourlySteps ?? []) : (healthData?.hourlySteps ?? []);
const activeCalories = useMockData ? (mockData?.activeEnergyBurned ?? null) : (healthData?.activeEnergyBurned ?? null);
const basalMetabolism: number | null = useMockData ? (mockData?.basalEnergyBurned ?? null) : (healthData?.basalEnergyBurned ?? null);
- const sleepDuration = useMockData ? (mockData?.sleepDuration ?? null) : (healthData?.sleepDuration ?? null);
const hrvValue = useMockData ? (mockData?.hrv ?? null) : (healthData?.hrv ?? null);
const oxygenSaturation = useMockData ? (mockData?.oxygenSaturation ?? null) : (healthData?.oxygenSaturation ?? null);
@@ -293,7 +292,6 @@ export default function ExploreScreen() {
steps: data.steps,
activeCalories: data.activeEnergyBurned,
basalEnergyBurned: data.basalEnergyBurned,
- sleepDuration: data.sleepDuration,
hrv: data.hrv,
oxygenSaturation: data.oxygenSaturation,
heartRate: data.heartRate,
@@ -587,9 +585,9 @@ export default function ExploreScreen() {
*/}
- pushIfAuthedElseLogin('/sleep-detail')}
+ pushIfAuthedElseLogin(`/sleep-detail?date=${dayjs(currentSelectedDate).format('YYYY-MM-DD')}`)}
/>
diff --git a/app/sleep-detail.tsx b/app/sleep-detail.tsx
index f35f4c6..ca391f1 100644
--- a/app/sleep-detail.tsx
+++ b/app/sleep-detail.tsx
@@ -9,7 +9,7 @@ import {
import { Ionicons } from '@expo/vector-icons';
import dayjs from 'dayjs';
import { LinearGradient } from 'expo-linear-gradient';
-import { router } from 'expo-router';
+import { router, useLocalSearchParams } from 'expo-router';
import React, { useCallback, useEffect, useState } from 'react';
import {
ActivityIndicator,
@@ -27,113 +27,6 @@ import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
-
-
-// 简化的睡眠阶段图表组件
-const SleepStageChart = ({
- sleepData,
- onInfoPress
-}: {
- sleepData: SleepDetailData;
- onInfoPress: () => void;
-}) => {
- const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
- const colorTokens = Colors[theme];
-
- // 使用真实的睡眠阶段数据,如果没有则使用默认数据
- const stages = sleepData.sleepStages.length > 0
- ? sleepData.sleepStages
- .filter(stage => stage.percentage > 0) // 只显示有数据的阶段
- .map(stage => ({
- stage: stage.stage,
- percentage: stage.percentage,
- duration: stage.duration
- }))
- : [
- { stage: SleepStage.Awake, percentage: 1, duration: 3 },
- { stage: SleepStage.REM, percentage: 20, duration: 89 },
- { stage: SleepStage.Core, percentage: 67, duration: 295 },
- { stage: SleepStage.Deep, percentage: 12, duration: 51 }
- ];
-
- return (
-
-
- 阶段分析
-
-
-
-
-
- {/* 入睡时间和起床时间显示 */}
-
-
-
- 入睡时间
-
-
- {sleepData.bedtime ? formatTime(sleepData.bedtime) : '--:--'}
-
-
-
-
- 起床时间
-
-
- {sleepData.wakeupTime ? formatTime(sleepData.wakeupTime) : '--:--'}
-
-
-
-
- {/* 简化的睡眠阶段条 */}
-
- {stages.map((stageData, index) => {
- const color = getSleepStageColor(stageData.stage);
- // 确保最小宽度,避免清醒阶段等小比例的阶段完全不可见
- const flexValue = Math.max(stageData.percentage || 1, 3);
- return (
-
- );
- })}
-
-
- {/* 图例 */}
-
-
-
-
- 清醒时间
-
-
-
- 快速眼动
-
-
-
- 核心睡眠
-
-
-
- 深度睡眠
-
-
-
-
- );
-};
-
// SleepGradeCard 组件现在在 InfoModal 组件内部
// SleepStagesInfoModal 组件现在从独立文件导入
@@ -145,7 +38,15 @@ export default function SleepDetailScreen() {
const colorTokens = Colors[theme];
const [sleepData, setSleepData] = useState(null);
const [loading, setLoading] = useState(true);
- const [selectedDate] = useState(dayjs().toDate());
+
+ // 从导航参数获取日期,如果没有则使用今天
+ const { date: dateParam } = useLocalSearchParams<{ date?: string }>();
+ const [selectedDate] = useState(() => {
+ if (dateParam) {
+ return dayjs(dateParam).toDate();
+ }
+ return dayjs().toDate();
+ });
const [infoModal, setInfoModal] = useState<{ visible: boolean; title: string; type: 'sleep-time' | 'sleep-quality' | null }>({
visible: false,
@@ -220,7 +121,7 @@ export default function SleepDetailScreen() {
{/* 顶部导航 */}
router.back()}
withSafeTop={true}
transparent={true}
diff --git a/components/statistic/SleepCard.tsx b/components/statistic/SleepCard.tsx
index 336c91d..517630a 100644
--- a/components/statistic/SleepCard.tsx
+++ b/components/statistic/SleepCard.tsx
@@ -1,22 +1,40 @@
-import React from 'react';
-import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
+import { fetchCompleteSleepData, formatSleepTime } from '@/utils/sleepHealthKit';
+import React, { useEffect, useState } from 'react';
+import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
interface SleepCardProps {
- sleepDuration?: number | null;
+ selectedDate?: Date;
style?: object;
onPress?: () => void;
}
const SleepCard: React.FC = ({
- sleepDuration,
+ selectedDate,
style,
onPress
}) => {
- const formatSleepDuration = (duration: number): string => {
- const hours = Math.floor(duration / 60);
- const minutes = Math.floor(duration % 60);
- return `${hours}小时${minutes}分钟`;
- };
+ const [sleepDuration, setSleepDuration] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ // 获取睡眠数据
+ useEffect(() => {
+ const loadSleepData = async () => {
+ if (!selectedDate) return;
+
+ try {
+ setLoading(true);
+ const data = await fetchCompleteSleepData(selectedDate);
+ setSleepDuration(data?.totalSleepTime || null);
+ } catch (error) {
+ console.error('SleepCard: 获取睡眠数据失败:', error);
+ setSleepDuration(null);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ loadSleepData();
+ }, [selectedDate]);
const CardContent = (
@@ -24,7 +42,7 @@ const SleepCard: React.FC = ({
睡眠
- {sleepDuration != null ? formatSleepDuration(sleepDuration) : '——'}
+ {loading ? '加载中...' : (sleepDuration != null ? formatSleepTime(sleepDuration) : '--')}
);
diff --git a/store/healthSlice.ts b/store/healthSlice.ts
index 9bb36ce..51c4257 100644
--- a/store/healthSlice.ts
+++ b/store/healthSlice.ts
@@ -1,6 +1,6 @@
+import { HourlyStepData } from '@/utils/health';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { AppDispatch, RootState } from './index';
-import { HourlyStepData } from '@/utils/health';
// 健康数据类型定义
export interface FitnessRingsData {
@@ -16,7 +16,6 @@ export interface HealthData {
steps: number | null;
activeCalories: number | null;
basalEnergyBurned: number | null;
- sleepDuration: number | null;
hrv: number | null;
oxygenSaturation: number | null;
heartRate: number | null;
diff --git a/utils/health.ts b/utils/health.ts
index bf2664d..ce483ba 100644
--- a/utils/health.ts
+++ b/utils/health.ts
@@ -57,7 +57,6 @@ export type TodayHealthData = {
steps: number;
activeEnergyBurned: number; // kilocalories
basalEnergyBurned: number; // kilocalories - 基础代谢率
- sleepDuration: number; // 睡眠时长(分钟)
hrv: number | null; // 心率变异性 (ms)
// 健身圆环数据
activeCalories: number;
@@ -442,43 +441,6 @@ async function fetchBasalEnergyBurned(options: HealthDataOptions): Promise {
- return new Promise((resolve) => {
- // 使用睡眠专用的日期范围,包含前一天晚上的睡眠数据
- const sleepOptions = createSleepDateRange(date);
-
- AppleHealthKit.getSleepSamples(sleepOptions, (err, res) => {
- if (err) {
- logError('睡眠数据', err);
- return resolve(0);
- }
- if (!res || !Array.isArray(res) || res.length === 0) {
- logWarning('睡眠', '为空或格式错误');
- return resolve(0);
- }
- logSuccess('睡眠', res);
-
- // 过滤睡眠数据,只计算主睡眠时间段
- const filteredSamples = res.filter(sample => {
- if (!sample || !sample.startDate || !sample.endDate) return false;
-
- const startDate = dayjs(sample.startDate);
- const endDate = dayjs(sample.endDate);
- const targetDate = dayjs(date);
-
- // 判断这个睡眠段是否属于当天的主睡眠
- // 睡眠段的结束时间应该在当天,或者睡眠段跨越了前一天晚上到当天早上
- const isMainSleepPeriod = endDate.isSame(targetDate, 'day') ||
- (startDate.isBefore(targetDate, 'day') && endDate.isAfter(targetDate.startOf('day')));
-
- return isMainSleepPeriod;
- });
-
- resolve(calculateSleepDuration(filteredSamples));
- });
- });
-}
-
async function fetchHeartRateVariability(options: HealthDataOptions): Promise {
return new Promise((resolve) => {
console.log('=== 开始获取HRV数据 ===');
@@ -626,7 +588,6 @@ function getDefaultHealthData(): TodayHealthData {
steps: 0,
activeEnergyBurned: 0,
basalEnergyBurned: 0,
- sleepDuration: 0,
hrv: null,
activeCalories: 0,
activeCaloriesGoal: 350,
@@ -653,7 +614,6 @@ export async function fetchHealthDataForDate(date: Date): Promise