# 方案总结
基于提供的 Git diff,我将生成以下 conventional commit message: ## 变更分析: 1. **核心功能**: - 新增睡眠监控服务(`services/sleepMonitor.ts`) - 新增睡眠通知服务(`services/sleepNotificationService.ts`) - iOS 原生端增加睡眠观察者方法 2. **应用启动优化**: - 重构 `app/_layout.tsx` 中的初始化流程,按优先级分阶段加载服务 3. **药品功能改进**: - 优化语音识别交互(实时预览、可取消) - Widget 增加 URL scheme 支持 4. **路由配置**: - 新增药品管理路由常量 ## 提交信息类型: - **主类型**:`feat` (新增睡眠监控功能) - **作用域**:`health` (健康相关功能) --- 请确认方案后,我将生成最终的 commit message。 --- **最终 Commit Message:** feat(health): 添加睡眠监控和通知服务,优化应用启动流程 - 新增睡眠监控服务,支持实时监听 HealthKit 睡眠数据更新 - 实现睡眠质量分析算法,计算睡眠评分和各阶段占比 - 新增睡眠通知服务,分析完成后自动推送质量评估和建议 - iOS 原生端实现睡眠数据观察者,支持后台数据传递 - 重构应用启动初始化流程,按优先级分阶段加载服务(关键/次要/后台/空闲) - 优化药品录入页面语音识别交互,支持实时预览和取消操作 - 药品 Widget 增加 deeplink 支持,点击跳转到应用 - 新增药品管理路由常量配置
This commit is contained in:
513
services/sleepMonitor.ts
Normal file
513
services/sleepMonitor.ts
Normal file
@@ -0,0 +1,513 @@
|
||||
/**
|
||||
* 睡眠监控服务
|
||||
*
|
||||
* 负责监听睡眠数据更新事件,分析睡眠质量并提供反馈
|
||||
*/
|
||||
|
||||
import { logger } from '@/utils/logger';
|
||||
import { NativeEventEmitter, NativeModules } from 'react-native';
|
||||
import { analyzeSleepAndSendNotification } from './sleepNotificationService';
|
||||
|
||||
const { HealthKitManager } = NativeModules;
|
||||
|
||||
|
||||
// 睡眠阶段类型
|
||||
export type SleepStage = 'inBed' | 'asleep' | 'awake' | 'core' | 'deep' | 'rem' | 'unknown';
|
||||
|
||||
// 睡眠样本数据
|
||||
export interface SleepSample {
|
||||
id: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
value: number;
|
||||
categoryType: SleepStage;
|
||||
duration: number;
|
||||
source: {
|
||||
name: string;
|
||||
bundleIdentifier: string;
|
||||
};
|
||||
metadata: Record<string, any>;
|
||||
}
|
||||
|
||||
// 睡眠分析结果
|
||||
export interface SleepAnalysisData {
|
||||
sessionStart: string;
|
||||
sessionEnd: string;
|
||||
totalSleepDuration: number; // 秒
|
||||
totalSleepHours: number; // 小时
|
||||
deepSleepDuration: number; // 秒
|
||||
deepSleepHours: number; // 小时
|
||||
deepSleepPercentage: number; // 百分比
|
||||
remSleepDuration: number; // 秒
|
||||
remSleepHours: number; // 小时
|
||||
remSleepPercentage: number; // 百分比
|
||||
coreSleepDuration: number; // 秒
|
||||
coreSleepHours: number; // 小时
|
||||
coreSleepPercentage: number; // 百分比
|
||||
awakeDuration: number; // 秒
|
||||
awakeMinutes: number; // 分钟
|
||||
sleepEfficiency: number; // 百分比
|
||||
sleepScore: number; // 0-100分
|
||||
quality: 'excellent' | 'good' | 'fair' | 'poor' | 'very_poor';
|
||||
}
|
||||
|
||||
// 睡眠事件数据
|
||||
export interface SleepEventData {
|
||||
timestamp: number;
|
||||
type: 'sleep_data_updated';
|
||||
}
|
||||
|
||||
/**
|
||||
* 睡眠监控服务类
|
||||
*/
|
||||
class SleepMonitorService {
|
||||
private eventEmitter: NativeEventEmitter | null = null;
|
||||
private eventSubscription: any = null;
|
||||
private isInitialized = false;
|
||||
private lastProcessedTime = 0;
|
||||
private debounceDelay = 3000; // 3秒防抖延迟
|
||||
|
||||
/**
|
||||
* 初始化睡眠监控服务
|
||||
*/
|
||||
async initialize(): Promise<boolean> {
|
||||
if (this.isInitialized) {
|
||||
console.log('[SleepMonitor] Already initialized');
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建事件发射器
|
||||
this.eventEmitter = new NativeEventEmitter(HealthKitManager);
|
||||
|
||||
// 订阅睡眠更新事件
|
||||
this.eventSubscription = this.eventEmitter.addListener(
|
||||
'sleepUpdate',
|
||||
this.handleSleepUpdate.bind(this)
|
||||
);
|
||||
|
||||
// 启动睡眠观察者
|
||||
await HealthKitManager.startSleepObserver();
|
||||
|
||||
this.isInitialized = true;
|
||||
console.log('[SleepMonitor] Initialized successfully');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[SleepMonitor] Initialization failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止睡眠监控服务
|
||||
*/
|
||||
async stop(): Promise<void> {
|
||||
if (!this.isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 取消事件订阅
|
||||
if (this.eventSubscription) {
|
||||
this.eventSubscription.remove();
|
||||
this.eventSubscription = null;
|
||||
}
|
||||
|
||||
// 停止睡眠观察者
|
||||
await HealthKitManager.stopSleepObserver();
|
||||
|
||||
this.isInitialized = false;
|
||||
console.log('[SleepMonitor] Stopped successfully');
|
||||
} catch (error) {
|
||||
console.error('[SleepMonitor] Stop failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理睡眠更新事件
|
||||
*/
|
||||
private async handleSleepUpdate(event: SleepEventData): Promise<void> {
|
||||
console.log('[SleepMonitor] Sleep data updated:', event);
|
||||
|
||||
// 防抖处理:避免短时间内重复处理
|
||||
const now = Date.now();
|
||||
if (now - this.lastProcessedTime < this.debounceDelay) {
|
||||
console.log('[SleepMonitor] Debouncing, skipping this update');
|
||||
return;
|
||||
}
|
||||
this.lastProcessedTime = now;
|
||||
|
||||
try {
|
||||
// 分析最近的睡眠数据
|
||||
const analysis = await this.analyzeSleepData();
|
||||
|
||||
if (analysis) {
|
||||
logger.info('[SleepMonitor] Sleep analysis completed:', {
|
||||
score: analysis.sleepScore,
|
||||
quality: analysis.quality,
|
||||
duration: `${analysis.totalSleepHours.toFixed(1)}h`,
|
||||
});
|
||||
|
||||
// 发送睡眠分析通知
|
||||
await this.notifySleepAnalysis(analysis);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[SleepMonitor] Failed to analyze sleep data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析睡眠数据
|
||||
*/
|
||||
async analyzeSleepData(): Promise<SleepAnalysisData | null> {
|
||||
try {
|
||||
// 获取最近24小时的睡眠数据
|
||||
const endDate = new Date();
|
||||
const startDate = new Date(endDate.getTime() - 24 * 60 * 60 * 1000);
|
||||
|
||||
const result = await HealthKitManager.getSleepData({
|
||||
startDate: startDate.toISOString(),
|
||||
endDate: endDate.toISOString(),
|
||||
});
|
||||
|
||||
if (!result.data || result.data.length === 0) {
|
||||
console.log('[SleepMonitor] No sleep data found');
|
||||
return null;
|
||||
}
|
||||
|
||||
const sleepSamples: SleepSample[] = result.data;
|
||||
|
||||
// 找到最近一次完整的睡眠会话
|
||||
const session = this.findLatestCompleteSleepSession(sleepSamples);
|
||||
if (!session) {
|
||||
console.log('[SleepMonitor] No complete sleep session found');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 分析睡眠样本
|
||||
const analysis = this.analyzeSleepSamples(session.samples, session.startDate, session.endDate);
|
||||
|
||||
return analysis;
|
||||
} catch (error) {
|
||||
console.error('[SleepMonitor] Failed to analyze sleep data:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 找到最近一次完整的睡眠会话
|
||||
*/
|
||||
private findLatestCompleteSleepSession(samples: SleepSample[]): {
|
||||
samples: SleepSample[];
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
} | null {
|
||||
if (samples.length === 0) return null;
|
||||
|
||||
// 按开始时间排序
|
||||
const sortedSamples = [...samples].sort((a, b) =>
|
||||
new Date(a.startDate).getTime() - new Date(b.startDate).getTime()
|
||||
);
|
||||
|
||||
// 从最新的样本开始往前查找
|
||||
const reversedSamples = [...sortedSamples].reverse();
|
||||
const sessionSamples: SleepSample[] = [];
|
||||
let sessionEnd: Date | null = null;
|
||||
|
||||
for (const sample of reversedSamples) {
|
||||
// 如果是第一个样本,记录结束时间
|
||||
if (!sessionEnd) {
|
||||
sessionEnd = new Date(sample.endDate);
|
||||
}
|
||||
|
||||
// 收集连续的睡眠样本(时间间隔不超过30分钟)
|
||||
if (sessionSamples.length > 0) {
|
||||
const lastSample = sessionSamples[sessionSamples.length - 1];
|
||||
const gap = new Date(lastSample.startDate).getTime() - new Date(sample.endDate).getTime();
|
||||
if (gap > 30 * 60 * 1000) { // 间隔超过30分钟
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sessionSamples.push(sample);
|
||||
}
|
||||
|
||||
// 反转回正常顺序
|
||||
sessionSamples.reverse();
|
||||
|
||||
if (sessionSamples.length === 0) return null;
|
||||
|
||||
const sessionStart = new Date(sessionSamples[0].startDate);
|
||||
const sessionEndDate = sessionEnd || new Date(sessionSamples[sessionSamples.length - 1].endDate);
|
||||
|
||||
// 确保会话至少有2小时的数据
|
||||
const duration = sessionEndDate.getTime() - sessionStart.getTime();
|
||||
if (duration < 2 * 60 * 60 * 1000) { // 至少2小时
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
samples: sessionSamples,
|
||||
startDate: sessionStart,
|
||||
endDate: sessionEndDate,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析睡眠样本数据
|
||||
*/
|
||||
private analyzeSleepSamples(
|
||||
samples: SleepSample[],
|
||||
sessionStart: Date,
|
||||
sessionEnd: Date
|
||||
): SleepAnalysisData {
|
||||
// 计算各阶段睡眠时长
|
||||
let totalSleepDuration = 0;
|
||||
let deepSleepDuration = 0;
|
||||
let remSleepDuration = 0;
|
||||
let coreSleepDuration = 0;
|
||||
let awakeDuration = 0;
|
||||
|
||||
for (const sample of samples) {
|
||||
const duration = sample.duration;
|
||||
|
||||
switch (sample.categoryType) {
|
||||
case 'deep':
|
||||
deepSleepDuration += duration;
|
||||
totalSleepDuration += duration;
|
||||
break;
|
||||
case 'rem':
|
||||
remSleepDuration += duration;
|
||||
totalSleepDuration += duration;
|
||||
break;
|
||||
case 'core':
|
||||
coreSleepDuration += duration;
|
||||
totalSleepDuration += duration;
|
||||
break;
|
||||
case 'asleep':
|
||||
totalSleepDuration += duration;
|
||||
break;
|
||||
case 'awake':
|
||||
awakeDuration += duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算睡眠效率
|
||||
const totalInBedDuration = (sessionEnd.getTime() - sessionStart.getTime()) / 1000;
|
||||
const sleepEfficiency = totalInBedDuration > 0
|
||||
? (totalSleepDuration / totalInBedDuration) * 100
|
||||
: 0;
|
||||
|
||||
// 计算各阶段睡眠占比
|
||||
const deepSleepPercentage = totalSleepDuration > 0
|
||||
? (deepSleepDuration / totalSleepDuration) * 100
|
||||
: 0;
|
||||
const remSleepPercentage = totalSleepDuration > 0
|
||||
? (remSleepDuration / totalSleepDuration) * 100
|
||||
: 0;
|
||||
const coreSleepPercentage = totalSleepDuration > 0
|
||||
? (coreSleepDuration / totalSleepDuration) * 100
|
||||
: 0;
|
||||
|
||||
// 计算睡眠评分
|
||||
const sleepScore = this.calculateSleepScore(
|
||||
totalSleepDuration,
|
||||
deepSleepDuration,
|
||||
remSleepDuration,
|
||||
sleepEfficiency,
|
||||
awakeDuration
|
||||
);
|
||||
|
||||
return {
|
||||
sessionStart: sessionStart.toISOString(),
|
||||
sessionEnd: sessionEnd.toISOString(),
|
||||
totalSleepDuration,
|
||||
totalSleepHours: totalSleepDuration / 3600,
|
||||
deepSleepDuration,
|
||||
deepSleepHours: deepSleepDuration / 3600,
|
||||
deepSleepPercentage,
|
||||
remSleepDuration,
|
||||
remSleepHours: remSleepDuration / 3600,
|
||||
remSleepPercentage,
|
||||
coreSleepDuration,
|
||||
coreSleepHours: coreSleepDuration / 3600,
|
||||
coreSleepPercentage,
|
||||
awakeDuration,
|
||||
awakeMinutes: awakeDuration / 60,
|
||||
sleepEfficiency,
|
||||
sleepScore,
|
||||
quality: this.getSleepQualityLabel(sleepScore),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算睡眠评分(0-100分)
|
||||
*/
|
||||
private calculateSleepScore(
|
||||
totalSleepDuration: number,
|
||||
deepSleepDuration: number,
|
||||
remSleepDuration: number,
|
||||
sleepEfficiency: number,
|
||||
awakeDuration: number
|
||||
): number {
|
||||
let score = 0;
|
||||
|
||||
// 1. 总睡眠时长评分(40分)
|
||||
const sleepHours = totalSleepDuration / 3600;
|
||||
if (sleepHours >= 7 && sleepHours <= 9) {
|
||||
score += 40; // 理想睡眠时长
|
||||
} else if (sleepHours >= 6 && sleepHours < 7) {
|
||||
score += 35; // 稍短
|
||||
} else if (sleepHours >= 9 && sleepHours < 10) {
|
||||
score += 35; // 稍长
|
||||
} else if (sleepHours >= 5 && sleepHours < 6) {
|
||||
score += 25; // 较短
|
||||
} else if (sleepHours >= 10 && sleepHours < 11) {
|
||||
score += 30; // 较长
|
||||
} else if (sleepHours >= 4 && sleepHours < 5) {
|
||||
score += 15; // 很短
|
||||
} else if (sleepHours >= 11) {
|
||||
score += 20; // 过长
|
||||
} else {
|
||||
score += 10; // 极短
|
||||
}
|
||||
|
||||
// 2. 深度睡眠评分(25分)
|
||||
const deepSleepPercentage = totalSleepDuration > 0
|
||||
? (deepSleepDuration / totalSleepDuration) * 100
|
||||
: 0;
|
||||
|
||||
if (deepSleepPercentage >= 13 && deepSleepPercentage <= 23) {
|
||||
score += 25; // 理想的深度睡眠占比(13-23%)
|
||||
} else if (deepSleepPercentage >= 10 && deepSleepPercentage < 13) {
|
||||
score += 20;
|
||||
} else if (deepSleepPercentage >= 23 && deepSleepPercentage < 30) {
|
||||
score += 20;
|
||||
} else if (deepSleepPercentage >= 7 && deepSleepPercentage < 10) {
|
||||
score += 15;
|
||||
} else if (deepSleepPercentage >= 30) {
|
||||
score += 15;
|
||||
} else {
|
||||
score += 10;
|
||||
}
|
||||
|
||||
// 3. REM睡眠评分(20分)
|
||||
const remSleepPercentage = totalSleepDuration > 0
|
||||
? (remSleepDuration / totalSleepDuration) * 100
|
||||
: 0;
|
||||
|
||||
if (remSleepPercentage >= 20 && remSleepPercentage <= 25) {
|
||||
score += 20; // 理想的REM睡眠占比(20-25%)
|
||||
} else if (remSleepPercentage >= 15 && remSleepPercentage < 20) {
|
||||
score += 18;
|
||||
} else if (remSleepPercentage >= 25 && remSleepPercentage < 30) {
|
||||
score += 18;
|
||||
} else if (remSleepPercentage >= 10 && remSleepPercentage < 15) {
|
||||
score += 12;
|
||||
} else if (remSleepPercentage >= 30) {
|
||||
score += 15;
|
||||
} else {
|
||||
score += 8;
|
||||
}
|
||||
|
||||
// 4. 睡眠效率评分(15分)
|
||||
if (sleepEfficiency >= 85) {
|
||||
score += 15; // 优秀的睡眠效率
|
||||
} else if (sleepEfficiency >= 75) {
|
||||
score += 12;
|
||||
} else if (sleepEfficiency >= 65) {
|
||||
score += 8;
|
||||
} else {
|
||||
score += 5;
|
||||
}
|
||||
|
||||
return Math.min(Math.round(score), 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据评分获取睡眠质量标签
|
||||
*/
|
||||
private getSleepQualityLabel(score: number): 'excellent' | 'good' | 'fair' | 'poor' | 'very_poor' {
|
||||
if (score >= 90) return 'excellent';
|
||||
if (score >= 75) return 'good';
|
||||
if (score >= 60) return 'fair';
|
||||
if (score >= 40) return 'poor';
|
||||
return 'very_poor';
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知睡眠分析结果
|
||||
*/
|
||||
private async notifySleepAnalysis(analysis: SleepAnalysisData): Promise<void> {
|
||||
try {
|
||||
// 发送睡眠分析通知
|
||||
await analyzeSleepAndSendNotification(analysis);
|
||||
|
||||
logger.info('[SleepMonitor] Sleep analysis notification sent:', {
|
||||
score: analysis.sleepScore,
|
||||
quality: analysis.quality,
|
||||
duration: `${analysis.totalSleepHours.toFixed(1)}h`,
|
||||
efficiency: `${analysis.sleepEfficiency.toFixed(0)}%`,
|
||||
});
|
||||
|
||||
// 可以在这里更新 Redux store
|
||||
// store.dispatch(updateSleepAnalysis(analysis));
|
||||
} catch (error) {
|
||||
logger.error('[SleepMonitor] Failed to send sleep notification:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取睡眠质量描述
|
||||
*/
|
||||
getQualityDescription(quality: string): string {
|
||||
const descriptions = {
|
||||
excellent: '优秀 - 您的睡眠质量非常好!',
|
||||
good: '良好 - 您的睡眠质量不错',
|
||||
fair: '一般 - 您的睡眠质量还可以改善',
|
||||
poor: '较差 - 建议改善睡眠习惯',
|
||||
very_poor: '很差 - 强烈建议关注睡眠健康',
|
||||
};
|
||||
return descriptions[quality as keyof typeof descriptions] || '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取睡眠建议
|
||||
*/
|
||||
getSleepRecommendations(analysis: SleepAnalysisData): string[] {
|
||||
const recommendations: string[] = [];
|
||||
|
||||
// 基于总睡眠时长的建议
|
||||
if (analysis.totalSleepHours < 7) {
|
||||
recommendations.push('建议增加睡眠时间,成年人每晚需要7-9小时睡眠');
|
||||
} else if (analysis.totalSleepHours > 9) {
|
||||
recommendations.push('睡眠时间偏长,可能影响睡眠质量');
|
||||
}
|
||||
|
||||
// 基于深度睡眠的建议
|
||||
if (analysis.deepSleepPercentage < 13) {
|
||||
recommendations.push('深度睡眠不足,建议睡前避免使用电子设备');
|
||||
}
|
||||
|
||||
// 基于REM睡眠的建议
|
||||
if (analysis.remSleepPercentage < 20) {
|
||||
recommendations.push('REM睡眠不足,建议保持规律的作息时间');
|
||||
}
|
||||
|
||||
// 基于睡眠效率的建议
|
||||
if (analysis.sleepEfficiency < 85) {
|
||||
recommendations.push('睡眠效率较低,建议改善睡眠环境');
|
||||
}
|
||||
|
||||
// 基于清醒时间的建议
|
||||
if (analysis.awakeMinutes > 30) {
|
||||
recommendations.push('夜间醒来时间较长,建议睡前放松身心');
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
export const sleepMonitorService = new SleepMonitorService();
|
||||
191
services/sleepNotificationService.ts
Normal file
191
services/sleepNotificationService.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* 睡眠通知服务
|
||||
*
|
||||
* 负责在睡眠分析完成后发送通知,提供睡眠质量评估和建议
|
||||
*/
|
||||
|
||||
import { logger } from '@/utils/logger';
|
||||
import * as Notifications from 'expo-notifications';
|
||||
import { SleepAnalysisData } from './sleepMonitor';
|
||||
|
||||
/**
|
||||
* 分析睡眠数据并发送通知
|
||||
*/
|
||||
export async function analyzeSleepAndSendNotification(
|
||||
analysis: SleepAnalysisData
|
||||
): Promise<void> {
|
||||
try {
|
||||
logger.info('开始分析睡眠并发送通知:', {
|
||||
score: analysis.sleepScore,
|
||||
quality: analysis.quality,
|
||||
duration: analysis.totalSleepHours,
|
||||
});
|
||||
|
||||
// 构建通知内容
|
||||
const notification = buildSleepNotification(analysis);
|
||||
|
||||
// 发送通知
|
||||
await Notifications.scheduleNotificationAsync({
|
||||
content: notification,
|
||||
trigger: null, // 立即发送
|
||||
});
|
||||
|
||||
logger.info('睡眠分析通知已发送');
|
||||
} catch (error) {
|
||||
logger.error('发送睡眠分析通知失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建睡眠通知内容
|
||||
*/
|
||||
function buildSleepNotification(analysis: SleepAnalysisData): Notifications.NotificationContentInput {
|
||||
const { sleepScore, quality, totalSleepHours, sleepEfficiency } = analysis;
|
||||
|
||||
// 根据质量等级选择emoji和标题
|
||||
const qualityConfig = getQualityConfig(quality);
|
||||
|
||||
// 构建通知标题
|
||||
const title = `${qualityConfig.emoji} ${qualityConfig.title}`;
|
||||
|
||||
// 构建通知正文
|
||||
const sleepDuration = formatSleepDuration(totalSleepHours);
|
||||
const efficiencyText = `睡眠效率 ${sleepEfficiency.toFixed(0)}%`;
|
||||
const body = `您昨晚睡了 ${sleepDuration},${efficiencyText}。评分:${sleepScore}分`;
|
||||
|
||||
// 获取建议
|
||||
const suggestion = getSleepSuggestion(analysis);
|
||||
|
||||
return {
|
||||
title,
|
||||
body: `${body}\n${suggestion}`,
|
||||
data: {
|
||||
type: 'sleep_analysis',
|
||||
score: sleepScore,
|
||||
quality,
|
||||
analysis: JSON.stringify(analysis),
|
||||
url: '/sleep-detail', // 点击通知跳转到睡眠详情页
|
||||
},
|
||||
sound: 'default',
|
||||
badge: 1,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取质量配置
|
||||
*/
|
||||
function getQualityConfig(quality: string): {
|
||||
emoji: string;
|
||||
title: string;
|
||||
} {
|
||||
const configs = {
|
||||
excellent: {
|
||||
emoji: '😴',
|
||||
title: '睡眠质量优秀',
|
||||
},
|
||||
good: {
|
||||
emoji: '😊',
|
||||
title: '睡眠质量良好',
|
||||
},
|
||||
fair: {
|
||||
emoji: '😐',
|
||||
title: '睡眠质量一般',
|
||||
},
|
||||
poor: {
|
||||
emoji: '😟',
|
||||
title: '睡眠质量较差',
|
||||
},
|
||||
very_poor: {
|
||||
emoji: '😰',
|
||||
title: '睡眠质量很差',
|
||||
},
|
||||
};
|
||||
|
||||
return configs[quality as keyof typeof configs] || {
|
||||
emoji: '💤',
|
||||
title: '睡眠分析完成',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化睡眠时长
|
||||
*/
|
||||
function formatSleepDuration(hours: number): string {
|
||||
const h = Math.floor(hours);
|
||||
const m = Math.round((hours - h) * 60);
|
||||
|
||||
if (m === 0) {
|
||||
return `${h}小时`;
|
||||
}
|
||||
return `${h}小时${m}分钟`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取睡眠建议
|
||||
*/
|
||||
function getSleepSuggestion(analysis: SleepAnalysisData): string {
|
||||
const { quality, totalSleepHours, deepSleepPercentage, remSleepPercentage, sleepEfficiency } = analysis;
|
||||
|
||||
// 优秀或良好的睡眠
|
||||
if (quality === 'excellent' || quality === 'good') {
|
||||
const tips = [
|
||||
'继续保持良好的睡眠习惯!',
|
||||
'坚持规律作息,身体会感谢你!',
|
||||
'优质睡眠让你精力充沛!',
|
||||
];
|
||||
return tips[Math.floor(Math.random() * tips.length)];
|
||||
}
|
||||
|
||||
// 根据具体问题给出建议
|
||||
const suggestions: string[] = [];
|
||||
|
||||
if (totalSleepHours < 7) {
|
||||
suggestions.push('建议增加睡眠时间至7-9小时');
|
||||
} else if (totalSleepHours > 9) {
|
||||
suggestions.push('睡眠时间偏长,注意睡眠质量');
|
||||
}
|
||||
|
||||
if (deepSleepPercentage < 13) {
|
||||
suggestions.push('深度睡眠不足,睡前避免使用电子设备');
|
||||
}
|
||||
|
||||
if (remSleepPercentage < 20) {
|
||||
suggestions.push('REM睡眠不足,保持规律的作息时间');
|
||||
}
|
||||
|
||||
if (sleepEfficiency < 85) {
|
||||
suggestions.push('睡眠效率较低,改善睡眠环境');
|
||||
}
|
||||
|
||||
// 如果有具体建议,返回第一条;否则返回通用建议
|
||||
if (suggestions.length > 0) {
|
||||
return `💡 ${suggestions[0]}`;
|
||||
}
|
||||
|
||||
return '建议关注睡眠质量,保持良好作息';
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送简单的睡眠提醒(用于测试)
|
||||
*/
|
||||
export async function sendSimpleSleepReminder(userName: string = '朋友'): Promise<void> {
|
||||
try {
|
||||
await Notifications.scheduleNotificationAsync({
|
||||
content: {
|
||||
title: '😴 睡眠质量分析',
|
||||
body: `${userName},您的睡眠数据已更新,点击查看详细分析`,
|
||||
data: {
|
||||
type: 'sleep_reminder',
|
||||
url: '/sleep-detail',
|
||||
},
|
||||
sound: 'default',
|
||||
},
|
||||
trigger: null,
|
||||
});
|
||||
|
||||
logger.info('简单睡眠提醒已发送');
|
||||
} catch (error) {
|
||||
logger.error('发送简单睡眠提醒失败:', error);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user