import i18n from '@/i18n'; import dayjs from 'dayjs'; import HealthKitManager, { HealthKitUtils } from './healthKit'; // 睡眠阶段枚举 export enum SleepStage { InBed = 'INBED', Asleep = 'ASLEEP', Awake = 'AWAKE', Core = 'CORE', Deep = 'DEEP', REM = 'REM' } // 阶段中被视为真正睡着的类型 const ACTIVE_SLEEP_STAGE_SET = new Set([ SleepStage.Asleep, SleepStage.Core, SleepStage.Deep, SleepStage.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; }; // 完整睡眠详情数据 export type CompleteSleepData = { sleepScore: number; 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; }; /** * 创建睡眠日期范围 (从前一天 18:00 到当天 12:00) */ const createSleepDateRange = (date: Date): { startDate: Date; endDate: Date } => { return { startDate: dayjs(date).subtract(1, 'day').hour(18).minute(0).second(0).millisecond(0).toDate(), endDate: dayjs(date).hour(12).minute(0).second(0).millisecond(0).toDate() }; }; /** * 映射 HealthKit 睡眠值到自定义枚举 * HealthKit 原生模块返回的睡眠分析值: * "inBed" = InBed (在床时间) * "asleep" = Asleep (睡着但未分阶段) * "awake" = Awake (醒来时间) * "core" = Core (核心睡眠/浅度睡眠) * "deep" = Deep (深度睡眠) * "rem" = REM (快速眼动睡眠) */ const mapHealthKitSleepValue = (value: string): SleepStage => { switch (value) { case 'inBed': return SleepStage.InBed; case 'asleep': return SleepStage.Asleep; case 'awake': return SleepStage.Awake; case 'core': return SleepStage.Core; case 'deep': return SleepStage.Deep; case 'rem': return SleepStage.REM; default: console.warn(`[Sleep] 未识别的睡眠阶段值: ${value}`); return SleepStage.Asleep; } }; /** * 获取睡眠样本数据 */ export const fetchSleepSamples = async (date: Date): Promise => { try { const dateRange = createSleepDateRange(date); console.log('[Sleep] 查询睡眠数据范围:', { startDate: dayjs(dateRange.startDate).format('YYYY-MM-DD HH:mm:ss'), endDate: dayjs(dateRange.endDate).format('YYYY-MM-DD HH:mm:ss') }); const options = { startDate: dateRange.startDate.toISOString(), endDate: dateRange.endDate.toISOString(), }; const result = await HealthKitManager.getSleepData(options); if (!result.data || result.data.length === 0) { console.warn('[Sleep] 未找到睡眠数据'); return []; } console.log('[Sleep] 获取到原始睡眠样本:', result.data.length, '条'); // 过滤并转换数据格式 const sleepSamples: SleepSample[] = result.data .filter(sample => { const sampleStart = new Date(sample.startDate).getTime(); const sampleEnd = new Date(sample.endDate).getTime(); const rangeStart = dateRange.startDate.getTime(); const rangeEnd = dateRange.endDate.getTime(); return (sampleStart >= rangeStart && sampleStart < rangeEnd) || (sampleStart < rangeEnd && sampleEnd > rangeStart); }) .map(sample => { console.log('[Sleep] 原始睡眠样本:', { startDate: sample.startDate, endDate: sample.endDate, categoryType: sample.categoryType, source: sample.source.name }); return { startDate: sample.startDate, endDate: sample.endDate, value: mapHealthKitSleepValue(sample.categoryType), sourceName: sample.source.name, sourceId: sample.id }; }); console.log('[Sleep] 过滤后的睡眠样本:', sleepSamples.length, '条'); return sleepSamples; } catch (error) { console.error('[Sleep] 获取睡眠样本失败:', error); return []; } }; /** * 获取睡眠期间心率数据 */ export const fetchSleepHeartRateData = async (): Promise => { try { // 暂时返回空数组,因为心率数据获取功能被注释掉了 return []; // return new Promise((resolve) => { // HealthKit.getHeartRateSamples(options, (error: string, results: any[]) => { // if (error) { // console.error('[Sleep] 获取心率数据失败:', error); // resolve([]); // return; // } // if (!results || results.length === 0) { // console.log('[Sleep] 未找到心率数据'); // resolve([]); // return; // } // const heartRateData: HeartRateData[] = results // .filter(sample => { // const sampleTime = new Date(sample.startDate).getTime(); // const bedtimeMs = new Date(bedtime).getTime(); // const wakeupTimeMs = new Date(wakeupTime).getTime(); // return sampleTime >= bedtimeMs && sampleTime <= wakeupTimeMs; // }) // .map(sample => ({ // timestamp: sample.startDate, // value: Math.round(sample.value) // })) // .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); // console.log('[Sleep] 获取到睡眠心率数据:', heartRateData.length, '个样本'); // resolve(heartRateData); // }); // }); } catch (error) { console.error('[Sleep] 获取睡眠心率数据失败:', error); return []; } }; /** * 计算睡眠阶段统计 */ export const calculateSleepStageStats = (samples: SleepSample[]): SleepStageStats[] => { console.log('[Sleep] 开始计算睡眠阶段统计,原始样本数:', samples.length); const stageMap = new Map(); // 统计各阶段持续时间 samples.forEach(sample => { const startTime = dayjs(sample.startDate); const endTime = dayjs(sample.endDate); const durationMinutes = endTime.diff(startTime, 'minute', true); const roundedDuration = Math.round(durationMinutes); console.log(`[Sleep] 阶段: ${sample.value}, 持续时间: ${roundedDuration}分钟`); const currentDuration = stageMap.get(sample.value) || 0; stageMap.set(sample.value, currentDuration + durationMinutes); }); console.log('[Sleep] 阶段时间统计:', Array.from(stageMap.entries())); // 计算实际睡眠时间(排除在床时间,但包含醒来时间) const actualSleepTime = Array.from(stageMap.entries()) .filter(([stage]) => stage !== SleepStage.InBed) .reduce((total, [, duration]) => total + duration, 0); console.log('[Sleep] 实际睡眠时间(包含醒来):', Math.round(actualSleepTime), '分钟'); const stats: SleepStageStats[] = []; stageMap.forEach((duration, stage) => { // 不处理在床时间,但处理所有其他阶段包括醒来时间 if (stage === SleepStage.InBed) return; const percentage = actualSleepTime > 0 ? (duration / actualSleepTime) * 100 : 0; const roundedDuration = Math.round(duration); 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.Asleep: // 未分阶段的睡眠时间,按中等质量处理 quality = SleepQuality.Fair; 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: roundedDuration, percentage: Math.round(percentage), quality }); console.log( `[Sleep] 阶段统计: ${stage}, 时长: ${roundedDuration}分钟, 百分比: ${Math.round(percentage)}%, 质量: ${quality}` ); }); const sortedStats = stats.sort((a, b) => b.duration - a.duration); console.log('[Sleep] 最终睡眠阶段统计:', sortedStats); return sortedStats; }; /** * 计算睡眠得分 */ export const calculateSleepScore = ( sleepStages: SleepStageStats[], sleepEfficiency: number, totalSleepTime: number ): number => { let score = 0; // 睡眠时长得分 (30%) const idealSleepHours = 8 * 60; 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)); }; /** * 获取睡眠质量描述和建议 */ export const getSleepQualityInfo = (sleepScore: number): { description: string; recommendation: string } => { if (sleepScore >= 85) { return { description: i18n.t('sleepQuality.excellent.description'), recommendation: i18n.t('sleepQuality.excellent.recommendation') }; } else if (sleepScore >= 70) { return { description: i18n.t('sleepQuality.good.description'), recommendation: i18n.t('sleepQuality.good.recommendation') }; } else if (sleepScore >= 50) { return { description: i18n.t('sleepQuality.fair.description'), recommendation: i18n.t('sleepQuality.fair.recommendation') }; } else { return { description: i18n.t('sleepQuality.poor.description'), recommendation: i18n.t('sleepQuality.poor.recommendation') }; } }; /** * 主函数:获取完整的睡眠详情数据 */ export const fetchCompleteSleepData = async (date: Date): Promise => { try { console.log('[Sleep] 开始获取完整睡眠数据...', dayjs(date).format('YYYY-MM-DD')); // 检查HealthKit是否可用 if (!HealthKitUtils.isAvailable()) { console.log('HealthKit不可用,可能运行在Android设备或模拟器上'); return null; } await HealthKitManager.requestAuthorization() const status = await HealthKitManager.getAuthorizationStatus() console.log('status~~~', status); // await ensureHealthPermissions() // 获取睡眠样本 const sleepSamples = await fetchSleepSamples(date); if (sleepSamples.length === 0) { console.warn('[Sleep] 没有找到睡眠数据'); return null; } // 计算入睡和起床时间 const sortedSamples = sleepSamples.sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime() ); const bedtime = sortedSamples[0].startDate; const wakeupTime = sortedSamples[sortedSamples.length - 1].endDate; console.log('[Sleep] 计算睡眠时间范围:'); console.log('- 入睡时间:', dayjs(bedtime).format('YYYY-MM-DD HH:mm:ss')); console.log('- 起床时间:', dayjs(wakeupTime).format('YYYY-MM-DD HH:mm:ss')); // 计算在床时间 let timeInBed: number; const inBedSamples = sleepSamples.filter(sample => sample.value === SleepStage.InBed); if (inBedSamples.length > 0) { 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('[Sleep] 在床时间:', timeInBed, '分钟'); } else { timeInBed = dayjs(wakeupTime).diff(dayjs(bedtime), 'minute'); console.log('[Sleep] 使用睡眠时间作为在床时间:', timeInBed, '分钟'); } // 计算睡眠阶段统计 const sleepStages = calculateSleepStageStats(sleepSamples); // 计算总睡眠时间(排除在床时间和醒来时间) const totalSleepMinutes = sleepSamples.reduce((total, sample) => { if (!ACTIVE_SLEEP_STAGE_SET.has(sample.value)) { return total; } const start = dayjs(sample.startDate); const end = dayjs(sample.endDate); return total + end.diff(start, 'minute', true); }, 0); const totalSleepTime = Math.round(totalSleepMinutes); // 重新计算睡眠效率 const sleepEfficiency = timeInBed > 0 ? Math.round((totalSleepTime / timeInBed) * 100) : 0; console.log('[Sleep] 睡眠效率计算:'); console.log('- 总睡眠时间(不含醒来):', totalSleepTime, '分钟'); console.log('- 在床时间:', timeInBed, '分钟'); console.log('- 睡眠效率:', sleepEfficiency, '%'); // 获取睡眠期间心率数据 const sleepHeartRateData = await fetchSleepHeartRateData(); 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); console.log('[Sleep] 睡眠数据处理完成:'); console.log('- 总睡眠时间:', totalSleepTime, '分钟'); console.log('- 睡眠效率:', sleepEfficiency, '%'); console.log('- 睡眠得分:', sleepScore); return { sleepScore, totalSleepTime, sleepQualityPercentage: sleepScore, bedtime, wakeupTime, timeInBed, sleepStages, rawSleepSamples: sleepSamples, averageHeartRate, sleepHeartRateData, sleepEfficiency, qualityDescription: qualityInfo.description, recommendation: qualityInfo.recommendation }; } catch (error) { console.error('[Sleep] 获取完整睡眠数据失败:', error); return null; } }; /** * 工具函数:格式化睡眠时间显示 */ export const 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`; } }; /** * 工具函数:格式化时间显示 */ export const formatTime = (dateString: string): string => { return dayjs(dateString).format('HH:mm'); }; /** * 工具函数:获取睡眠阶段颜色 */ export const 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'; } };