Files
digital-pilates/utils/health.ts

886 lines
28 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 type { HealthActivitySummary, HealthKitPermissions } from 'react-native-health';
import AppleHealthKit from 'react-native-health';
type HealthDataOptions = {
startDate: string;
endDate: string;
};
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 = {
steps: number;
activeEnergyBurned: number; // kilocalories
basalEnergyBurned: number; // kilocalories - 基础代谢率
sleepDuration: number; // 睡眠时长(分钟)
hrv: number | null; // 心率变异性 (ms)
// 健身圆环数据
activeCalories: number;
activeCaloriesGoal: number;
exerciseMinutes: number;
exerciseMinutesGoal: number;
standHours: number;
standHoursGoal: number;
// 新增血氧饱和度和心率数据
oxygenSaturation: number | null;
heartRate: number | null;
// 每小时步数数据
hourlySteps: HourlyStepData[];
};
export async function ensureHealthPermissions(): Promise<boolean> {
return new Promise((resolve) => {
console.log('开始初始化HealthKit...');
AppleHealthKit.initHealthKit(PERMISSIONS, (error) => {
if (error) {
console.error('HealthKit初始化失败:', error);
// 常见错误处理
if (typeof error === 'string') {
if (error.includes('not available')) {
console.warn('HealthKit不可用 - 可能在模拟器上运行或非iOS设备');
}
}
resolve(false);
return;
}
console.log('HealthKit初始化成功');
resolve(true);
});
});
}
// 日期工具函数
function createDateRange(date: Date): HealthDataOptions {
return {
startDate: dayjs(date).startOf('day').toDate().toISOString(),
endDate: dayjs(date).endOf('day').toDate().toISOString()
};
}
// 睡眠时长计算
function calculateSleepDuration(samples: any[]): number {
return samples.reduce((total, sample) => {
if (sample && sample.startDate && sample.endDate) {
const startTime = dayjs(sample.startDate).valueOf();
const endTime = dayjs(sample.endDate).valueOf();
const durationMinutes = (endTime - startTime) / (1000 * 60);
return total + durationMinutes;
}
return total;
}, 0);
}
// 通用错误处理
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;
}
// 健康数据获取函数
async function fetchStepCount(date: Date): Promise<number> {
return new Promise((resolve) => {
AppleHealthKit.getStepCount({
date: dayjs(date).toDate().toISOString()
}, (err, res) => {
if (err) {
logError('步数', err);
return resolve(0);
}
if (!res) {
logWarning('步数', '为空');
return resolve(0);
}
logSuccess('步数', res);
resolve(res.value || 0);
});
});
}
// 使用样本数据获取每小时步数
async function fetchHourlyStepSamples(date: Date): Promise<HourlyStepData[]> {
return new Promise((resolve) => {
const startOfDay = dayjs(date).startOf('day');
const endOfDay = dayjs(date).endOf('day');
// 使用正确的 getDailyStepCountSamples 方法,设置 period 为 60 分钟获取每小时数据
const options = {
startDate: startOfDay.toDate().toISOString(),
endDate: endOfDay.toDate().toISOString(),
ascending: false,
period: 60, // 60分钟为一个时间段获取每小时数据
includeManuallyAdded: false,
};
AppleHealthKit.getDailyStepCountSamples(
options,
(err: any, res: any[]) => {
if (err) {
logError('每小时步数样本', err);
// 如果主方法失败,返回默认数据
resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })));
return;
}
logSuccess('每小时步数样本', res);
// 初始化24小时数据
const hourlyData: HourlyStepData[] = Array.from({ length: 24 }, (_, i) => ({
hour: i,
steps: 0
}));
// 将每小时的步数样本数据映射到对应的小时
res.forEach((sample: any) => {
if (sample && sample.startDate && sample.value !== undefined) {
const hour = dayjs(sample.startDate).hour();
if (hour >= 0 && hour < 24) {
// 使用样本中的步数值,如果有 metadata优先使用 metadata 中的数据
const stepValue = sample.metadata && sample.metadata.length > 0
? sample.metadata.reduce((total: number, meta: any) => total + (meta.quantity || 0), 0)
: sample.value;
hourlyData[hour].steps = Math.round(stepValue);
}
}
});
resolve(hourlyData);
}
);
});
}
// 获取每小时活动热量数据
// 优化版本:使用更精确的时间间隔来获取每小时数据
async function fetchHourlyActiveCalories(date: Date): Promise<HourlyActivityData[]> {
return new Promise(async (resolve) => {
const startOfDay = dayjs(date).startOf('day');
// 初始化24小时数据
const hourlyData: HourlyActivityData[] = Array.from({ length: 24 }, (_, i) => ({
hour: i,
calories: 0
}));
try {
// 为每个小时单独获取数据,确保精确性
const promises = Array.from({ length: 24 }, (_, hour) => {
const hourStart = startOfDay.add(hour, 'hour');
const hourEnd = hourStart.add(1, 'hour');
const options = {
startDate: hourStart.toDate().toISOString(),
endDate: hourEnd.toDate().toISOString(),
ascending: true,
includeManuallyAdded: false
};
return new Promise<number>((resolveHour) => {
AppleHealthKit.getActiveEnergyBurned(options, (err, res) => {
if (err || !res || !Array.isArray(res)) {
resolveHour(0);
return;
}
const total = res.reduce((acc: number, sample: any) => {
return acc + (sample?.value || 0);
}, 0);
resolveHour(Math.round(total));
});
});
});
const results = await Promise.all(promises);
results.forEach((calories, hour) => {
hourlyData[hour].calories = calories;
});
logSuccess('每小时活动热量', hourlyData);
resolve(hourlyData);
} catch (error) {
logError('每小时活动热量', error);
resolve(hourlyData);
}
});
}
// 获取每小时锻炼分钟数据
// 使用 AppleHealthKit.getAppleExerciseTime 获取锻炼样本数据
async function fetchHourlyExerciseMinutes(date: Date): Promise<HourlyExerciseData[]> {
return new Promise((resolve) => {
const startOfDay = dayjs(date).startOf('day');
const endOfDay = dayjs(date).endOf('day');
const options = {
startDate: startOfDay.toDate().toISOString(),
endDate: endOfDay.toDate().toISOString(),
ascending: true,
includeManuallyAdded: false
};
// 使用 getAppleExerciseTime 获取详细的锻炼样本数据
AppleHealthKit.getAppleExerciseTime(options, (err, res) => {
if (err) {
logError('每小时锻炼分钟', err);
resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, minutes: 0 })));
return;
}
if (!res || !Array.isArray(res)) {
logWarning('每小时锻炼分钟', '数据为空');
resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, minutes: 0 })));
return;
}
logSuccess('每小时锻炼分钟', res);
// 初始化24小时数据
const hourlyData: HourlyExerciseData[] = Array.from({ length: 24 }, (_, i) => ({
hour: i,
minutes: 0
}));
// 将锻炼样本数据按小时分组统计
res.forEach((sample: any) => {
if (sample && sample.startDate && sample.value !== undefined) {
const hour = dayjs(sample.startDate).hour();
if (hour >= 0 && hour < 24) {
hourlyData[hour].minutes += sample.value;
}
}
});
// 四舍五入处理
hourlyData.forEach(data => {
data.minutes = Math.round(data.minutes);
});
resolve(hourlyData);
});
});
}
// 获取每小时站立小时数据
// 使用 AppleHealthKit.getAppleStandTime 获取站立样本数据
async function fetchHourlyStandHours(date: Date): Promise<number[]> {
return new Promise((resolve) => {
const startOfDay = dayjs(date).startOf('day');
const endOfDay = dayjs(date).endOf('day');
const options = {
startDate: startOfDay.toDate().toISOString(),
endDate: endOfDay.toDate().toISOString()
};
// 使用 getAppleStandTime 获取详细的站立样本数据
AppleHealthKit.getAppleStandTime(options, (err, res) => {
if (err) {
logError('每小时站立数据', err);
resolve(Array.from({ length: 24 }, () => 0));
return;
}
if (!res || !Array.isArray(res)) {
logWarning('每小时站立数据', '数据为空');
resolve(Array.from({ length: 24 }, () => 0));
return;
}
logSuccess('每小时站立数据', res);
// 初始化24小时数据
const hourlyData: number[] = Array.from({ length: 24 }, () => 0);
// 将站立样本数据按小时分组统计
res.forEach((sample: any) => {
if (sample && sample.startDate && sample.value !== undefined) {
const hour = dayjs(sample.startDate).hour();
if (hour >= 0 && hour < 24) {
// 站立时间通常以分钟为单位转换为小时1表示该小时有站立0表示没有
hourlyData[hour] = sample.value > 0 ? 1 : 0;
}
}
});
resolve(hourlyData);
});
});
}
async function fetchActiveEnergyBurned(options: HealthDataOptions): Promise<number> {
return new Promise((resolve) => {
AppleHealthKit.getActiveEnergyBurned(options, (err, res) => {
if (err) {
logError('消耗卡路里', err);
return resolve(0);
}
if (!res || !Array.isArray(res) || res.length === 0) {
logWarning('卡路里', '为空或格式错误');
return resolve(0);
}
logSuccess('卡路里', res);
const total = res.reduce((acc: number, item: any) => acc + (item?.value || 0), 0);
resolve(total);
});
});
}
async function fetchBasalEnergyBurned(options: HealthDataOptions): Promise<number> {
return new Promise((resolve) => {
AppleHealthKit.getBasalEnergyBurned(options, (err, res) => {
if (err) {
logError('基础代谢', err);
return resolve(0);
}
if (!res || !Array.isArray(res) || res.length === 0) {
logWarning('基础代谢', '为空或格式错误');
return resolve(0);
}
logSuccess('基础代谢', res);
const total = res.reduce((acc: number, item: any) => acc + (item?.value || 0), 0);
resolve(total);
});
});
}
async function fetchSleepDuration(options: HealthDataOptions): Promise<number> {
return new Promise((resolve) => {
AppleHealthKit.getSleepSamples(options, (err, res) => {
if (err) {
logError('睡眠数据', err);
return resolve(0);
}
if (!res || !Array.isArray(res) || res.length === 0) {
logWarning('睡眠', '为空或格式错误');
return resolve(0);
}
logSuccess('睡眠', res);
resolve(calculateSleepDuration(res));
});
});
}
async function fetchHeartRateVariability(options: HealthDataOptions): Promise<number | null> {
return new Promise((resolve) => {
AppleHealthKit.getHeartRateVariabilitySamples(options, (err, res) => {
if (err) {
logError('HRV数据', err);
return resolve(null);
}
if (!res || !Array.isArray(res) || res.length === 0) {
logWarning('HRV', '为空或格式错误');
return resolve(null);
}
logSuccess('HRV', res);
const latestHrv = res[res.length - 1];
if (latestHrv && latestHrv.value) {
resolve(Math.round(latestHrv.value * 1000));
} else {
resolve(null);
}
});
});
}
async function fetchActivitySummary(options: HealthDataOptions): Promise<HealthActivitySummary | null> {
return new Promise((resolve) => {
AppleHealthKit.getActivitySummary(
options,
(err: string, results: HealthActivitySummary[]) => {
if (err) {
logError('ActivitySummary', err);
return resolve(null);
}
if (!results || results.length === 0) {
logWarning('ActivitySummary', '为空');
return resolve(null);
}
logSuccess('ActivitySummary', results[0]);
resolve(results[0]);
},
);
});
}
async function fetchOxygenSaturation(options: HealthDataOptions): Promise<number | null> {
return new Promise((resolve) => {
AppleHealthKit.getOxygenSaturationSamples(options, (err, res) => {
if (err) {
logError('血氧饱和度', err);
return resolve(null);
}
if (!res || !Array.isArray(res) || res.length === 0) {
logWarning('血氧饱和度', '为空或格式错误');
return resolve(null);
}
logSuccess('血氧饱和度', res);
const latestOxygen = res[res.length - 1];
return resolve(validateOxygenSaturation(latestOxygen?.value));
});
});
}
async function fetchHeartRate(options: HealthDataOptions): Promise<number | null> {
return new Promise((resolve) => {
AppleHealthKit.getHeartRateSamples(options, (err, res) => {
if (err) {
logError('心率', err);
return resolve(null);
}
if (!res || !Array.isArray(res) || res.length === 0) {
logWarning('心率', '为空或格式错误');
return resolve(null);
}
logSuccess('心率', res);
const latestHeartRate = res[res.length - 1];
return resolve(validateHeartRate(latestHeartRate?.value));
});
});
}
// 获取指定时间范围内的最大心率
export async function fetchMaximumHeartRate(options: HealthDataOptions): Promise<number | null> {
return new Promise((resolve) => {
AppleHealthKit.getHeartRateSamples(options, (err, res) => {
if (err) {
logError('最大心率', err);
return resolve(null);
}
if (!res || !Array.isArray(res) || res.length === 0) {
logWarning('最大心率', '为空或格式错误');
return resolve(null);
}
// 从所有心率样本中找出最大值
let maxHeartRate = 0;
let validSamplesCount = 0;
res.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 });
resolve(maxHeartRate);
} else {
logWarning('最大心率', '没有找到有效的样本数据');
resolve(null);
}
});
});
}
// 默认健康数据
function getDefaultHealthData(): TodayHealthData {
return {
steps: 0,
activeEnergyBurned: 0,
basalEnergyBurned: 0,
sleepDuration: 0,
hrv: null,
activeCalories: 0,
activeCaloriesGoal: 350,
exerciseMinutes: 0,
exerciseMinutesGoal: 30,
standHours: 0,
standHoursGoal: 12,
oxygenSaturation: null,
heartRate: null,
hourlySteps: Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 }))
};
}
export async function fetchHealthDataForDate(date: Date): Promise<TodayHealthData> {
try {
console.log('开始获取指定日期健康数据...', date);
const options = createDateRange(date);
console.log('查询选项:', options);
// 并行获取所有健康数据
const [
steps,
hourlySteps,
activeEnergyBurned,
basalEnergyBurned,
sleepDuration,
hrv,
activitySummary,
oxygenSaturation,
heartRate
] = await Promise.all([
fetchStepCount(date),
fetchHourlyStepSamples(date),
fetchActiveEnergyBurned(options),
fetchBasalEnergyBurned(options),
fetchSleepDuration(options),
fetchHeartRateVariability(options),
fetchActivitySummary(options),
fetchOxygenSaturation(options),
fetchHeartRate(options)
]);
console.log('指定日期健康数据获取完成:', {
steps,
hourlySteps,
activeEnergyBurned,
basalEnergyBurned,
sleepDuration,
hrv,
activitySummary,
oxygenSaturation,
heartRate
});
return {
steps,
hourlySteps,
activeEnergyBurned,
basalEnergyBurned,
sleepDuration,
hrv,
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),
oxygenSaturation,
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);
}
// 更新healthkit中的体重
export async function updateWeight(weight: number) {
return new Promise((resolve) => {
AppleHealthKit.saveWeight({
value: weight,
}, (err, res) => {
if (err) {
console.error('更新体重失败:', err);
return resolve(false);
}
console.log('体重更新成功:', res);
resolve(true);
});
});
}
export async function testOxygenSaturationData(date: Date = dayjs().toDate()): Promise<void> {
console.log('=== 开始测试血氧饱和度数据获取 ===');
const options = createDateRange(date);
return new Promise((resolve) => {
AppleHealthKit.getOxygenSaturationSamples(options, (err, res) => {
if (err) {
console.error('获取血氧饱和度失败:', err);
resolve();
return;
}
console.log('原始血氧饱和度数据:', res);
if (!res || !Array.isArray(res) || res.length === 0) {
console.warn('血氧饱和度数据为空');
resolve();
return;
}
// 分析所有数据样本
res.forEach((sample, index) => {
console.log(`样本 ${index + 1}:`, {
value: sample.value,
valueType: typeof sample.value,
startDate: sample.startDate,
endDate: sample.endDate
});
});
// 获取最新的血氧饱和度值并验证
const latestOxygen = res[res.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('=== 血氧饱和度数据测试完成 ===');
resolve();
});
});
}
// 添加饮水记录到 HealthKit
export async function saveWaterIntakeToHealthKit(amount: number, recordedAt?: string): Promise<boolean> {
return new Promise((resolve) => {
// HealthKit 水分摄入量使用升(L)作为单位,需要将毫升转换为升
const waterOptions = {
value: amount / 1000, // 将毫升转换为升 (ml -> L)
startDate: recordedAt ? new Date(recordedAt).toISOString() : new Date().toISOString(),
endDate: recordedAt ? new Date(recordedAt).toISOString() : new Date().toISOString(),
};
AppleHealthKit.saveWater(waterOptions, (error: string, result) => {
if (error) {
console.error('添加饮水记录到 HealthKit 失败:', error);
resolve(false);
return;
}
console.log('成功添加饮水记录到 HealthKit:', {
originalAmount: amount,
convertedAmount: amount / 1000,
recordedAt,
result
});
resolve(true);
});
});
}
// 获取 HealthKit 中的饮水记录
export async function getWaterIntakeFromHealthKit(options: HealthDataOptions): Promise<any[]> {
return new Promise((resolve) => {
AppleHealthKit.getWaterSamples(options, (error: string, results: any[]) => {
if (error) {
console.error('获取 HealthKit 饮水记录失败:', error);
resolve([]);
return;
}
console.log('从 HealthKit 获取饮水记录:', results);
resolve(results || []);
});
});
}
// 删除 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;
}
}