feat: 优化提醒注册逻辑,确保用户姓名存在时注册午餐、晚餐和心情提醒;更新睡眠详情页面,添加清醒时间段的判断和模拟数据展示;调整样式以提升用户体验
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import dayjs from 'dayjs';
|
||||
import AppleHealthKit, { HealthKitPermissions } from 'react-native-health';
|
||||
import AppleHealthKit from 'react-native-health';
|
||||
|
||||
// 睡眠阶段枚举(与 HealthKit 保持一致)
|
||||
export enum SleepStage {
|
||||
InBed = 'INBED',
|
||||
Asleep = 'ASLEEP',
|
||||
Asleep = 'ASLEEP',
|
||||
Awake = 'AWAKE',
|
||||
Core = 'CORE',
|
||||
Deep = 'DEEP',
|
||||
@@ -48,22 +48,22 @@ 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; // 睡眠建议
|
||||
@@ -82,22 +82,22 @@ function createSleepDateRange(date: Date): { startDate: string; endDate: string
|
||||
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 SleepSample[]);
|
||||
resolve(results as unknown as SleepSample[]);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -110,24 +110,24 @@ async function fetchSleepHeartRateData(bedtime: string, wakeupTime: string): Pro
|
||||
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);
|
||||
});
|
||||
@@ -137,52 +137,52 @@ async function fetchSleepHeartRateData(bedtime: string, wakeupTime: string): Pro
|
||||
// 计算睡眠阶段统计
|
||||
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 && stage !== SleepStage.Awake)
|
||||
.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;
|
||||
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;
|
||||
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;
|
||||
percentage >= 35 ? SleepQuality.Good :
|
||||
percentage >= 25 ? SleepQuality.Fair : SleepQuality.Poor;
|
||||
break;
|
||||
default:
|
||||
quality = SleepQuality.Fair;
|
||||
}
|
||||
|
||||
|
||||
stats.push({
|
||||
stage,
|
||||
duration,
|
||||
@@ -190,7 +190,7 @@ function calculateSleepStageStats(samples: SleepSample[]): SleepStageStats[] {
|
||||
quality
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// 按持续时间排序
|
||||
return stats.sort((a, b) => b.duration - a.duration);
|
||||
}
|
||||
@@ -198,26 +198,26 @@ function calculateSleepStageStats(samples: SleepSample[]): SleepStageStats[] {
|
||||
// 计算睡眠得分
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -290,48 +290,48 @@ export function getSleepStageColor(stage: SleepStage): string {
|
||||
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 :
|
||||
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) :
|
||||
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,
|
||||
@@ -346,10 +346,10 @@ export async function fetchSleepDetailForDate(date: Date): Promise<SleepDetailDa
|
||||
qualityDescription: qualityInfo.description,
|
||||
recommendation: qualityInfo.recommendation
|
||||
};
|
||||
|
||||
|
||||
console.log('睡眠详情数据获取完成:', sleepDetailData);
|
||||
return sleepDetailData;
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取睡眠详情数据失败:', error);
|
||||
return null;
|
||||
@@ -360,7 +360,7 @@ export async function fetchSleepDetailForDate(date: Date): Promise<SleepDetailDa
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user