Files
digital-pilates/utils/health.ts
richarjiang e6dfd4d59a feat(health): 重构营养卡片数据获取逻辑,支持基础代谢与运动消耗分离
- 新增 fetchCompleteNutritionCardData 异步 action,统一拉取营养、健康与基础代谢数据
- NutritionRadarCard 改用 Redux 数据源,移除 props 透传,自动根据日期刷新
- BasalMetabolismCard 新增详情弹窗,展示 BMR 计算公式、正常区间及提升策略
- StepsCard 与 StepsCardOptimized 引入 InteractionManager 与动画懒加载,减少 UI 阻塞
- HealthKitManager 新增饮水读写接口,支持将饮水记录同步至 HealthKit
- 移除 statistics 页面冗余 mock 与 nutrition/health 重复请求,缓存时间统一为 5 分钟
2025-09-23 10:01:50 +08:00

974 lines
31 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import dayjs from 'dayjs';
import { AppState, AppStateStatus, NativeModules } from 'react-native';
import { SimpleEventEmitter } from './SimpleEventEmitter';
type HealthDataOptions = {
startDate: string;
endDate: string;
};
// 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<HealthPermissionStatus> {
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<boolean> {
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<boolean> {
return await healthPermissionManager.requestPermission();
}
// 获取当前权限状态
export function getHealthPermissionStatus(): HealthPermissionStatus {
return healthPermissionManager.getPermissionStatus();
}
// 检查权限状态
export async function checkHealthPermissionStatus(forceCheck: boolean = false): Promise<HealthPermissionStatus> {
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;
}
// 健康数据获取函数
export async function fetchStepCount(date: Date): Promise<number> {
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<HourlyStepData[]> {
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<number, number>();
// 优化:批量处理数据,减少重复验证
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<HourlyActivityData[]> {
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<HourlyExerciseData[]> {
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<number[]> {
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<number> {
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<number> {
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<number | null> {
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) {
const hrvValue = result.data[0].value;
logSuccess('HRV数据', result);
return Math.round(hrvValue); // Value already in ms from native
} else {
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<HealthActivitySummary | null> {
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<number | null> {
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;
}
}
async function fetchHeartRate(options: HealthDataOptions): Promise<number | null> {
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<number | null> {
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<TodayHealthData> {
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<TodayHealthData> {
return fetchHealthDataForDate(dayjs().toDate());
}
export async function fetchHRVForDate(date: Date): Promise<number | null> {
console.log('开始获取指定日期HRV数据...', date);
const options = createDateRange(date);
return fetchHeartRateVariability(options);
}
export async function fetchTodayHRV(): Promise<number | null> {
return fetchHRVForDate(dayjs().toDate());
}
// 获取最近几小时内的实时HRV数据
export async function fetchRecentHRV(hoursBack: number = 2): Promise<number | null> {
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<void> {
console.log('=== 开始测试HRV数据获取 ===');
try {
// 首先确保权限
const hasPermission = await ensureHealthPermissions();
if (!hasPermission) {
console.error('没有健康数据权限无法测试HRV');
return;
}
console.log('权限检查通过开始获取HRV数据...');
// 测试不同时间范围的HRV数据
const options = createDateRange(date);
// 获取今日HRV
const todayHRV = await fetchHeartRateVariability(options);
console.log('今日HRV结果:', todayHRV);
// 获取最近2小时HRV
const recentHRV = await fetchRecentHRV(2);
console.log('最近2小时HRV结果:', recentHRV);
// 获取指定日期HRV
const dateHRV = await fetchHRVForDate(date);
console.log('指定日期HRV结果:', dateHRV);
console.log('=== HRV数据测试完成 ===');
} catch (error) {
console.error('HRV测试过程中出现错误:', error);
}
}
// 更新healthkit中的体重 (暂未实现)
export async function updateWeight(_weight: number) {
try {
// Note: Weight saving would need to be implemented in native module
console.log('体重保存到HealthKit暂未实现');
return true; // Return true for now to not break existing functionality
} catch (error) {
console.error('更新体重失败:', error);
return false;
}
}
export async function testOxygenSaturationData(_date: Date = dayjs().toDate()): Promise<void> {
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<boolean> {
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 getWaterIntakeFromHealthKit(options: HealthDataOptions): Promise<any[]> {
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<boolean> {
// 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<HourlyActivityData[]> {
return fetchHourlyActiveCalories(date);
}
// 导出每小时锻炼分钟数据获取函数
export async function fetchHourlyExerciseMinutesForDate(date: Date): Promise<HourlyExerciseData[]> {
return fetchHourlyExerciseMinutes(date);
}
// 导出每小时站立数据获取函数
export async function fetchHourlyStandHoursForDate(date: Date): Promise<HourlyStandData[]> {
const hourlyStandData = await fetchHourlyStandHours(date);
return hourlyStandData.map((hasStood, hour) => ({
hour,
hasStood
}));
}
// 专门为活动圆环详情页获取精简的数据
export async function fetchActivityRingsForDate(date: Date): Promise<ActivityRingsData | null> {
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;
}