import dayjs from 'dayjs'; import AppleHealthKit from 'react-native-health'; // 睡眠阶段枚举(与 HealthKit 保持一致) export enum SleepStage { InBed = 'INBED', Asleep = 'ASLEEP', Awake = 'AWAKE', Core = 'CORE', Deep = 'DEEP', REM = 'REM' } // 睡眠质量评级 export enum SleepQuality { Poor = 'poor', // 差 Fair = 'fair', // 一般 Good = 'good', // 良好 Excellent = 'excellent' // 优秀 } // 睡眠样本数据类型 export type SleepSample = { startDate: string; endDate: string; value: SleepStage; sourceName?: string; sourceId?: string; }; // 睡眠阶段统计 export type SleepStageStats = { stage: SleepStage; duration: number; // 分钟 percentage: number; // 百分比 quality: SleepQuality; }; // 心率数据类型 export type HeartRateData = { timestamp: string; value: number; // BPM }; // 睡眠详情数据类型 export type SleepDetailData = { // 基础睡眠信息 sleepScore: number; // 睡眠得分 0-100 totalSleepTime: number; // 总睡眠时间(分钟) sleepQualityPercentage: number; // 睡眠质量百分比 // 睡眠时间信息 bedtime: string; // 上床时间 wakeupTime: string; // 起床时间 timeInBed: number; // 在床时间(分钟) // 睡眠阶段统计 sleepStages: SleepStageStats[]; // 原始睡眠样本数据(用于图表显示) rawSleepSamples: SleepSample[]; // 心率数据 averageHeartRate: number | null; // 平均心率 sleepHeartRateData: HeartRateData[]; // 睡眠期间心率数据 // 睡眠效率 sleepEfficiency: number; // 睡眠效率百分比 (总睡眠时间/在床时间) // 建议和评价 qualityDescription: string; // 睡眠质量描述 recommendation: string; // 睡眠建议 }; // 日期范围工具函数 function createSleepDateRange(date: Date): { startDate: string; endDate: string } { // 睡眠数据通常跨越两天,从前一天18:00到当天12:00 return { startDate: dayjs(date).subtract(1, 'day').hour(18).minute(0).second(0).millisecond(0).toISOString(), endDate: dayjs(date).hour(12).minute(0).second(0).millisecond(0).toISOString() }; } // 获取睡眠样本数据 async function fetchSleepSamples(date: Date): Promise { return new Promise((resolve) => { const options = createSleepDateRange(date); AppleHealthKit.getSleepSamples(options, (err, results) => { if (err) { console.error('获取睡眠样本失败:', err); resolve([]); return; } if (!results || !Array.isArray(results)) { console.warn('睡眠样本数据为空'); resolve([]); return; } // 添加详细日志,了解实际获取到的数据类型 console.log('获取到睡眠样本:', results.length); console.log('睡眠样本详情:', results.map(r => ({ value: r.value, start: r.startDate?.substring(11, 16), end: r.endDate?.substring(11, 16), duration: `${Math.round((new Date(r.endDate).getTime() - new Date(r.startDate).getTime()) / 60000)}min` }))); // 检查可用的睡眠阶段类型 const uniqueValues = [...new Set(results.map(r => r.value))]; console.log('可用的睡眠阶段类型:', uniqueValues); resolve(results as unknown as SleepSample[]); }); }); } // 获取睡眠期间心率数据 async function fetchSleepHeartRateData(bedtime: string, wakeupTime: string): Promise { return new Promise((resolve) => { const options = { startDate: bedtime, endDate: wakeupTime, ascending: true }; AppleHealthKit.getHeartRateSamples(options, (err, results) => { if (err) { console.error('获取睡眠心率数据失败:', err); resolve([]); return; } if (!results || !Array.isArray(results)) { resolve([]); return; } const heartRateData: HeartRateData[] = results.map(sample => ({ timestamp: sample.startDate, value: Math.round(sample.value) })); console.log('获取到睡眠心率数据:', heartRateData.length, '个样本'); resolve(heartRateData); }); }); } // 计算睡眠阶段统计 function calculateSleepStageStats(samples: SleepSample[]): SleepStageStats[] { const stageMap = new Map(); // 计算每个阶段的总时长 samples.forEach(sample => { const startTime = dayjs(sample.startDate); const endTime = dayjs(sample.endDate); const duration = endTime.diff(startTime, 'minute'); const currentDuration = stageMap.get(sample.value) || 0; stageMap.set(sample.value, currentDuration + duration); }); // 计算实际睡眠时间(包括所有睡眠阶段,排除在床时间) const actualSleepTime = Array.from(stageMap.entries()) .filter(([stage]) => stage !== SleepStage.InBed) .reduce((total, [, duration]) => total + duration, 0); // 生成统计数据,包含所有睡眠阶段(包括清醒时间) const stats: SleepStageStats[] = []; stageMap.forEach((duration, stage) => { // 只排除在床时间,保留清醒时间 if (stage === SleepStage.InBed) return; const percentage = actualSleepTime > 0 ? (duration / actualSleepTime) * 100 : 0; let quality: SleepQuality; // 根据睡眠阶段和比例判断质量 switch (stage) { case SleepStage.Deep: quality = percentage >= 15 ? SleepQuality.Excellent : percentage >= 10 ? SleepQuality.Good : percentage >= 5 ? SleepQuality.Fair : SleepQuality.Poor; break; case SleepStage.REM: quality = percentage >= 20 ? SleepQuality.Excellent : percentage >= 15 ? SleepQuality.Good : percentage >= 10 ? SleepQuality.Fair : SleepQuality.Poor; break; case SleepStage.Core: quality = percentage >= 45 ? SleepQuality.Excellent : percentage >= 35 ? SleepQuality.Good : percentage >= 25 ? SleepQuality.Fair : SleepQuality.Poor; break; case SleepStage.Awake: // 清醒时间越少越好 quality = percentage <= 5 ? SleepQuality.Excellent : percentage <= 10 ? SleepQuality.Good : percentage <= 15 ? SleepQuality.Fair : SleepQuality.Poor; break; default: quality = SleepQuality.Fair; } stats.push({ stage, duration, percentage: Math.round(percentage), quality }); }); // 按持续时间排序 return stats.sort((a, b) => b.duration - a.duration); } // 计算睡眠得分 function calculateSleepScore(sleepStages: SleepStageStats[], sleepEfficiency: number, totalSleepTime: number): number { let score = 0; // 睡眠时长得分 (30分) const idealSleepHours = 8 * 60; // 8小时 const sleepDurationScore = Math.min(30, (totalSleepTime / idealSleepHours) * 30); score += sleepDurationScore; // 睡眠效率得分 (25分) const efficiencyScore = (sleepEfficiency / 100) * 25; score += efficiencyScore; // 深度睡眠得分 (25分) const deepSleepStage = sleepStages.find(stage => stage.stage === SleepStage.Deep); const deepSleepScore = deepSleepStage ? Math.min(25, (deepSleepStage.percentage / 20) * 25) : 0; score += deepSleepScore; // REM睡眠得分 (20分) const remSleepStage = sleepStages.find(stage => stage.stage === SleepStage.REM); const remSleepScore = remSleepStage ? Math.min(20, (remSleepStage.percentage / 25) * 20) : 0; score += remSleepScore; return Math.round(Math.min(100, score)); } // 获取睡眠质量描述和建议 function getSleepQualityInfo(sleepScore: number): { description: string; recommendation: string } { if (sleepScore >= 85) { return { description: '你身心愉悦并且精力充沛', recommendation: '恭喜你获得优质的睡眠!如果你感到精力充沛,可以考虑中等强度的运动,以维持健康的生活方式,并进一步减轻压力,以获得最佳睡眠。' }; } else if (sleepScore >= 70) { return { description: '睡眠质量良好,精神状态不错', recommendation: '你的睡眠质量还不错,但还有改善空间。建议保持规律的睡眠时间,睡前避免使用电子设备,营造安静舒适的睡眠环境。' }; } else if (sleepScore >= 50) { return { description: '睡眠质量一般,可能影响日间表现', recommendation: '你的睡眠需要改善。建议制定固定的睡前例行程序,限制咖啡因摄入,确保卧室温度适宜,考虑进行轻度运动来改善睡眠质量。' }; } else { return { description: '睡眠质量较差,建议重视睡眠健康', recommendation: '你的睡眠质量需要严重关注。建议咨询医生或睡眠专家,检查是否有睡眠障碍,同时改善睡眠环境和习惯,避免睡前刺激性活动。' }; } } // 获取睡眠阶段中文名称 export function getSleepStageDisplayName(stage: SleepStage): string { switch (stage) { case SleepStage.Deep: return '深度'; case SleepStage.Core: return '核心'; case SleepStage.REM: return '快速眼动'; case SleepStage.Asleep: return '浅睡'; case SleepStage.Awake: return '清醒'; case SleepStage.InBed: return '在床'; default: return '未知'; } } // 获取睡眠质量颜色 export function getSleepStageColor(stage: SleepStage): string { switch (stage) { case SleepStage.Deep: return '#3B82F6'; // 蓝色 case SleepStage.Core: return '#8B5CF6'; // 紫色 case SleepStage.REM: case SleepStage.Asleep: return '#EC4899'; // 粉色 case SleepStage.Awake: return '#F59E0B'; // 橙色 case SleepStage.InBed: return '#6B7280'; // 灰色 default: return '#9CA3AF'; } } // 主函数:获取完整的睡眠详情数据 export async function fetchSleepDetailForDate(date: Date): Promise { try { console.log('开始获取睡眠详情数据...', date); // 获取睡眠样本数据 const sleepSamples = await fetchSleepSamples(date); if (sleepSamples.length === 0) { console.warn('没有找到睡眠数据'); return null; } // 找到入睡时间和起床时间 // 过滤出实际睡眠阶段(排除在床时间和清醒时间) const actualSleepSamples = sleepSamples.filter(sample => sample.value !== SleepStage.InBed && sample.value !== SleepStage.Awake ); // 入睡时间:第一个实际睡眠阶段的开始时间 // 起床时间:最后一个实际睡眠阶段的结束时间 let bedtime: string; let wakeupTime: string; if (actualSleepSamples.length > 0) { // 按开始时间排序 const sortedSleepSamples = actualSleepSamples.sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime() ); bedtime = sortedSleepSamples[0].startDate; wakeupTime = sortedSleepSamples[sortedSleepSamples.length - 1].endDate; console.log('计算入睡和起床时间:'); console.log('- 入睡时间:', dayjs(bedtime).format('YYYY-MM-DD HH:mm:ss')); console.log('- 起床时间:', dayjs(wakeupTime).format('YYYY-MM-DD HH:mm:ss')); } else { // 如果没有实际睡眠数据,回退到使用所有样本数据 console.warn('没有找到实际睡眠阶段数据,使用所有样本数据'); const sortedAllSamples = sleepSamples.sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime() ); bedtime = sortedAllSamples[0].startDate; wakeupTime = sortedAllSamples[sortedAllSamples.length - 1].endDate; } // 计算在床时间 - 使用 INBED 样本数据 let timeInBed: number; const inBedSamples = sleepSamples.filter(sample => sample.value === SleepStage.InBed); if (inBedSamples.length > 0) { // 使用 INBED 样本计算在床时间 const sortedInBedSamples = inBedSamples.sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime() ); const inBedStart = sortedInBedSamples[0].startDate; const inBedEnd = sortedInBedSamples[sortedInBedSamples.length - 1].endDate; timeInBed = dayjs(inBedEnd).diff(dayjs(inBedStart), 'minute'); console.log('在床时间计算:'); console.log('- 上床时间:', dayjs(inBedStart).format('YYYY-MM-DD HH:mm:ss')); console.log('- 离床时间:', dayjs(inBedEnd).format('YYYY-MM-DD HH:mm:ss')); console.log('- 在床时长:', timeInBed, '分钟'); } else { // 如果没有 INBED 数据,使用睡眠时间作为在床时间 timeInBed = dayjs(wakeupTime).diff(dayjs(bedtime), 'minute'); console.log('没有INBED数据,使用睡眠时间作为在床时间:', timeInBed, '分钟'); } // 计算睡眠阶段统计 const sleepStages = calculateSleepStageStats(sleepSamples); // 计算总睡眠时间(排除清醒时间) const totalSleepTime = sleepStages .filter(stage => stage.stage !== SleepStage.Awake) .reduce((total, stage) => total + stage.duration, 0); // 计算睡眠效率 const sleepEfficiency = timeInBed > 0 ? Math.round((totalSleepTime / timeInBed) * 100) : 0; // 获取睡眠期间心率数据 const sleepHeartRateData = await fetchSleepHeartRateData(bedtime, wakeupTime); // 计算平均心率 const averageHeartRate = sleepHeartRateData.length > 0 ? Math.round(sleepHeartRateData.reduce((sum, data) => sum + data.value, 0) / sleepHeartRateData.length) : null; // 计算睡眠得分 const sleepScore = calculateSleepScore(sleepStages, sleepEfficiency, totalSleepTime); // 获取质量描述和建议 const qualityInfo = getSleepQualityInfo(sleepScore); const sleepDetailData: SleepDetailData = { sleepScore, totalSleepTime, sleepQualityPercentage: sleepScore, // 使用睡眠得分作为质量百分比 bedtime, wakeupTime, timeInBed, sleepStages, rawSleepSamples: sleepSamples, // 保存原始睡眠样本数据 averageHeartRate, sleepHeartRateData, sleepEfficiency, qualityDescription: qualityInfo.description, recommendation: qualityInfo.recommendation }; console.log('睡眠详情数据获取完成:', sleepDetailData); return sleepDetailData; } catch (error) { console.error('获取睡眠详情数据失败:', error); return null; } } // 格式化睡眠时间显示 export function formatSleepTime(minutes: number): string { const hours = Math.floor(minutes / 60); const mins = minutes % 60; if (hours > 0 && mins > 0) { return `${hours}h ${mins}m`; } else if (hours > 0) { return `${hours}h`; } else { return `${mins}m`; } } // 格式化时间显示 (HH:MM) export function formatTime(dateString: string): string { return dayjs(dateString).format('HH:mm'); } // 将睡眠样本数据转换为15分钟间隔的睡眠阶段数据 export function convertSleepSamplesToIntervals(sleepSamples: SleepSample[], bedtime: string, wakeupTime: string): { time: string; stage: SleepStage }[] { const data: { time: string; stage: SleepStage }[] = []; if (sleepSamples.length === 0) { console.log('没有睡眠样本数据可用于图表显示'); return []; } // 过滤掉InBed阶段,只保留实际睡眠阶段 const sleepOnlySamples = sleepSamples.filter(sample => sample.value !== SleepStage.InBed ); if (sleepOnlySamples.length === 0) { console.log('只有InBed数据,没有详细睡眠阶段数据'); return []; } console.log('处理睡眠阶段数据 - 样本数量:', sleepOnlySamples.length); console.log('时间范围:', formatTime(bedtime), '-', formatTime(wakeupTime)); const startTime = dayjs(bedtime); const endTime = dayjs(wakeupTime); let currentTime = startTime.clone(); // 创建一个映射,用于快速查找每个时间点的睡眠阶段 while (currentTime.isBefore(endTime)) { const currentTimestamp = currentTime.toDate().getTime(); // 找到当前时间点对应的睡眠阶段 let currentStage = SleepStage.Awake; // 默认为清醒 for (const sample of sleepOnlySamples) { const sampleStart = new Date(sample.startDate).getTime(); const sampleEnd = new Date(sample.endDate).getTime(); // 如果当前时间在这个样本的时间范围内 if (currentTimestamp >= sampleStart && currentTimestamp < sampleEnd) { currentStage = sample.value; break; } } const timeStr = currentTime.format('HH:mm'); data.push({ time: timeStr, stage: currentStage }); // 移动到下一个15分钟间隔 currentTime = currentTime.add(15, 'minute'); } console.log('生成的睡眠阶段间隔数据点数量:', data.length); console.log('阶段分布:', data.reduce((acc, curr) => { acc[curr.stage] = (acc[curr.stage] || 0) + 1; return acc; }, {} as Record)); return data; }