feat: 支持步数卡片; 优化数据分析各类卡片样式

This commit is contained in:
richarjiang
2025-08-30 17:07:04 +08:00
parent 465d5350f3
commit 741688065d
9 changed files with 462 additions and 103 deletions

View File

@@ -27,6 +27,11 @@ const PERMISSIONS: HealthKitPermissions = {
},
};
export type HourlyStepData = {
hour: number; // 0-23
steps: number;
};
export type TodayHealthData = {
steps: number;
activeEnergyBurned: number; // kilocalories
@@ -43,6 +48,8 @@ export type TodayHealthData = {
// 新增血氧饱和度和心率数据
oxygenSaturation: number | null;
heartRate: number | null;
// 每小时步数数据
hourlySteps: HourlyStepData[];
};
export async function ensureHealthPermissions(): Promise<boolean> {
@@ -155,6 +162,88 @@ async function fetchStepCount(date: Date): Promise<number> {
});
}
// 获取指定日期每小时步数数据
async function fetchHourlyStepCount(date: Date): Promise<HourlyStepData[]> {
return new Promise((resolve) => {
const startOfDay = dayjs(date).startOf('day');
const endOfDay = dayjs(date).endOf('day');
AppleHealthKit.getStepCount({
startDate: startOfDay.toDate().toISOString(),
endDate: endOfDay.toDate().toISOString(),
includeManuallyAdded: false,
}, (err, res) => {
if (err) {
logError('每小时步数', err);
return resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })));
}
if (!res || !Array.isArray(res) || res.length === 0) {
logWarning('每小时步数', '为空');
return resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })));
}
logSuccess('每小时步数', res);
// 初始化24小时数据
const hourlyData: HourlyStepData[] = Array.from({ length: 24 }, (_, i) => ({
hour: i,
steps: 0
}));
// 如果返回的是累计数据,我们需要获取样本数据
resolve(hourlyData);
});
});
}
// 使用样本数据获取每小时步数
async function fetchHourlyStepSamples(date: Date): Promise<HourlyStepData[]> {
return new Promise((resolve) => {
const startOfDay = dayjs(date).startOf('day');
const endOfDay = dayjs(date).endOf('day');
AppleHealthKit.getSamples(
{
startDate: startOfDay.toDate().toISOString(),
endDate: endOfDay.toDate().toISOString(),
type: 'StepCount',
},
(err: any, res: any[]) => {
if (err) {
logError('每小时步数样本', err);
return resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })));
}
if (!res || !Array.isArray(res) || res.length === 0) {
logWarning('每小时步数样本', '为空');
return resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })));
}
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) {
const hour = dayjs(sample.startDate).hour();
if (hour >= 0 && hour < 24) {
hourlyData[hour].steps += Math.round(sample.value);
}
}
});
resolve(hourlyData);
}
);
});
}
async function fetchActiveEnergyBurned(options: HealthDataOptions): Promise<number> {
return new Promise((resolve) => {
AppleHealthKit.getActiveEnergyBurned(options, (err, res) => {
@@ -304,7 +393,8 @@ function getDefaultHealthData(): TodayHealthData {
standHours: 0,
standHoursGoal: 12,
oxygenSaturation: null,
heartRate: null
heartRate: null,
hourlySteps: Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 }))
};
}
@@ -318,6 +408,7 @@ export async function fetchHealthDataForDate(date: Date): Promise<TodayHealthDat
// 并行获取所有健康数据
const [
steps,
hourlySteps,
activeEnergyBurned,
basalEnergyBurned,
sleepDuration,
@@ -327,6 +418,7 @@ export async function fetchHealthDataForDate(date: Date): Promise<TodayHealthDat
heartRate
] = await Promise.all([
fetchStepCount(date),
fetchHourlyStepSamples(date),
fetchActiveEnergyBurned(options),
fetchBasalEnergyBurned(options),
fetchSleepDuration(options),
@@ -338,6 +430,7 @@ export async function fetchHealthDataForDate(date: Date): Promise<TodayHealthDat
console.log('指定日期健康数据获取完成:', {
steps,
hourlySteps,
activeEnergyBurned,
basalEnergyBurned,
sleepDuration,
@@ -349,6 +442,7 @@ export async function fetchHealthDataForDate(date: Date): Promise<TodayHealthDat
return {
steps,
hourlySteps,
activeEnergyBurned,
basalEnergyBurned,
sleepDuration,