import dayjs from 'dayjs'; import { AppState, AppStateStatus, NativeModules } from 'react-native'; import { SimpleEventEmitter } from './SimpleEventEmitter'; type HealthDataOptions = { startDate: string; endDate: string; limit?: number; }; // 锻炼数据类型定义 export interface WorkoutData { id: string; startDate: string; endDate: string; duration: number; // 秒 workoutActivityType: number; workoutActivityTypeString: string; totalEnergyBurned?: number; // 千卡 totalDistance?: number; // 米 averageHeartRate?: number; source: { name: string; bundleIdentifier: string; }; metadata: Record; } export interface HeartRateSample { id: string; startDate: string; endDate: string; value: number; source?: { name: string; bundleIdentifier: string; }; metadata?: Record; } // 锻炼记录查询选项 export interface WorkoutOptions extends HealthDataOptions { limit?: number; // 默认10条 } // 锻炼活动类型枚举 export enum WorkoutActivityType { AmericanFootball = 1, Archery = 2, AustralianFootball = 3, Badminton = 4, Baseball = 5, Basketball = 6, Bowling = 7, Boxing = 8, Climbing = 9, Cricket = 10, CrossTraining = 11, Curling = 12, Cycling = 13, Dance = 14, DanceInspiredTraining = 15, Elliptical = 16, EquestrianSports = 17, Fencing = 18, Fishing = 19, FunctionalStrengthTraining = 20, Golf = 21, Gymnastics = 22, Handball = 23, Hiking = 24, Hockey = 25, Hunting = 26, Lacrosse = 27, MartialArts = 28, MindAndBody = 29, MixedMetabolicCardioTraining = 30, PaddleSports = 31, Play = 32, PreparationAndRecovery = 33, Racquetball = 34, Rowing = 35, Rugby = 36, Running = 37, Sailing = 38, SkatingSports = 39, SnowSports = 40, Soccer = 41, Softball = 42, Squash = 43, StairClimbing = 44, SurfingSports = 45, Swimming = 46, TableTennis = 47, Tennis = 48, TrackAndField = 49, TraditionalStrengthTraining = 50, Volleyball = 51, Walking = 52, WaterFitness = 53, WaterPolo = 54, WaterSports = 55, Wrestling = 56, Yoga = 57, Barre = 58, CoreTraining = 59, CrossCountrySkiing = 60, DownhillSkiing = 61, Flexibility = 62, HighIntensityIntervalTraining = 63, JumpRope = 64, Kickboxing = 65, Pilates = 66, Snowboarding = 67, Stairs = 68, StepTraining = 69, WheelchairWalkPace = 70, WheelchairRunPace = 71, TaiChi = 72, MixedCardio = 73, HandCycling = 74, DiscSports = 75, FitnessGaming = 76, CardioDance = 77, SocialDance = 78, Pickleball = 79, Cooldown = 80, SwimBikeRun = 82, Transition = 83, UnderwaterDiving = 84, Other = 3000 } // React Native bridge to native HealthKitManager const { HealthKitManager } = NativeModules; // HealthKit权限状态枚举 export enum HealthPermissionStatus { Unknown = 'unknown', Authorized = 'authorized', Denied = 'denied', NotDetermined = 'notDetermined' } // 权限状态管理类 class HealthPermissionManager extends SimpleEventEmitter { private permissionStatus: HealthPermissionStatus = HealthPermissionStatus.Unknown; private isChecking: boolean = false; private lastCheckTime: number = 0; private checkInterval: number = 5000; // 5秒检查间隔,避免频繁检查 private appStateSubscription: any = null; constructor() { super(); this.setupAppStateListener(); } // 设置应用状态监听 private setupAppStateListener() { this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange.bind(this)); } // 处理应用状态变化 private handleAppStateChange(nextAppState: AppStateStatus) { if (nextAppState === 'active') { // 应用回到前台时检查权限状态 console.log('应用回到前台,检查HealthKit权限状态...'); this.checkPermissionStatus(true); } } // 获取当前权限状态 public getPermissionStatus(): HealthPermissionStatus { return this.permissionStatus; } // 设置权限状态 private setPermissionStatus(status: HealthPermissionStatus, shouldEmit: boolean = true) { const oldStatus = this.permissionStatus; this.permissionStatus = status; if (shouldEmit && oldStatus !== status) { console.log(`HealthKit权限状态变化: ${oldStatus} -> ${status}`); this.emit('permissionStatusChanged', status, oldStatus); } } // 检查权限状态(通过尝试读取数据来间接判断) public async checkPermissionStatus(forceCheck: boolean = false): Promise { const now = Date.now(); // 避免频繁检查 if (!forceCheck && this.isChecking) { return this.permissionStatus; } if (!forceCheck && (now - this.lastCheckTime) < this.checkInterval) { return this.permissionStatus; } this.isChecking = true; this.lastCheckTime = now; try { // 尝试获取简单的步数数据来检测权限 const today = new Date(); const options = { startDate: dayjs(today).startOf('day').toDate().toISOString(), endDate: dayjs(today).endOf('day').toDate().toISOString() }; const result = await HealthKitManager.getStepCount(options); if (result && result.totalValue !== undefined) { // 能够获取数据,说明有权限 this.setPermissionStatus(HealthPermissionStatus.Authorized); } else if (result && result.error) { // 有错误返回,可能是权限被拒绝 this.setPermissionStatus(HealthPermissionStatus.Denied); } else { // 其他情况 this.setPermissionStatus(HealthPermissionStatus.Unknown); } } catch (error) { console.log('HealthKit权限检查失败,可能是权限被拒绝:', error); this.setPermissionStatus(HealthPermissionStatus.Denied); } finally { this.isChecking = false; } return this.permissionStatus; } // 请求权限 public async requestPermission(): Promise { try { console.log('开始请求HealthKit权限...'); const result = await HealthKitManager.requestAuthorization(); if (result && result.success) { console.log('HealthKit权限请求成功'); this.setPermissionStatus(HealthPermissionStatus.Authorized); // 权限获取成功后触发数据刷新事件 this.emit('permissionGranted'); return true; } else { console.error('HealthKit权限请求失败'); this.setPermissionStatus(HealthPermissionStatus.Denied); return false; } } catch (error) { console.error('HealthKit权限请求出现异常:', error); this.setPermissionStatus(HealthPermissionStatus.Denied); return false; } } // 清理资源 public destroy() { if (this.appStateSubscription) { this.appStateSubscription.remove(); } this.removeAllListeners(); } } // 全局权限管理实例 export const healthPermissionManager = new HealthPermissionManager(); // Interface for activity summary data from HealthKit export interface HealthActivitySummary { activeEnergyBurned: number; activeEnergyBurnedGoal: number; appleExerciseTime: number; appleExerciseTimeGoal: number; appleStandHours: number; appleStandHoursGoal: number; dateComponents: { day: number; month: number; year: number; }; } // const PERMISSIONS: HealthKitPermissions = { // permissions: { // read: [ // AppleHealthKit.Constants.Permissions.StepCount, // AppleHealthKit.Constants.Permissions.ActiveEnergyBurned, // AppleHealthKit.Constants.Permissions.BasalEnergyBurned, // AppleHealthKit.Constants.Permissions.SleepAnalysis, // AppleHealthKit.Constants.Permissions.HeartRateVariability, // AppleHealthKit.Constants.Permissions.ActivitySummary, // AppleHealthKit.Constants.Permissions.OxygenSaturation, // AppleHealthKit.Constants.Permissions.HeartRate, // AppleHealthKit.Constants.Permissions.Water, // // 添加 Apple Exercise Time 和 Apple Stand Time 权限 // AppleHealthKit.Constants.Permissions.AppleExerciseTime, // AppleHealthKit.Constants.Permissions.AppleStandTime, // ], // write: [ // // 支持体重写入 // AppleHealthKit.Constants.Permissions.Weight, // // 支持饮水量写入 // AppleHealthKit.Constants.Permissions.Water, // ], // }, // }; export type HourlyStepData = { hour: number; // 0-23 steps: number; }; export type HourlyActivityData = { hour: number; // 0-23 calories: number; // 活动热量 }; export type HourlyExerciseData = { hour: number; // 0-23 minutes: number; // 锻炼分钟数 }; export type HourlyStandData = { hour: number; // 0-23 hasStood: number; // 1表示该小时有站立,0表示没有 }; export type TodayHealthData = { activeEnergyBurned: number; // kilocalories // 健身圆环数据 activeCalories: number; activeCaloriesGoal: number; exerciseMinutes: number; exerciseMinutesGoal: number; standHours: number; standHoursGoal: number; heartRate: number | null; }; // 更新:使用新的权限管理系统 export async function ensureHealthPermissions(): Promise { return await healthPermissionManager.requestPermission(); } // 获取当前权限状态 export function getHealthPermissionStatus(): HealthPermissionStatus { return healthPermissionManager.getPermissionStatus(); } // 检查权限状态 export async function checkHealthPermissionStatus(forceCheck: boolean = false): Promise { return await healthPermissionManager.checkPermissionStatus(forceCheck); } // 日期工具函数 function createDateRange(date: Date): HealthDataOptions { return { startDate: dayjs(date).startOf('day').toDate().toISOString(), endDate: dayjs(date).endOf('day').toDate().toISOString() }; } // Note: createSleepDateRange and calculateSleepDuration functions removed as unused // 通用错误处理 function logError(operation: string, error: any): void { console.error(`获取${operation}失败:`, error); } function logWarning(operation: string, message: string): void { console.warn(`${operation}数据${message}`); } function logSuccess(operation: string, data: any): void { console.log(`${operation}数据:`, data); } // 数值验证和转换 function validateOxygenSaturation(value: any): number | null { if (value === undefined || value === null) return null; let numValue = Number(value); // 如果值小于1,可能是小数形式(0.0-1.0),需要转换为百分比 if (numValue > 0 && numValue < 1) { numValue = numValue * 100; } // 血氧饱和度通常在0-100之间,验证数据有效性 if (numValue >= 0 && numValue <= 100) { return Number(numValue.toFixed(1)); } console.warn('血氧饱和度数据异常:', numValue); return null; } function validateHeartRate(value: any): number | null { if (value === undefined || value === null) return null; const numValue = Number(value); // 心率通常在30-200之间,验证数据有效性 if (numValue >= 30 && numValue <= 200) { return Math.round(numValue); } console.warn('心率数据异常:', numValue); return null; } function validateHRVValue(value: any): number | null { if (value === undefined || value === null) return null; const numValue = Number(value); // HRV SDNN 正常范围检查 // 正常范围: 18-76ms,但允许更宽范围 5-150ms 以包含边缘情况 if (numValue >= 5 && numValue <= 150) { // 保留1位小数的精度,避免过度舍入 return Math.round(numValue * 10) / 10; } // 记录异常值用于调试 console.warn('HRV数据超出合理范围:', { value: numValue, expectedRange: '5-150ms', normalRange: '18-76ms' }); return null; } // 健康数据获取函数 export async function fetchStepCount(date: Date): Promise { try { const options = createDateRange(date); const result = await HealthKitManager.getStepCount(options); if (result && result.totalValue !== undefined) { logSuccess('步数', result); return Math.round(result.totalValue); } else { logWarning('步数', '为空或格式错误'); return 0; } } catch (error) { logError('步数', error); return 0; } } // 使用样本数据获取每小时步数 - 优化版本,减少计算复杂度 export async function fetchHourlyStepSamples(date: Date): Promise { try { const options = createDateRange(date); const result = await HealthKitManager.getDailyStepCountSamples(options); if (result && result.data && Array.isArray(result.data)) { logSuccess('每小时步数样本', result); // 优化:使用更高效的数据结构 const hourlyMap = new Map(); // 优化:批量处理数据,减少重复验证 result.data.forEach((sample: any) => { if (sample?.hour >= 0 && sample?.hour < 24 && sample?.value !== undefined) { hourlyMap.set(sample.hour, Math.round(sample.value)); } }); // 生成最终数组 const hourlyData: HourlyStepData[] = []; for (let i = 0; i < 24; i++) { hourlyData.push({ hour: i, steps: hourlyMap.get(i) || 0 }); } return hourlyData; } else { logWarning('每小时步数', '为空或格式错误'); return Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })); } } catch (error) { logError('每小时步数样本', error); return Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })); } } // 获取每小时活动热量数据 async function fetchHourlyActiveCalories(date: Date): Promise { try { const options = createDateRange(date); const result = await HealthKitManager.getHourlyActiveEnergyBurned(options); if (result && result.data && Array.isArray(result.data)) { logSuccess('每小时活动热量', result); // 初始化24小时数据 const hourlyData: HourlyActivityData[] = Array.from({ length: 24 }, (_, i) => ({ hour: i, calories: 0 })); // 将API返回的数据映射到对应的小时 result.data.forEach((sample: any) => { if (sample && sample.hour !== undefined && sample.value !== undefined) { const hour = sample.hour; if (hour >= 0 && hour < 24) { hourlyData[hour].calories = Math.round(sample.value); } } }); return hourlyData; } else { logWarning('每小时活动热量', '为空或格式错误'); return Array.from({ length: 24 }, (_, i) => ({ hour: i, calories: 0 })); } } catch (error) { logError('每小时活动热量', error); return Array.from({ length: 24 }, (_, i) => ({ hour: i, calories: 0 })); } } // 获取每小时锻炼分钟数据 async function fetchHourlyExerciseMinutes(date: Date): Promise { try { const options = createDateRange(date); const result = await HealthKitManager.getHourlyExerciseTime(options); if (result && result.data && Array.isArray(result.data)) { logSuccess('每小时锻炼分钟', result); // 初始化24小时数据 const hourlyData: HourlyExerciseData[] = Array.from({ length: 24 }, (_, i) => ({ hour: i, minutes: 0 })); // 将API返回的数据映射到对应的小时 result.data.forEach((sample: any) => { if (sample && sample.hour !== undefined && sample.value !== undefined) { const hour = sample.hour; if (hour >= 0 && hour < 24) { hourlyData[hour].minutes = Math.round(sample.value); } } }); return hourlyData; } else { logWarning('每小时锻炼分钟', '为空或格式错误'); return Array.from({ length: 24 }, (_, i) => ({ hour: i, minutes: 0 })); } } catch (error) { logError('每小时锻炼分钟', error); return Array.from({ length: 24 }, (_, i) => ({ hour: i, minutes: 0 })); } } // 获取每小时站立小时数据 async function fetchHourlyStandHours(date: Date): Promise { try { const options = createDateRange(date); const result = await HealthKitManager.getHourlyStandHours(options); if (result && result.data && Array.isArray(result.data)) { logSuccess('每小时站立数据', result); // 初始化24小时数据 const hourlyData: number[] = Array.from({ length: 24 }, () => 0); // 将API返回的数据映射到对应的小时 result.data.forEach((sample: any) => { if (sample && sample.hour !== undefined && sample.value !== undefined) { const hour = sample.hour; if (hour >= 0 && hour < 24) { hourlyData[hour] = sample.value; } } }); return hourlyData; } else { logWarning('每小时站立数据', '为空或格式错误'); return Array.from({ length: 24 }, () => 0); } } catch (error) { logError('每小时站立数据', error); return Array.from({ length: 24 }, () => 0); } } async function fetchActiveEnergyBurned(options: HealthDataOptions): Promise { try { const result = await HealthKitManager.getActiveEnergyBurned(options); if (result && result.totalValue !== undefined) { logSuccess('消耗卡路里', result); return result.totalValue; } else { logWarning('卡路里', '为空或格式错误'); return 0; } } catch (error) { logError('消耗卡路里', error); return 0; } } export async function fetchBasalEnergyBurned(options: HealthDataOptions): Promise { try { const result = await HealthKitManager.getBasalEnergyBurned(options); if (result && result.totalValue !== undefined) { logSuccess('基础代谢', result); return result.totalValue; } else { logWarning('基础代谢', '为空或格式错误'); return 0; } } catch (error) { logError('基础代谢', error); return 0; } } async function fetchHeartRateVariability(options: HealthDataOptions): Promise { try { console.log('=== 开始获取HRV数据 ==='); console.log('查询选项:', options); const result = await HealthKitManager.getHeartRateVariabilitySamples(options); console.log('HRV API调用结果:', result); if (result && result.data && Array.isArray(result.data) && result.data.length > 0) { let selectedSample: any = null; let bestQuality = -1; console.log(`获取到 ${result.data.length} 个HRV样本`); // 首先尝试使用最佳质量值 if (result.bestQualityValue && typeof result.bestQualityValue === 'number') { const qualityValue = validateHRVValue(result.bestQualityValue); if (qualityValue !== null) { // 找到质量分数最高的样本 for (const sample of result.data) { const sampleQuality = sample.qualityScore || 0; const sampleValue = validateHRVValue(sample.value); if (sampleValue !== null && sampleQuality > bestQuality) { bestQuality = sampleQuality; selectedSample = sample; } } if (selectedSample) { logSuccess('HRV数据(最佳质量)', { value: qualityValue, qualityScore: bestQuality, totalSamples: result.data.length, recordedAt: selectedSample.endDate }); } } } // 如果没有找到最佳质量样本,或者最佳质量值无效,重新评估所有样本 if (!selectedSample) { console.log('重新评估所有样本以找到最佳数据...'); // 按质量分数、手动测量标志和时间排序 const sortedSamples = result.data.sort((a: any, b: any) => { const qualityA = a.qualityScore || 0; const qualityB = b.qualityScore || 0; const isManualA = a.isManualMeasurement || false; const isManualB = b.isManualMeasurement || false; // 手动测量优先 if (isManualA && !isManualB) return -1; if (!isManualA && isManualB) return 1; // 质量分数优先 if (qualityA !== qualityB) return qualityB - qualityA; // 时间优先(最新的优先) const dateA = new Date(a.endDate || a.startDate).getTime(); const dateB = new Date(b.endDate || b.startDate).getTime(); return dateB - dateA; }); // 选择第一个有效样本 for (const sample of sortedSamples) { const sampleValue = validateHRVValue(sample.value); if (sampleValue !== null) { selectedSample = sample; bestQuality = sample.qualityScore || 0; console.log('选择最佳HRV样本:', { value: sampleValue, qualityScore: bestQuality, isManual: sample.isManualMeasurement, source: sample.source?.name, recordedAt: sample.endDate }); break; } } } // 构建完整的HRV数据对象 if (selectedSample) { const validatedValue = validateHRVValue(selectedSample.value); if (validatedValue !== null) { const hrvData: HRVData = { value: validatedValue, recordedAt: selectedSample.startDate, endDate: selectedSample.endDate, source: { name: selectedSample.source?.name || 'Unknown', bundleIdentifier: selectedSample.source?.bundleIdentifier || '' }, isManualMeasurement: selectedSample.isManualMeasurement || false, qualityScore: selectedSample.qualityScore, sampleId: selectedSample.id }; logSuccess('HRV完整数据', hrvData); return hrvData; } } } logWarning('HRV', '为空或格式错误'); console.warn('HRV数据为空或无效,原始响应:', result); return null; } catch (error) { logError('HRV数据', error); console.error('HRV获取错误详情:', error); return null; } } async function fetchActivitySummary(options: HealthDataOptions): Promise { try { const result = await HealthKitManager.getActivitySummary(options); if (result && Array.isArray(result) && result.length > 0) { logSuccess('ActivitySummary', result[0]); return result[0]; } else { logWarning('ActivitySummary', '为空'); return null; } } catch (error) { logError('ActivitySummary', error); return null; } } export async function fetchOxygenSaturation(options: HealthDataOptions): Promise { try { const result = await HealthKitManager.getOxygenSaturationSamples(options); if (result && result.data && Array.isArray(result.data) && result.data.length > 0) { logSuccess('血氧饱和度', result); const latestOxygen = result.data[result.data.length - 1]; return validateOxygenSaturation(latestOxygen?.value); } else { logWarning('血氧饱和度', '为空或格式错误'); return null; } } catch (error) { logError('血氧饱和度', error); return null; } } export async function fetchHeartRateSamplesForRange( startDate: Date, endDate: Date, limit: number = 500 ): Promise { try { const options = { startDate: dayjs(startDate).toISOString(), endDate: dayjs(endDate).toISOString(), limit, }; const result = await HealthKitManager.getHeartRateSamples(options); if (result && Array.isArray(result.data)) { const samples: HeartRateSample[] = result.data .filter((sample: any) => sample && typeof sample.value === 'number' && !Number.isNaN(sample.value)) .map((sample: any) => ({ id: sample.id, startDate: sample.startDate, endDate: sample.endDate, value: Number(sample.value), source: sample.source, metadata: sample.metadata, })); logSuccess('锻炼心率采样', { count: samples.length, startDate: options.startDate, endDate: options.endDate }); return samples; } logWarning('锻炼心率采样', '为空或格式错误'); return []; } catch (error) { logError('锻炼心率采样', error); return []; } } async function fetchHeartRate(options: HealthDataOptions): Promise { try { const result = await HealthKitManager.getHeartRateSamples(options); if (result && result.data && Array.isArray(result.data) && result.data.length > 0) { logSuccess('心率', result); const latestHeartRate = result.data[result.data.length - 1]; return validateHeartRate(latestHeartRate?.value); } else { logWarning('心率', '为空或格式错误'); return null; } } catch (error) { logError('心率', error); return null; } } // 获取指定时间范围内的最大心率 export async function fetchMaximumHeartRate(_options: HealthDataOptions): Promise { try { // 暂未实现,返回null console.log('最大心率获取暂未实现'); return null; // const result = await HealthKitManager.getHeartRateSamples(options); // if (result && result.data && Array.isArray(result.data) && result.data.length > 0) { // // 从所有心率样本中找出最大值 // let maxHeartRate = 0; // let validSamplesCount = 0; // result.data.forEach((sample: any) => { // if (sample && sample.value !== undefined) { // const heartRate = validateHeartRate(sample.value); // if (heartRate !== null) { // maxHeartRate = Math.max(maxHeartRate, heartRate); // validSamplesCount++; // } // } // }); // if (validSamplesCount > 0 && maxHeartRate > 0) { // logSuccess('最大心率', { maxHeartRate, validSamplesCount }); // return maxHeartRate; // } else { // logWarning('最大心率', '没有找到有效的样本数据'); // return null; // } // } else { // logWarning('最大心率', '为空或格式错误'); // return null; // } } catch (error) { logError('最大心率', error); return null; } } // 默认健康数据 function getDefaultHealthData(): TodayHealthData { return { activeEnergyBurned: 0, activeCalories: 0, activeCaloriesGoal: 350, exerciseMinutes: 0, exerciseMinutesGoal: 30, standHours: 0, standHoursGoal: 12, heartRate: null, }; } export async function fetchHealthDataForDate(date: Date): Promise { try { console.log('开始获取指定日期健康数据...', date); const options = createDateRange(date); console.log('查询选项:', options); // 并行获取所有健康数据 const [ activeEnergyBurned, activitySummary, heartRate ] = await Promise.all([ fetchActiveEnergyBurned(options), fetchActivitySummary(options), fetchHeartRate(options) ]); return { activeEnergyBurned, activeCalories: Math.round(activitySummary?.activeEnergyBurned || 0), activeCaloriesGoal: Math.round(activitySummary?.activeEnergyBurnedGoal || 350), exerciseMinutes: Math.round(activitySummary?.appleExerciseTime || 0), exerciseMinutesGoal: Math.round(activitySummary?.appleExerciseTimeGoal || 30), standHours: Math.round(activitySummary?.appleStandHours || 0), standHoursGoal: Math.round(activitySummary?.appleStandHoursGoal || 12), heartRate }; } catch (error) { console.error('获取指定日期健康数据失败:', error); return getDefaultHealthData(); } } export async function fetchTodayHealthData(): Promise { return fetchHealthDataForDate(dayjs().toDate()); } export async function fetchHRVForDate(date: Date): Promise { console.log('开始获取指定日期HRV数据...', date); // 首先尝试获取指定日期的HRV数据 const options = createDateRange(date); const hrvData = await fetchHeartRateVariability(options); // 如果当天没有数据,尝试获取最近7天内的最新数据 if (!hrvData) { console.log('指定日期无HRV数据,尝试获取最近7天内的数据...'); const endDate = new Date(date); const startDate = new Date(date); startDate.setDate(startDate.getDate() - 7); // 往前推7天 const recentOptions = { startDate: startDate.toISOString(), endDate: endDate.toISOString() }; const recentHrvData = await fetchHeartRateVariability(recentOptions); if (recentHrvData) { console.log('获取到最近7天内的HRV数据:', recentHrvData); return recentHrvData; } else { console.log('最近7天内也无HRV数据'); } } return hrvData; } export async function fetchTodayHRV(): Promise { return fetchHRVForDate(dayjs().toDate()); } // 获取最近几小时内的实时HRV数据 export async function fetchRecentHRV(hoursBack: number = 2): Promise { console.log(`开始获取最近${hoursBack}小时内的HRV数据...`); const now = new Date(); const options = { startDate: dayjs(now).subtract(hoursBack, 'hour').toDate().toISOString(), endDate: now.toISOString() }; return fetchHeartRateVariability(options); } // 测试HRV数据获取功能 export async function testHRVDataFetch(date: Date = dayjs().toDate()): Promise { console.log('=== 开始测试HRV数据获取 ==='); try { // 首先确保权限 const hasPermission = await ensureHealthPermissions(); if (!hasPermission) { console.error('没有健康数据权限,无法测试HRV'); return; } console.log('权限检查通过,开始获取HRV数据...'); // 测试不同时间范围的HRV数据 const options = createDateRange(date); // 获取今日HRV(带详细分析) console.log('--- 测试今日HRV ---'); const result = await HealthKitManager.getHeartRateVariabilitySamples(options); console.log('原始HRV API响应:', result); if (result && result.data && Array.isArray(result.data)) { console.log(`获取到 ${result.data.length} 个HRV样本`); // 分析数据质量 result.data.forEach((sample: any, index: number) => { console.log(`样本 ${index + 1}:`, { value: sample.value, source: sample.source?.name, bundleId: sample.source?.bundleIdentifier, isManual: sample.isManualMeasurement, qualityScore: sample.qualityScore, startDate: sample.startDate, endDate: sample.endDate }); }); if (result.bestQualityValue !== undefined) { console.log('最佳质量HRV值:', result.bestQualityValue); } } // 使用优化后的方法获取HRV const todayHRV = await fetchHeartRateVariability(options); console.log('最终HRV结果:', todayHRV); // 获取最近2小时HRV console.log('--- 测试最近2小时HRV ---'); const recentHRV = await fetchRecentHRV(2); console.log('最近2小时HRV结果:', recentHRV); // 获取指定日期HRV console.log('--- 测试指定日期HRV ---'); const dateHRV = await fetchHRVForDate(date); console.log('指定日期HRV结果:', dateHRV); // 提供数据解释 if (todayHRV) { console.log('--- HRV数据解读 ---'); console.log(`HRV值: ${todayHRV.value}ms`); console.log(`记录时间: ${todayHRV.recordedAt}`); console.log(`数据来源: ${todayHRV.source.name}`); console.log(`手动测量: ${todayHRV.isManualMeasurement ? '是' : '否'}`); if (todayHRV.value >= 18 && todayHRV.value <= 76) { console.log('✅ HRV值在正常范围内 (18-76ms)'); } else if (todayHRV.value < 18) { console.log('⚠️ HRV值偏低,可能表示压力或疲劳状态'); } else if (todayHRV.value > 76) { console.log('📈 HRV值较高,通常表示良好的恢复状态'); } } console.log('=== HRV数据测试完成 ==='); } catch (error) { console.error('HRV测试过程中出现错误:', error); } } // === 个人健康数据读取和写入方法 === /** * 从 HealthKit 获取身高(单位:厘米) */ export async function fetchHeight(): Promise { try { console.log('开始从 HealthKit 获取身高...'); const result = await HealthKitManager.getHeight(); if (result && typeof result.value === 'number') { const heightInCm = Math.round(result.value); console.log('成功获取身高:', heightInCm, 'cm'); return heightInCm; } else { console.log('未找到身高数据'); return null; } } catch (error) { console.error('获取身高失败:', error); return null; } } /** * 从 HealthKit 获取体重(单位:千克) */ export async function fetchWeight(): Promise { try { console.log('开始从 HealthKit 获取体重...'); const result = await HealthKitManager.getWeight(); if (result && typeof result.value === 'number') { const weightInKg = Math.round(result.value * 10) / 10; // 保留1位小数 console.log('成功获取体重:', weightInKg, 'kg'); return weightInKg; } else { console.log('未找到体重数据'); return null; } } catch (error) { console.error('获取体重失败:', error); return null; } } /** * 从 HealthKit 获取出生日期 */ export async function fetchDateOfBirth(): Promise { try { console.log('开始从 HealthKit 获取出生日期...'); const result = await HealthKitManager.getDateOfBirth(); if (result && typeof result.value === 'string') { console.log('成功获取出生日期:', result.value); return result.value; } else { console.log('未找到出生日期数据'); return null; } } catch (error) { console.error('获取出生日期失败:', error); return null; } } /** * 保存身高到 HealthKit * @param heightInCm 身高(单位:厘米) * @param unit 单位,默认为 'cm'(厘米),也支持 'in'(英寸) */ export async function saveHeight(heightInCm: number, unit: 'cm' | 'in' = 'cm'): Promise { try { console.log('开始保存身高到 HealthKit...', { heightInCm, unit }); if (heightInCm <= 0) { console.error('身高值无效:', heightInCm); return false; } const options = { value: heightInCm, unit: unit }; const result = await HealthKitManager.saveHeight(options); if (result && result.success) { console.log('身高保存成功'); return true; } else { console.error('身高保存失败:', result); return false; } } catch (error) { console.error('保存身高到 HealthKit 失败:', error); return false; } } /** * 保存体重到 HealthKit * @param weightInKg 体重(单位:千克) */ export async function saveWeight(weightInKg: number): Promise { try { console.log('开始保存体重到 HealthKit...', { weightInKg }); if (weightInKg <= 0) { console.error('体重值无效:', weightInKg); return false; } const options = { value: weightInKg }; const result = await HealthKitManager.saveWeight(options); if (result && result.success) { console.log('体重保存成功'); return true; } else { console.error('体重保存失败:', result); return false; } } catch (error) { console.error('保存体重到 HealthKit 失败:', error); return false; } } // 保持向后兼容的旧函数名 export async function updateWeight(weight: number): Promise { return saveWeight(weight); } /** * 批量获取个人健康数据(身高、体重、出生日期) */ export async function fetchPersonalHealthData(): Promise<{ height: number | null; weight: number | null; dateOfBirth: string | null; }> { try { console.log('开始批量获取个人健康数据...'); const [height, weight, dateOfBirth] = await Promise.all([ fetchHeight(), fetchWeight(), fetchDateOfBirth() ]); console.log('个人健康数据获取完成:', { height, weight, dateOfBirth }); return { height, weight, dateOfBirth }; } catch (error) { console.error('批量获取个人健康数据失败:', error); return { height: null, weight: null, dateOfBirth: null }; } } export async function testOxygenSaturationData(_date: Date = dayjs().toDate()): Promise { console.log('=== 开始测试血氧饱和度数据获取 ==='); // const options = createDateRange(date); try { // const result = await HealthKitManager.getOxygenSaturationSamples(options); // console.log('原始血氧饱和度数据:', result); // if (!result || !result.data || !Array.isArray(result.data) || result.data.length === 0) { // console.warn('血氧饱和度数据为空'); // return; // } // // 分析所有数据样本 // result.data.forEach((sample: any, index: number) => { // console.log(`样本 ${index + 1}:`, { // value: sample.value, // valueType: typeof sample.value, // startDate: sample.startDate, // endDate: sample.endDate // }); // }); // // 获取最新的血氧饱和度值并验证 // const latestOxygen = result.data[result.data.length - 1]; // if (latestOxygen?.value !== undefined && latestOxygen?.value !== null) { // const processedValue = validateOxygenSaturation(latestOxygen.value); // console.log('处理前的值:', latestOxygen.value); // console.log('最终处理后的值:', processedValue); // console.log('数据有效性检查:', processedValue !== null ? '有效' : '无效'); // } // console.log('=== 血氧饱和度数据测试完成 ==='); } catch (error) { console.error('获取血氧饱和度失败:', error); } } // 添加饮水记录到 HealthKit export async function saveWaterIntakeToHealthKit(amount: number, recordedAt?: string): Promise { try { console.log('开始保存饮水记录到HealthKit...', { amount, recordedAt }); const options = { amount: amount, recordedAt: recordedAt || new Date().toISOString() }; const result = await HealthKitManager.saveWaterIntakeToHealthKit(options); if (result && result.success) { console.log('饮水记录保存成功:', result); return true; } else { console.error('饮水记录保存失败:', result); return false; } } catch (error) { console.error('添加饮水记录到 HealthKit 失败:', error); return false; } } // 添加蛋白质记录到 HealthKit export async function saveProteinToHealthKit(grams: number, recordedAt?: string): Promise { try { console.log('开始保存蛋白质记录到HealthKit...', { grams, recordedAt }); const options = { amount: grams, recordedAt: recordedAt || new Date().toISOString() }; const result = await HealthKitManager.saveProteinToHealthKit(options); if (result && result.success) { console.log('蛋白质记录保存成功:', result); return true; } else { console.error('蛋白质记录保存失败:', result); return false; } } catch (error) { console.error('添加蛋白质记录到 HealthKit 失败:', error); return false; } } // 添加脂肪记录到 HealthKit export async function saveFatToHealthKit(grams: number, recordedAt?: string): Promise { try { console.log('开始保存脂肪记录到HealthKit...', { grams, recordedAt }); const options = { amount: grams, recordedAt: recordedAt || new Date().toISOString() }; const result = await HealthKitManager.saveFatToHealthKit(options); if (result && result.success) { console.log('脂肪记录保存成功:', result); return true; } else { console.error('脂肪记录保存失败:', result); return false; } } catch (error) { console.error('添加脂肪记录到 HealthKit 失败:', error); return false; } } // 添加碳水化合物记录到 HealthKit export async function saveCarbohydratesToHealthKit(grams: number, recordedAt?: string): Promise { try { console.log('开始保存碳水化合物记录到HealthKit...', { grams, recordedAt }); const options = { amount: grams, recordedAt: recordedAt || new Date().toISOString() }; const result = await HealthKitManager.saveCarbohydratesToHealthKit(options); if (result && result.success) { console.log('碳水化合物记录保存成功:', result); return true; } else { console.error('碳水化合物记录保存失败:', result); return false; } } catch (error) { console.error('添加碳水化合物记录到 HealthKit 失败:', error); return false; } } // 批量保存营养数据到 HealthKit(蛋白质、脂肪和碳水化合物) export async function saveNutritionToHealthKit( nutrition: { proteinGrams?: number; fatGrams?: number; carbohydrateGrams?: number }, recordedAt?: string ): Promise<{ proteinSaved: boolean; fatSaved: boolean; carbohydrateSaved: boolean }> { try { console.log('开始批量保存营养数据到HealthKit...', { nutrition, recordedAt }); const results = await Promise.allSettled([ nutrition.proteinGrams && nutrition.proteinGrams > 0 ? saveProteinToHealthKit(nutrition.proteinGrams, recordedAt) : Promise.resolve(false), nutrition.fatGrams && nutrition.fatGrams > 0 ? saveFatToHealthKit(nutrition.fatGrams, recordedAt) : Promise.resolve(false), nutrition.carbohydrateGrams && nutrition.carbohydrateGrams > 0 ? saveCarbohydratesToHealthKit(nutrition.carbohydrateGrams, recordedAt) : Promise.resolve(false), ]); const proteinSaved = results[0].status === 'fulfilled' ? results[0].value : false; const fatSaved = results[1].status === 'fulfilled' ? results[1].value : false; const carbohydrateSaved = results[2].status === 'fulfilled' ? results[2].value : false; console.log('营养数据批量保存结果:', { proteinSaved, fatSaved, carbohydrateSaved }); return { proteinSaved, fatSaved, carbohydrateSaved }; } catch (error) { console.error('批量保存营养数据到 HealthKit 失败:', error); return { proteinSaved: false, fatSaved: false, carbohydrateSaved: false }; } } // 获取 HealthKit 中的饮水记录 export async function getWaterIntakeFromHealthKit(options: HealthDataOptions): Promise { try { console.log('开始从HealthKit获取饮水记录...', options); const result = await HealthKitManager.getWaterIntakeFromHealthKit(options); if (result && result.data && Array.isArray(result.data)) { console.log('成功获取饮水记录:', result); return result.data; } else { console.log('饮水记录为空或格式错误:', result); return []; } } catch (error) { console.error('获取 HealthKit 饮水记录失败:', error); return []; } } // 删除 HealthKit 中的饮水记录 // 注意: react-native-health 库可能不支持直接删除特定记录,这个功能可能需要手动实现或使用其他方法 export async function deleteWaterIntakeFromHealthKit(recordId: string, recordedAt: string): Promise { // HealthKit 通常不支持直接删除单条记录 // 这是一个占位函数,实际实现可能需要更复杂的逻辑 console.log('注意: HealthKit 通常不支持直接删除单条饮水记录'); console.log('记录信息:', { recordId, recordedAt }); // 返回 true 表示"成功"(但实际上可能没有真正删除) return Promise.resolve(true); } // 获取当前小时的站立状态 export async function getCurrentHourStandStatus(): Promise<{ hasStood: boolean; standHours: number; standHoursGoal: number }> { try { const currentHour = new Date().getHours(); console.log(`检查当前小时 ${currentHour} 的站立状态...`); // 获取今日健康数据 const todayHealthData = await fetchTodayHealthData(); return { hasStood: todayHealthData.standHours > currentHour - 1, // 如果站立小时数大于当前小时-1,说明当前小时已站立 standHours: todayHealthData.standHours, standHoursGoal: todayHealthData.standHoursGoal }; } catch (error) { console.error('获取当前小时站立状态失败:', error); return { hasStood: true, // 默认认为已站立,避免过度提醒 standHours: 0, standHoursGoal: 12 }; } } // === 专门为健身圆环详情页提供的独立函数 === // 精简的活动圆环数据类型,只包含必要字段 export type ActivityRingsData = { // 活动圆环数据(来自 getActivitySummary) activeEnergyBurned: number; // activeEnergyBurned activeEnergyBurnedGoal: number; // activeEnergyBurnedGoal appleExerciseTime: number; // appleExerciseTime (分钟) appleExerciseTimeGoal: number; // appleExerciseTimeGoal appleStandHours: number; // appleStandHours appleStandHoursGoal: number; // appleStandHoursGoal }; // 导出每小时活动热量数据获取函数 export async function fetchHourlyActiveCaloriesForDate(date: Date): Promise { return fetchHourlyActiveCalories(date); } // 导出每小时锻炼分钟数据获取函数 export async function fetchHourlyExerciseMinutesForDate(date: Date): Promise { return fetchHourlyExerciseMinutes(date); } // 导出每小时站立数据获取函数 export async function fetchHourlyStandHoursForDate(date: Date): Promise { const hourlyStandData = await fetchHourlyStandHours(date); return hourlyStandData.map((hasStood, hour) => ({ hour, hasStood })); } // 专门为活动圆环详情页获取精简的数据 export async function fetchActivityRingsForDate(date: Date): Promise { try { console.log('获取活动圆环数据...', date); const options = createDateRange(date); const activitySummary = await fetchActivitySummary(options); if (!activitySummary) { console.warn('ActivitySummary 数据为空'); return null; } // 直接使用 getActivitySummary 返回的字段名,与文档保持一致 return { activeEnergyBurned: Math.round(activitySummary.activeEnergyBurned || 0), activeEnergyBurnedGoal: Math.round(activitySummary.activeEnergyBurnedGoal || 350), appleExerciseTime: Math.round(activitySummary.appleExerciseTime || 0), appleExerciseTimeGoal: Math.round(activitySummary.appleExerciseTimeGoal || 30), appleStandHours: Math.round(activitySummary.appleStandHours || 0), appleStandHoursGoal: Math.round(activitySummary.appleStandHoursGoal || 12), }; } catch (error) { console.error('获取活动圆环数据失败:', error); return null; } } // === 权限管理工具函数 === // 初始化健康权限管理(应在应用启动时调用) export function initializeHealthPermissions() { console.log('初始化HealthKit权限管理系统...'); // 延迟检查权限状态,避免应用启动时的性能影响 setTimeout(() => { healthPermissionManager.checkPermissionStatus(true); }, 1000); } // 监听权限状态变化(用于组件外部使用) export function addHealthPermissionListener( event: 'permissionStatusChanged' | 'permissionGranted', listener: (...args: any[]) => void ) { healthPermissionManager.on(event, listener); } // 移除权限状态监听器 export function removeHealthPermissionListener( event: 'permissionStatusChanged' | 'permissionGranted', listener: (...args: any[]) => void ) { healthPermissionManager.off(event, listener); } // 清理权限管理资源(应在应用退出时调用) export function cleanupHealthPermissions() { console.log('清理HealthKit权限管理资源...'); healthPermissionManager.destroy(); } // 获取权限状态的可读文本 export function getPermissionStatusText(status: HealthPermissionStatus): string { switch (status) { case HealthPermissionStatus.Authorized: return '已授权'; case HealthPermissionStatus.Denied: return '已拒绝'; case HealthPermissionStatus.NotDetermined: return '未确定'; case HealthPermissionStatus.Unknown: default: return '未知'; } } // 检查是否需要显示权限请求UI export function shouldShowPermissionRequest(): boolean { const status = healthPermissionManager.getPermissionStatus(); return status === HealthPermissionStatus.NotDetermined || status === HealthPermissionStatus.Unknown; } // 检查是否权限被用户拒绝 export function isPermissionDenied(): boolean { const status = healthPermissionManager.getPermissionStatus(); return status === HealthPermissionStatus.Denied; } // HRV数据结构 export interface HRVData { value: number; recordedAt: string; // ISO string format endDate: string; // ISO string format source: { name: string; bundleIdentifier: string; }; isManualMeasurement: boolean; qualityScore?: number; sampleId?: string; } // HRV数据质量分析和解读 export interface HRVAnalysis { value: number; quality: 'excellent' | 'good' | 'fair' | 'poor'; interpretation: string; recommendations: string[]; dataSource: string; isManualMeasurement: boolean; recordedAt: string; } export function analyzeHRVData(hrvData: HRVData): HRVAnalysis { const { value: hrvValue, source, isManualMeasurement, recordedAt } = hrvData; const sourceName = source.name; let quality: HRVAnalysis['quality']; let interpretation: string; let recommendations: string[] = []; // 质量评估基于数值范围和数据来源 if (hrvValue >= 18 && hrvValue <= 76) { if (isManualMeasurement) { quality = 'excellent'; interpretation = 'HRV值在正常范围内,且来自高质量测量'; } else { quality = 'good'; interpretation = 'HRV值在正常范围内'; } } else if (hrvValue >= 10 && hrvValue < 18) { quality = 'fair'; interpretation = 'HRV值偏低,可能表示压力、疲劳或恢复不足'; recommendations.push('考虑增加休息和恢复时间'); recommendations.push('评估近期的压力水平和睡眠质量'); } else if (hrvValue > 76 && hrvValue <= 100) { quality = isManualMeasurement ? 'excellent' : 'good'; interpretation = 'HRV值较高,通常表示良好的心血管健康和恢复状态'; recommendations.push('保持当前的生活方式和训练强度'); } else if (hrvValue < 10) { quality = 'poor'; interpretation = 'HRV值异常低,建议关注身体状态或数据准确性'; recommendations.push('建议使用手动测量(如呼吸应用)获得更准确的数据'); recommendations.push('如持续偏低,建议咨询医疗专业人士'); } else if (hrvValue > 100) { quality = 'fair'; interpretation = 'HRV值异常高,可能需要验证数据准确性'; recommendations.push('建议重复测量确认数据准确性'); } else { quality = 'poor'; interpretation = 'HRV数据超出预期范围'; recommendations.push('建议使用标准化的测量方法'); } // 根据数据来源添加建议 if (!isManualMeasurement) { recommendations.push('推荐使用呼吸应用进行手动HRV测量以获得更准确的数据'); } return { value: hrvValue, quality, interpretation, recommendations, dataSource: sourceName, isManualMeasurement, recordedAt }; } // 获取HRV数据并提供分析 export async function fetchHRVWithAnalysis(date: Date): Promise<{ hrvData: HRVData | null; analysis: HRVAnalysis | null }> { try { const hrvData = await fetchHRVForDate(date); if (hrvData) { const analysis = analyzeHRVData(hrvData); return { hrvData, analysis }; } return { hrvData: null, analysis: null }; } catch (error) { console.error('获取HRV分析数据失败:', error); return { hrvData: null, analysis: null }; } } // 智能HRV数据获取 - 优先获取实时数据,如果没有则获取历史数据 export async function fetchSmartHRVData(date: Date): Promise { console.log('开始智能HRV数据获取...', date); try { // 1. 首先尝试获取最近2小时的实时数据 console.log('1. 尝试获取最近2小时的实时HRV数据...'); const recentHRV = await fetchRecentHRV(2); if (recentHRV) { console.log('✅ 成功获取到实时HRV数据:', recentHRV); // 检查数据是否足够新(1小时内) const dataTime = new Date(recentHRV.recordedAt).getTime(); const now = Date.now(); const oneHour = 60 * 60 * 1000; if (now - dataTime <= oneHour) { console.log('✅ 实时数据足够新,直接使用'); return recentHRV; } else { console.log('⚠️ 实时数据较旧,继续寻找更好的数据'); } } // 2. 如果没有实时数据或数据太旧,尝试获取当天的数据 console.log('2. 尝试获取当天的HRV数据...'); const todayHRV = await fetchHRVForDate(date); if (todayHRV) { console.log('✅ 成功获取到当天HRV数据:', todayHRV); return todayHRV; } // 3. 如果当天没有数据,尝试获取最近3天的数据 console.log('3. 尝试获取最近3天的HRV数据...'); const endDate = new Date(date); const startDate = new Date(date); startDate.setDate(startDate.getDate() - 3); const recentOptions = { startDate: startDate.toISOString(), endDate: endDate.toISOString() }; const recentData = await fetchHeartRateVariability(recentOptions); if (recentData) { console.log('✅ 成功获取到最近3天的HRV数据:', recentData); return recentData; } // 4. 如果仍然没有数据,返回null console.log('❌ 未找到任何HRV数据'); return null; } catch (error) { console.error('智能HRV数据获取失败:', error); return null; } } // === 锻炼记录相关方法 === // 获取最近锻炼记录 export async function fetchRecentWorkouts(options?: Partial): Promise { try { console.log('开始获取最近锻炼记录...', options); // 设置默认选项 const defaultOptions: WorkoutOptions = { startDate: dayjs().subtract(30, 'day').startOf('day').toISOString(), endDate: dayjs().endOf('day').toISOString(), limit: 10 }; const finalOptions = { ...defaultOptions, ...options }; const result = await HealthKitManager.getRecentWorkouts(finalOptions); if (result && result.data && Array.isArray(result.data)) { logSuccess('锻炼记录', result); // 验证和处理返回的数据 const validatedWorkouts: WorkoutData[] = result.data .filter((workout: any) => { // 基本数据验证 return workout && workout.id && workout.startDate && workout.endDate && workout.duration !== undefined; }) .map((workout: any) => ({ id: workout.id, startDate: workout.startDate, endDate: workout.endDate, duration: workout.duration, workoutActivityType: workout.workoutActivityType || 0, workoutActivityTypeString: workout.workoutActivityTypeString || 'unknown', totalEnergyBurned: workout.totalEnergyBurned, totalDistance: workout.totalDistance, averageHeartRate: workout.averageHeartRate, source: { name: workout.source?.name || 'Unknown', bundleIdentifier: workout.source?.bundleIdentifier || '' }, metadata: workout.metadata || {} })); console.log(`成功获取 ${validatedWorkouts.length} 条锻炼记录`); return validatedWorkouts; } else { logWarning('锻炼记录', '为空或格式错误'); return []; } } catch (error) { logError('锻炼记录', error); return []; } } // 获取指定日期范围内的锻炼记录 export async function fetchWorkoutsForDateRange( startDate: Date, endDate: Date, limit: number = 10 ): Promise { const options: WorkoutOptions = { startDate: dayjs(startDate).startOf('day').toISOString(), endDate: dayjs(endDate).endOf('day').toISOString(), limit }; return fetchRecentWorkouts(options); } // 获取今日锻炼记录 export async function fetchTodayWorkouts(): Promise { const today = dayjs(); return fetchWorkoutsForDateRange(today.toDate(), today.toDate(), 20); } // 获取本周锻炼记录 export async function fetchThisWeekWorkouts(): Promise { const today = dayjs(); const startOfWeek = today.startOf('week'); return fetchWorkoutsForDateRange(startOfWeek.toDate(), today.toDate(), 50); } // 获取本月锻炼记录 export async function fetchThisMonthWorkouts(): Promise { const today = dayjs(); const startOfMonth = today.startOf('month'); return fetchWorkoutsForDateRange(startOfMonth.toDate(), today.toDate(), 100); } // 根据锻炼类型筛选锻炼记录 export function filterWorkoutsByType( workouts: WorkoutData[], workoutType: WorkoutActivityType ): WorkoutData[] { return workouts.filter(workout => workout.workoutActivityType === workoutType); } // 获取锻炼统计信息 export function getWorkoutStatistics(workouts: WorkoutData[]): { totalWorkouts: number; totalDuration: number; // 秒 totalEnergyBurned: number; // 千卡 totalDistance: number; // 米 averageDuration: number; // 秒 workoutTypes: Record; // 各类型锻炼次数 } { const stats = { totalWorkouts: workouts.length, totalDuration: 0, totalEnergyBurned: 0, totalDistance: 0, averageDuration: 0, workoutTypes: {} as Record }; workouts.forEach(workout => { stats.totalDuration += workout.duration; stats.totalEnergyBurned += workout.totalEnergyBurned || 0; stats.totalDistance += workout.totalDistance || 0; // 统计锻炼类型 const typeString = workout.workoutActivityTypeString; stats.workoutTypes[typeString] = (stats.workoutTypes[typeString] || 0) + 1; }); if (stats.totalWorkouts > 0) { stats.averageDuration = Math.round(stats.totalDuration / stats.totalWorkouts); } return stats; } // 格式化锻炼持续时间 export function formatWorkoutDuration(durationInSeconds: number): string { const hours = Math.floor(durationInSeconds / 3600); const minutes = Math.floor((durationInSeconds % 3600) / 60); const seconds = durationInSeconds % 60; if (hours > 0) { return `${hours}小时${minutes}分钟`; } else if (minutes > 0) { return `${minutes}分钟${seconds}秒`; } else { return `${seconds}秒`; } } // 格式化锻炼距离 export function formatWorkoutDistance(distanceInMeters: number): string { if (distanceInMeters >= 1000) { return `${(distanceInMeters / 1000).toFixed(2)}公里`; } else { return `${Math.round(distanceInMeters)}米`; } } const WORKOUT_TYPE_LABELS: Record = { running: '跑步', walking: '步行', cycling: '骑行', swimming: '游泳', yoga: '瑜伽', pilates: '普拉提', functionalstrengthtraining: '功能性力量训练', traditionalstrengthtraining: '传统力量训练', crosstraining: '交叉训练', mixedcardio: '混合有氧', highintensityintervaltraining: '高强度间歇训练', flexibility: '柔韧性训练', cooldown: '放松运动', dance: '舞蹈', danceinspiredtraining: '舞蹈训练', cardiodance: '有氧舞蹈', socialdance: '社交舞', swimbikerun: '铁人三项', transition: '项目转换', underwaterdiving: '水下潜水', pickleball: '匹克球', americanfootball: '美式橄榄球', australianfootball: '澳式橄榄球', archery: '射箭', badminton: '羽毛球', baseball: '棒球', basketball: '篮球', bowling: '保龄球', boxing: '拳击', climbing: '攀岩', cricket: '板球', curling: '冰壶', elliptical: '椭圆机', equestriansports: '马术', fencing: '击剑', fishing: '钓鱼', golf: '高尔夫', gymnastics: '体操', handball: '手球', hiking: '徒步', hockey: '曲棍球', hunting: '狩猎', lacrosse: '长曲棍球', martialarts: '武术', mindandbody: '身心运动', mixedmetaboliccardiotraining: '混合代谢有氧训练', paddlesports: '桨类运动', play: '自由活动', preparationandrecovery: '准备与恢复', racquetball: '壁球', rowing: '划船', rugby: '橄榄球', sailing: '帆船', skatingsports: '滑冰运动', snowsports: '雪上运动', soccer: '足球', softball: '垒球', squash: '壁球', stairclimbing: '爬楼梯', surfing: '冲浪', surfingsports: '冲浪运动', tabletennis: '乒乓球', tennis: '网球', trackandfield: '田径', volleyball: '排球', waterfitness: '水中健身', watersports: '水上运动', weighttraining: '重量训练', wrestling: '摔跤', barre: '芭蕾杆训练', corebTraining: '核心训练', jumprope: '跳绳', kickboxing: '踢拳', taichi: '太极', taichichuan: '太极拳', nordicwalking: '北欧式行走', frisbee: '飞盘', ultimatefrisbee: '极限飞盘', mountainbiking: '山地自行车', roadcycling: '公路骑行', virtualrunning: '虚拟跑步', virtualcycling: '虚拟骑行', trailrunning: '越野跑', treadmillrunning: '跑步机跑步', trackrunning: '场地跑', openwaterswimming: '公开水域游泳', poolswimming: '游泳池游泳', apneadiving: '自由潜', functionalStrengthTraining: '功能性力量训练', other: '其他运动', }; function humanizeWorkoutTypeKey(raw: string | undefined): string { if (!raw) { return '其他运动'; } const cleaned = raw .replace(/^HKWorkoutActivityType/i, '') .replace(/[_\-]+/g, ' ') .trim(); if (!cleaned) { return '其他运动'; } const withSpaces = cleaned.replace(/([a-z0-9])([A-Z])/g, '$1 $2'); const words = withSpaces .split(/\s+/) .filter(Boolean) .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()); return words.join(' '); } // 获取锻炼类型的显示名称 export function getWorkoutTypeDisplayName(workoutType: WorkoutActivityType | string): string { if (typeof workoutType === 'string') { const normalized = workoutType.replace(/\s+/g, '').toLowerCase(); return WORKOUT_TYPE_LABELS[normalized] || humanizeWorkoutTypeKey(workoutType); } switch (workoutType) { case WorkoutActivityType.Running: return '跑步'; case WorkoutActivityType.Cycling: return '骑行'; case WorkoutActivityType.Walking: return '步行'; case WorkoutActivityType.Swimming: return '游泳'; case WorkoutActivityType.Yoga: return '瑜伽'; case WorkoutActivityType.FunctionalStrengthTraining: return '功能性力量训练'; case WorkoutActivityType.TraditionalStrengthTraining: return '传统力量训练'; case WorkoutActivityType.CrossTraining: return '交叉训练'; case WorkoutActivityType.MixedCardio: return '混合有氧'; case WorkoutActivityType.HighIntensityIntervalTraining: return '高强度间歇训练'; case WorkoutActivityType.Flexibility: return '柔韧性训练'; case WorkoutActivityType.Cooldown: return '放松运动'; case WorkoutActivityType.Tennis: return '网球'; case WorkoutActivityType.Basketball: return '篮球'; case WorkoutActivityType.Soccer: return '足球'; case WorkoutActivityType.Baseball: return '棒球'; case WorkoutActivityType.Volleyball: return '排球'; case WorkoutActivityType.Dance: return '舞蹈'; case WorkoutActivityType.DanceInspiredTraining: return '舞蹈训练'; case WorkoutActivityType.Elliptical: return '椭圆机'; case WorkoutActivityType.Rowing: return '划船'; case WorkoutActivityType.StairClimbing: return '爬楼梯'; case WorkoutActivityType.Hiking: return '徒步'; case WorkoutActivityType.Climbing: return '攀岩'; case WorkoutActivityType.MindAndBody: return '身心运动'; case WorkoutActivityType.MartialArts: return '武术'; case WorkoutActivityType.Golf: return '高尔夫'; case WorkoutActivityType.Boxing: return '拳击'; case WorkoutActivityType.SnowSports: return '雪上运动'; case WorkoutActivityType.SurfingSports: return '冲浪运动'; case WorkoutActivityType.WaterFitness: return '水中健身'; case WorkoutActivityType.Other: return '其他运动'; default: return humanizeWorkoutTypeKey(WorkoutActivityType[workoutType]); } } // 测试锻炼记录获取功能 export async function testWorkoutDataFetch(): Promise { console.log('=== 开始测试锻炼记录获取 ==='); try { // 确保权限 const hasPermission = await ensureHealthPermissions(); if (!hasPermission) { console.error('没有健康数据权限,无法测试锻炼记录'); return; } console.log('权限检查通过,开始获取锻炼记录...'); // 测试获取最近锻炼记录 console.log('--- 测试获取最近锻炼记录 ---'); const recentWorkouts = await fetchRecentWorkouts(); console.log(`获取到 ${recentWorkouts.length} 条最近锻炼记录`); recentWorkouts.forEach((workout, index) => { console.log(`锻炼 ${index + 1}:`, { 类型: getWorkoutTypeDisplayName(workout.workoutActivityTypeString), 持续时间: formatWorkoutDuration(workout.duration), 能量消耗: workout.totalEnergyBurned ? `${workout.totalEnergyBurned}千卡` : '无', 距离: workout.totalDistance ? formatWorkoutDistance(workout.totalDistance) : '无', 开始时间: workout.startDate, 数据来源: workout.source.name }); }); // 测试统计功能 if (recentWorkouts.length > 0) { console.log('--- 锻炼统计信息 ---'); const stats = getWorkoutStatistics(recentWorkouts); console.log('统计结果:', { 总锻炼次数: stats.totalWorkouts, 总持续时间: formatWorkoutDuration(stats.totalDuration), 总能量消耗: `${stats.totalEnergyBurned}千卡`, 总距离: formatWorkoutDistance(stats.totalDistance), 平均持续时间: formatWorkoutDuration(stats.averageDuration), 锻炼类型分布: stats.workoutTypes }); } console.log('=== 锻炼记录测试完成 ==='); } catch (error) { console.error('锻炼记录测试过程中出现错误:', error); } } // 获取HRV数据并附带详细的状态信息 export async function fetchHRVWithStatus(date: Date): Promise<{ hrvData: HRVData | null; status: 'realtime' | 'recent' | 'historical' | 'none'; message: string; }> { try { const hrvData = await fetchSmartHRVData(date); if (!hrvData) { return { hrvData: null, status: 'none', message: '未找到HRV数据' }; } const dataTime = new Date(hrvData.recordedAt).getTime(); const now = Date.now(); const oneHour = 60 * 60 * 1000; const oneDay = 24 * 60 * 60 * 1000; let status: 'realtime' | 'recent' | 'historical'; let message: string; if (now - dataTime <= oneHour) { status = 'realtime'; message = '实时HRV数据'; } else if (now - dataTime <= oneDay) { status = 'recent'; message = '近期HRV数据'; } else { status = 'historical'; message = '历史HRV数据'; } return { hrvData, status, message }; } catch (error) { console.error('获取HRV状态失败:', error); return { hrvData: null, status: 'none', message: '获取HRV数据失败' }; } }