Files
digital-pilates/services/sleepService.ts

376 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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[];
// 心率数据
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<SleepSample[]> {
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);
resolve(results as unknown as SleepSample[]);
});
});
}
// 获取睡眠期间心率数据
async function fetchSleepHeartRateData(bedtime: string, wakeupTime: string): Promise<HeartRateData[]> {
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<SleepStage, number>();
// 计算每个阶段的总时长
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 totalSleepTime = 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 || stage === SleepStage.Awake) return;
const percentage = totalSleepTime > 0 ? (duration / totalSleepTime) * 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;
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 '#1E40AF'; // 深蓝色
case SleepStage.Core:
return '#3B82F6'; // 蓝色
case SleepStage.REM:
return '#8B5CF6'; // 紫色
case SleepStage.Asleep:
return '#06B6D4'; // 青色
case SleepStage.Awake:
return '#F59E0B'; // 橙色
case SleepStage.InBed:
return '#6B7280'; // 灰色
default:
return '#9CA3AF';
}
}
// 主函数:获取完整的睡眠详情数据
export async function fetchSleepDetailForDate(date: Date): Promise<SleepDetailData | null> {
try {
console.log('开始获取睡眠详情数据...', date);
// 获取睡眠样本数据
const sleepSamples = await fetchSleepSamples(date);
if (sleepSamples.length === 0) {
console.warn('没有找到睡眠数据');
return null;
}
// 找到上床时间和起床时间
const inBedSamples = sleepSamples.filter(sample => sample.value === SleepStage.InBed);
const bedtime = inBedSamples.length > 0 ? inBedSamples[0].startDate : sleepSamples[0].startDate;
const wakeupTime = inBedSamples.length > 0 ?
inBedSamples[inBedSamples.length - 1].endDate :
sleepSamples[sleepSamples.length - 1].endDate;
// 计算在床时间
const timeInBed = dayjs(wakeupTime).diff(dayjs(bedtime), 'minute');
// 计算睡眠阶段统计
const sleepStages = calculateSleepStageStats(sleepSamples);
// 计算总睡眠时间
const totalSleepTime = sleepStages.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,
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');
}