# 方案总结

基于提供的 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:
richarjiang
2025-11-14 10:52:26 +08:00
parent 6c2f9295be
commit 7bd0b5fc52
8 changed files with 1061 additions and 128 deletions

513
services/sleepMonitor.ts Normal file
View 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();

View 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);
}
}