feat: 增加睡眠分析通知功能,支持睡眠质量评估与建议
This commit is contained in:
@@ -93,7 +93,7 @@ export class SimpleEventEmitter {
|
||||
* @param event 事件名称
|
||||
* @returns 监听器数组的副本
|
||||
*/
|
||||
listeners(event: string): ((...args: any[]) => void)[] {
|
||||
getListeners(event: string): ((...args: any[]) => void)[] {
|
||||
return this.listeners[event] ? [...this.listeners[event]] : [];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import { HourlyStepData, TodayHealthData } from './health';
|
||||
|
||||
// Mock的每小时步数数据,模拟真实的一天活动模式
|
||||
export const mockHourlySteps: HourlyStepData[] = [
|
||||
{ hour: 0, steps: 0 }, // 午夜
|
||||
{ hour: 1, steps: 0 }, // 凌晨
|
||||
{ hour: 2, steps: 0 },
|
||||
{ hour: 3, steps: 0 },
|
||||
{ hour: 4, steps: 0 },
|
||||
{ hour: 5, steps: 0 },
|
||||
{ hour: 6, steps: 120 }, // 早晨起床
|
||||
{ hour: 7, steps: 450 }, // 晨练/上班准备
|
||||
{ hour: 8, steps: 680 }, // 上班通勤
|
||||
{ hour: 9, steps: 320 }, // 工作时间
|
||||
{ hour: 10, steps: 180 }, // 办公室内活动
|
||||
{ hour: 11, steps: 280 }, // 会议/活动
|
||||
{ hour: 12, steps: 520 }, // 午餐时间
|
||||
{ hour: 13, steps: 150 }, // 午休
|
||||
{ hour: 14, steps: 240 }, // 下午工作
|
||||
{ hour: 15, steps: 300 }, // 工作活动
|
||||
{ hour: 16, steps: 380 }, // 会议/外出
|
||||
{ hour: 17, steps: 480 }, // 下班通勤
|
||||
{ hour: 18, steps: 620 }, // 晚餐/活动
|
||||
{ hour: 19, steps: 350 }, // 晚间活动
|
||||
{ hour: 20, steps: 280 }, // 散步
|
||||
{ hour: 21, steps: 150 }, // 休闲时间
|
||||
{ hour: 22, steps: 80 }, // 准备睡觉
|
||||
{ hour: 23, steps: 30 }, // 睡前
|
||||
];
|
||||
|
||||
// Mock的完整健康数据
|
||||
export const mockHealthData: TodayHealthData = {
|
||||
steps: 6140, // 总步数
|
||||
hourlySteps: mockHourlySteps,
|
||||
activeEnergyBurned: 420,
|
||||
basalEnergyBurned: 1680,
|
||||
sleepDuration: 480, // 8小时
|
||||
hrv: 45,
|
||||
activeCalories: 420,
|
||||
activeCaloriesGoal: 350,
|
||||
exerciseMinutes: 32,
|
||||
exerciseMinutesGoal: 30,
|
||||
standHours: 8,
|
||||
standHoursGoal: 12,
|
||||
oxygenSaturation: 98.2,
|
||||
heartRate: 72,
|
||||
};
|
||||
|
||||
// 生成随机的每小时步数数据(用于测试不同的数据模式)
|
||||
export const generateRandomHourlySteps = (): HourlyStepData[] => {
|
||||
return Array.from({ length: 24 }, (_, hour) => {
|
||||
let steps = 0;
|
||||
|
||||
// 模拟真实的活动模式
|
||||
if (hour >= 6 && hour <= 22) {
|
||||
if (hour >= 7 && hour <= 9) {
|
||||
// 早晨高峰期
|
||||
steps = Math.floor(Math.random() * 600) + 200;
|
||||
} else if (hour >= 12 && hour <= 13) {
|
||||
// 午餐时间
|
||||
steps = Math.floor(Math.random() * 400) + 300;
|
||||
} else if (hour >= 17 && hour <= 19) {
|
||||
// 晚间活跃期
|
||||
steps = Math.floor(Math.random() * 500) + 250;
|
||||
} else if (hour >= 6 && hour <= 22) {
|
||||
// 白天正常活动
|
||||
steps = Math.floor(Math.random() * 300) + 50;
|
||||
}
|
||||
} else {
|
||||
// 夜间很少活动
|
||||
steps = Math.floor(Math.random() * 50);
|
||||
}
|
||||
|
||||
return { hour, steps };
|
||||
});
|
||||
};
|
||||
|
||||
// 不同活动模式的预设数据
|
||||
export const activityPatterns = {
|
||||
// 久坐办公族
|
||||
sedentary: Array.from({ length: 24 }, (_, hour) => ({
|
||||
hour,
|
||||
steps: hour >= 7 && hour <= 18 ? Math.floor(Math.random() * 200) + 50 :
|
||||
hour >= 19 && hour <= 21 ? Math.floor(Math.random() * 300) + 100 :
|
||||
Math.floor(Math.random() * 20)
|
||||
})),
|
||||
|
||||
// 活跃用户
|
||||
active: Array.from({ length: 24 }, (_, hour) => ({
|
||||
hour,
|
||||
steps: hour >= 6 && hour <= 8 ? Math.floor(Math.random() * 800) + 400 :
|
||||
hour >= 12 && hour <= 13 ? Math.floor(Math.random() * 600) + 300 :
|
||||
hour >= 17 && hour <= 20 ? Math.floor(Math.random() * 900) + 500 :
|
||||
hour >= 9 && hour <= 16 ? Math.floor(Math.random() * 400) + 100 :
|
||||
Math.floor(Math.random() * 50)
|
||||
})),
|
||||
|
||||
// 健身爱好者
|
||||
fitness: Array.from({ length: 24 }, (_, hour) => ({
|
||||
hour,
|
||||
steps: hour === 6 ? Math.floor(Math.random() * 1200) + 800 : // 晨跑
|
||||
hour === 18 ? Math.floor(Math.random() * 1000) + 600 : // 晚间锻炼
|
||||
hour >= 7 && hour <= 17 ? Math.floor(Math.random() * 300) + 100 :
|
||||
Math.floor(Math.random() * 50)
|
||||
})),
|
||||
};
|
||||
|
||||
// 用于快速切换测试数据的函数
|
||||
export const getTestHealthData = (pattern: 'mock' | 'random' | 'sedentary' | 'active' | 'fitness' = 'mock'): TodayHealthData => {
|
||||
let hourlySteps: HourlyStepData[];
|
||||
|
||||
switch (pattern) {
|
||||
case 'random':
|
||||
hourlySteps = generateRandomHourlySteps();
|
||||
break;
|
||||
case 'sedentary':
|
||||
hourlySteps = activityPatterns.sedentary;
|
||||
break;
|
||||
case 'active':
|
||||
hourlySteps = activityPatterns.active;
|
||||
break;
|
||||
case 'fitness':
|
||||
hourlySteps = activityPatterns.fitness;
|
||||
break;
|
||||
default:
|
||||
hourlySteps = mockHourlySteps;
|
||||
}
|
||||
|
||||
const totalSteps = hourlySteps.reduce((sum, data) => sum + data.steps, 0);
|
||||
|
||||
return {
|
||||
...mockHealthData,
|
||||
steps: totalSteps,
|
||||
hourlySteps,
|
||||
};
|
||||
};
|
||||
@@ -1,198 +0,0 @@
|
||||
import { workoutMonitorService } from '@/services/workoutMonitor';
|
||||
import { WorkoutData } from '@/utils/health';
|
||||
|
||||
/**
|
||||
* 锻炼测试工具
|
||||
* 用于开发和测试锻炼监听功能
|
||||
*/
|
||||
export class WorkoutTestHelper {
|
||||
/**
|
||||
* 模拟一个锻炼完成事件
|
||||
*/
|
||||
static async simulateWorkoutCompletion(): Promise<void> {
|
||||
console.log('=== 开始模拟锻炼完成事件 ===');
|
||||
|
||||
try {
|
||||
// 手动触发锻炼检查
|
||||
await workoutMonitorService.manualCheck();
|
||||
console.log('✅ 锻炼检查已手动触发');
|
||||
} catch (error) {
|
||||
console.error('❌ 模拟锻炼完成事件失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取锻炼监听服务状态
|
||||
*/
|
||||
static getWorkoutMonitorStatus(): any {
|
||||
return workoutMonitorService.getStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建测试用的模拟锻炼数据
|
||||
*/
|
||||
static createMockWorkout(type: string = 'running'): WorkoutData {
|
||||
const now = new Date();
|
||||
const duration = 30 * 60; // 30分钟
|
||||
const startTime = new Date(now.getTime() - duration * 1000);
|
||||
|
||||
const workoutTypes: Record<string, number> = {
|
||||
running: 37,
|
||||
cycling: 13,
|
||||
swimming: 46,
|
||||
yoga: 57,
|
||||
functionalstrengthtraining: 20,
|
||||
traditionalstrengthtraining: 50,
|
||||
highintensityintervaltraining: 63,
|
||||
walking: 52,
|
||||
};
|
||||
|
||||
return {
|
||||
id: `mock-workout-${Date.now()}`,
|
||||
startDate: startTime.toISOString(),
|
||||
endDate: now.toISOString(),
|
||||
duration: duration,
|
||||
workoutActivityType: workoutTypes[type] || 37,
|
||||
workoutActivityTypeString: type,
|
||||
totalEnergyBurned: Math.round(Math.random() * 300 + 100), // 100-400千卡
|
||||
totalDistance: type === 'running' || type === 'cycling' ? Math.round(Math.random() * 10000 + 1000) : undefined,
|
||||
averageHeartRate: Math.round(Math.random() * 50 + 120), // 120-170次/分
|
||||
source: {
|
||||
name: 'Test App',
|
||||
bundleIdentifier: 'com.test.app'
|
||||
},
|
||||
metadata: {
|
||||
HKAverageMETs: Math.random() * 10 + 5 // 5-15 METs
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试不同类型的锻炼通知
|
||||
*/
|
||||
static async testWorkoutNotifications(): Promise<void> {
|
||||
console.log('=== 开始测试不同类型锻炼通知 ===');
|
||||
|
||||
const workoutTypes = ['running', 'cycling', 'swimming', 'yoga', 'functionalstrengthtraining', 'highintensityintervaltraining'];
|
||||
|
||||
for (const type of workoutTypes) {
|
||||
console.log(`--- 测试 ${type} 锻炼通知 ---`);
|
||||
|
||||
try {
|
||||
// 这里需要导入通知服务来直接测试
|
||||
const { analyzeWorkoutAndSendNotification } = await import('@/services/workoutNotificationService');
|
||||
const mockWorkout = this.createMockWorkout(type);
|
||||
|
||||
await analyzeWorkoutAndSendNotification(mockWorkout);
|
||||
|
||||
console.log(`✅ ${type} 锻炼通知测试成功`);
|
||||
|
||||
// 等待一段时间再测试下一个
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
} catch (error) {
|
||||
console.error(`❌ ${type} 锻炼通知测试失败:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('=== 锻炼通知测试完成 ===');
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试偏好设置功能
|
||||
*/
|
||||
static async testPreferences(): Promise<void> {
|
||||
console.log('=== 开始测试偏好设置功能 ===');
|
||||
|
||||
try {
|
||||
const {
|
||||
getWorkoutNotificationPreferences,
|
||||
saveWorkoutNotificationPreferences,
|
||||
isNotificationTimeAllowed,
|
||||
isWorkoutTypeEnabled
|
||||
} = await import('@/utils/workoutPreferences');
|
||||
|
||||
// 获取当前设置
|
||||
const currentPrefs = await getWorkoutNotificationPreferences();
|
||||
console.log('当前偏好设置:', currentPrefs);
|
||||
|
||||
// 测试时间检查
|
||||
const timeAllowed = await isNotificationTimeAllowed();
|
||||
console.log('当前时间是否允许通知:', timeAllowed);
|
||||
|
||||
// 测试锻炼类型检查
|
||||
const runningEnabled = await isWorkoutTypeEnabled('running');
|
||||
console.log('跑步通知是否启用:', runningEnabled);
|
||||
|
||||
// 临时修改设置
|
||||
await saveWorkoutNotificationPreferences({
|
||||
enabled: true,
|
||||
startTimeHour: 9,
|
||||
endTimeHour: 21,
|
||||
enabledWorkoutTypes: ['running', 'cycling']
|
||||
});
|
||||
|
||||
console.log('临时设置已保存');
|
||||
|
||||
// 恢复原始设置
|
||||
await saveWorkoutNotificationPreferences(currentPrefs);
|
||||
console.log('原始设置已恢复');
|
||||
|
||||
console.log('✅ 偏好设置功能测试完成');
|
||||
} catch (error) {
|
||||
console.error('❌ 偏好设置功能测试失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行完整的测试套件
|
||||
*/
|
||||
static async runFullTestSuite(): Promise<void> {
|
||||
console.log('🧪 开始运行锻炼监听功能完整测试套件');
|
||||
|
||||
try {
|
||||
// 1. 检查服务状态
|
||||
console.log('\n1. 检查服务状态...');
|
||||
const status = this.getWorkoutMonitorStatus();
|
||||
console.log('服务状态:', status);
|
||||
|
||||
// 2. 测试偏好设置
|
||||
console.log('\n2. 测试偏好设置...');
|
||||
await this.testPreferences();
|
||||
|
||||
// 3. 测试通知功能
|
||||
console.log('\n3. 测试通知功能...');
|
||||
await this.testWorkoutNotifications();
|
||||
|
||||
// 4. 测试手动触发
|
||||
console.log('\n4. 测试手动触发...');
|
||||
await this.simulateWorkoutCompletion();
|
||||
|
||||
console.log('\n🎉 完整测试套件运行完成!');
|
||||
} catch (error) {
|
||||
console.error('\n❌ 测试套件运行失败:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开发者调试函数
|
||||
* 可以在开发者控制台中调用
|
||||
*/
|
||||
declare global {
|
||||
interface Window {
|
||||
testWorkoutNotifications: () => Promise<void>;
|
||||
testWorkoutPreferences: () => Promise<void>;
|
||||
simulateWorkoutCompletion: () => Promise<void>;
|
||||
runWorkoutTestSuite: () => Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
// 在开发环境中暴露调试函数
|
||||
if (__DEV__) {
|
||||
// 这些函数可以在开发者控制台中调用
|
||||
// 例如: window.testWorkoutNotifications()
|
||||
|
||||
// 注意:这些函数需要在实际运行环境中绑定
|
||||
// 可以在应用的初始化代码中添加:
|
||||
// window.testWorkoutNotifications = WorkoutTestHelper.testWorkoutNotifications;
|
||||
}
|
||||
Reference in New Issue
Block a user