Files
digital-pilates/utils/notificationHelpers.ts
richarjiang dbe460a084 refactor(health): remove HRV field and improve notification types
- Remove heart rate variability (hrv) field from health data interfaces and implementations
- Update default member name to Chinese localization
- Replace type assertions with proper enum types in notification schedulers
2025-09-22 09:02:42 +08:00

1489 lines
48 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 * as Notifications from 'expo-notifications';
import { NotificationData, notificationService } from '../services/notifications';
import { getNotificationEnabled } from './userPreferences';
/**
* 构建 coach 页面的深度链接
*/
export function buildCoachDeepLink(params: {
action?: 'diet' | 'weight' | 'mood' | 'workout';
subAction?: 'record' | 'photo' | 'text' | 'card';
meal?: 'breakfast' | 'lunch' | 'dinner' | 'snack';
message?: string;
}): string {
const baseUrl = '/coach';
const searchParams = new URLSearchParams();
if (params.action) searchParams.set('action', params.action);
if (params.subAction) searchParams.set('subAction', params.subAction);
if (params.meal) searchParams.set('meal', params.meal);
if (params.message) searchParams.set('message', encodeURIComponent(params.message));
const queryString = searchParams.toString();
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
}
/**
* 运动相关的通知辅助函数
*/
export class WorkoutNotificationHelpers {
/**
* 发送运动开始提醒
*/
static async sendWorkoutStartReminder(userName: string) {
return notificationService.sendImmediateNotification({
title: '运动时间到',
body: `${userName},该开始今天的普拉提训练了!`,
data: { type: 'workout_start' },
sound: true,
priority: 'high',
});
}
/**
* 发送运动完成通知
*/
static async sendWorkoutCompleteNotification(userName: string, duration: number) {
return notificationService.sendImmediateNotification({
title: '运动完成',
body: `${userName},恭喜您完成了${duration}分钟的训练!`,
data: { type: 'workout_complete' },
sound: true,
priority: 'normal',
});
}
/**
* 安排每日运动提醒
*/
static async scheduleDailyWorkoutReminder(userName: string, hour: number = 9, minute: number = 0) {
const reminderTime = new Date();
reminderTime.setHours(hour, minute, 0, 0);
// 如果今天的时间已经过了,设置为明天
if (reminderTime.getTime() <= Date.now()) {
reminderTime.setDate(reminderTime.getDate() + 1);
}
return notificationService.scheduleRepeatingNotification(
{
title: '每日运动提醒',
body: `${userName},该开始今天的普拉提训练了!`,
data: { type: 'daily_workout_reminder' },
sound: true,
priority: 'high',
},
{ days: 1 }
);
}
}
/**
* 目标相关的通知辅助函数
*/
export class GoalNotificationHelpers {
/**
* 发送目标达成通知
*/
static async sendGoalAchievementNotification(userName: string, goalName: string) {
return notificationService.sendImmediateNotification({
title: '目标达成',
body: `${userName},恭喜您达成了目标:${goalName}`,
data: { type: 'goal_achievement', goalName },
sound: true,
priority: 'high',
});
}
/**
* 发送目标进度更新通知
*/
static async sendGoalProgressNotification(userName: string, goalName: string, progress: number) {
return notificationService.sendImmediateNotification({
title: '目标进度',
body: `${userName},您的目标"${goalName}"已完成${progress}%`,
data: { type: 'goal_progress', goalName, progress },
sound: true,
priority: 'normal',
});
}
/**
* 安排目标提醒
*/
static async scheduleGoalReminder(userName: string, goalName: string, deadline: Date) {
// 在截止日期前一天发送提醒
const reminderDate = new Date(deadline);
reminderDate.setDate(reminderDate.getDate() - 1);
return notificationService.scheduleNotificationAtDate(
{
title: '目标截止提醒',
body: `${userName},您的目标"${goalName}"明天就要截止了,加油!`,
data: { type: 'goal_deadline_reminder', goalName },
sound: true,
priority: 'high',
},
reminderDate
);
}
/**
* 根据目标设置创建定时推送
* @param goalData 目标数据
* @param userName 用户名
* @returns 通知ID数组
*/
static async scheduleGoalNotifications(
goalData: {
title: string;
repeatType: 'daily' | 'weekly' | 'monthly';
frequency: number;
hasReminder: boolean;
reminderTime?: string;
customRepeatRule?: {
weekdays?: number[];
dayOfMonth?: number[];
};
startTime?: number;
},
userName: string
): Promise<string[]> {
const notificationIds: string[] = [];
// 如果没有开启提醒,直接返回
if (!goalData.hasReminder || !goalData.reminderTime) {
console.log('目标未开启提醒或未设置提醒时间');
return notificationIds;
}
try {
// 解析提醒时间
const [hours, minutes] = goalData.reminderTime.split(':').map(Number);
// 创建通知内容
const notification: NotificationData = {
title: '目标提醒',
body: `${userName},该完成您的目标"${goalData.title}"了!`,
data: {
type: 'goal_reminder',
goalTitle: goalData.title,
repeatType: goalData.repeatType,
frequency: goalData.frequency
},
sound: true,
priority: 'high',
};
// 根据重复类型创建不同的通知
switch (goalData.repeatType) {
case 'daily':
// 每日重复 - 使用日历重复通知
const dailyId = await notificationService.scheduleCalendarRepeatingNotification(
notification,
{
type: Notifications.SchedulableTriggerInputTypes.DAILY,
hour: hours,
minute: minutes,
}
);
notificationIds.push(dailyId);
console.log(`已安排每日目标提醒通知ID${dailyId}`);
break;
case 'weekly':
// 每周重复 - 为每个选中的星期几创建单独的通知
if (goalData.customRepeatRule?.weekdays && goalData.customRepeatRule.weekdays.length > 0) {
for (const weekday of goalData.customRepeatRule.weekdays) {
const weeklyId = await notificationService.scheduleCalendarRepeatingNotification(
notification,
{
type: Notifications.SchedulableTriggerInputTypes.WEEKLY,
hour: hours,
minute: minutes,
weekdays: [weekday],
}
);
notificationIds.push(weeklyId);
console.log(`已安排每周目标提醒,星期${weekday}通知ID${weeklyId}`);
}
} else {
// 默认每周重复
const weeklyId = await notificationService.scheduleCalendarRepeatingNotification(
notification,
{
type: Notifications.SchedulableTriggerInputTypes.WEEKLY,
hour: hours,
minute: minutes,
}
);
notificationIds.push(weeklyId);
console.log(`已安排每周目标提醒通知ID${weeklyId}`);
}
break;
case 'monthly':
// 每月重复 - 为每个选中的日期创建单独的通知
if (goalData.customRepeatRule?.dayOfMonth && goalData.customRepeatRule.dayOfMonth.length > 0) {
for (const dayOfMonth of goalData.customRepeatRule.dayOfMonth) {
const monthlyId = await notificationService.scheduleCalendarRepeatingNotification(
notification,
{
type: Notifications.SchedulableTriggerInputTypes.MONTHLY,
hour: hours,
minute: minutes,
dayOfMonth: dayOfMonth,
}
);
notificationIds.push(monthlyId);
console.log(`已安排每月目标提醒,${dayOfMonth}通知ID${monthlyId}`);
}
} else {
// 默认每月重复
const monthlyId = await notificationService.scheduleCalendarRepeatingNotification(
notification,
{
type: Notifications.SchedulableTriggerInputTypes.MONTHLY,
hour: hours,
minute: minutes,
dayOfMonth: 1,
}
);
notificationIds.push(monthlyId);
console.log(`已安排每月目标提醒通知ID${monthlyId}`);
}
break;
}
console.log(`目标"${goalData.title}"的定时推送已创建完成,共${notificationIds.length}个通知`);
return notificationIds;
} catch (error) {
console.error('创建目标定时推送失败:', error);
throw error;
}
}
/**
* 取消特定目标的所有通知
*/
static async cancelGoalNotifications(goalTitle: string): Promise<void> {
try {
const notifications = await notificationService.getAllScheduledNotifications();
for (const notification of notifications) {
if (notification.content.data?.type === 'goal_reminder' &&
notification.content.data?.goalTitle === goalTitle) {
await notificationService.cancelNotification(notification.identifier);
console.log(`已取消目标"${goalTitle}"的通知:${notification.identifier}`);
}
}
} catch (error) {
console.error('取消目标通知失败:', error);
throw error;
}
}
}
/**
* 营养相关的通知辅助函数
*/
export class NutritionNotificationHelpers {
/**
* 发送营养记录提醒
*/
static async sendNutritionRecordReminder(userName: string) {
return notificationService.sendImmediateNotification({
title: '营养记录',
body: `${userName},记得记录今天的饮食情况`,
data: { type: 'nutrition_record_reminder' },
sound: true,
priority: 'normal',
});
}
/**
* 安排每日午餐提醒
* @param userName 用户名
* @param hour 小时 (默认12点)
* @param minute 分钟 (默认0分)
* @returns 通知ID
*/
static async scheduleDailyLunchReminder(
userName: string,
hour: number = 12,
minute: number = 0
): Promise<string | null> {
try {
// 检查是否已经存在午餐提醒
const existingNotifications = await notificationService.getAllScheduledNotifications();
const existingLunchReminder = existingNotifications.find(
notification =>
notification.content.data?.type === 'lunch_reminder' &&
notification.content.data?.isDailyReminder === true
);
if (existingLunchReminder) {
console.log('午餐提醒已存在,跳过重复注册:', existingLunchReminder.identifier);
return existingLunchReminder.identifier;
}
// 构建跳转到 coach 页面的深度链接
const coachUrl = buildCoachDeepLink({
action: 'diet',
subAction: 'card',
meal: 'lunch'
});
// 创建午餐提醒通知
const notificationId = await notificationService.scheduleCalendarRepeatingNotification(
{
title: '午餐记录提醒',
body: `${userName},记得记录今天的午餐情况哦!`,
data: {
type: 'lunch_reminder',
isDailyReminder: true,
meal: '午餐',
url: coachUrl // 添加深度链接
},
sound: true,
priority: 'normal',
},
{
type: Notifications.SchedulableTriggerInputTypes.DAILY,
hour: hour,
minute: minute,
}
);
console.log('每日午餐提醒已安排ID:', notificationId);
return notificationId;
} catch (error) {
console.error('安排每日午餐提醒失败:', error);
throw error;
}
}
/**
* 发送午餐记录提醒
*/
static async sendLunchReminder(userName: string) {
const coachUrl = buildCoachDeepLink({
action: 'diet',
subAction: 'card',
meal: 'lunch'
});
return notificationService.sendImmediateNotification({
title: '午餐记录提醒',
body: `${userName},记得记录今天的午餐情况哦!`,
data: {
type: 'lunch_reminder',
meal: '午餐',
url: coachUrl
},
sound: true,
priority: 'normal',
});
}
/**
* 取消午餐提醒
*/
static async cancelLunchReminder(): Promise<void> {
try {
const notifications = await notificationService.getAllScheduledNotifications();
for (const notification of notifications) {
if (notification.content.data?.type === 'lunch_reminder' &&
notification.content.data?.isDailyReminder === true) {
await notificationService.cancelNotification(notification.identifier);
console.log('已取消午餐提醒:', notification.identifier);
}
}
} catch (error) {
console.error('取消午餐提醒失败:', error);
throw error;
}
}
/**
* 安排每日晚餐提醒
* @param userName 用户名
* @param hour 小时 (默认18点)
* @param minute 分钟 (默认0分)
* @returns 通知ID
*/
static async scheduleDailyDinnerReminder(
userName: string,
hour: number = 18,
minute: number = 0
): Promise<string | null> {
try {
// 检查是否已经存在晚餐提醒
const existingNotifications = await notificationService.getAllScheduledNotifications();
const existingDinnerReminder = existingNotifications.find(
notification =>
notification.content.data?.type === 'dinner_reminder' &&
notification.content.data?.isDailyReminder === true
);
if (existingDinnerReminder) {
console.log('晚餐提醒已存在,跳过重复注册:', existingDinnerReminder.identifier);
return existingDinnerReminder.identifier;
}
// 创建晚餐提醒通知
const notificationId = await notificationService.scheduleCalendarRepeatingNotification(
{
title: '🍽️ 晚餐时光到啦!',
body: `${userName},美好的晚餐时光开始了~记得记录今天的晚餐哦!营养均衡很重要呢 💪`,
data: {
type: 'dinner_reminder',
isDailyReminder: true,
meal: '晚餐',
url: '/nutrition/records' // 直接跳转到营养记录页面
},
sound: true,
priority: 'normal',
},
{
type: Notifications.SchedulableTriggerInputTypes.DAILY,
hour: hour,
minute: minute,
}
);
console.log('每日晚餐提醒已安排ID:', notificationId);
return notificationId;
} catch (error) {
console.error('安排每日晚餐提醒失败:', error);
throw error;
}
}
/**
* 取消晚餐提醒
*/
static async cancelDinnerReminder(): Promise<void> {
try {
const notifications = await notificationService.getAllScheduledNotifications();
for (const notification of notifications) {
if (notification.content.data?.type === 'dinner_reminder' &&
notification.content.data?.isDailyReminder === true) {
await notificationService.cancelNotification(notification.identifier);
console.log('已取消晚餐提醒:', notification.identifier);
}
}
} catch (error) {
console.error('取消晚餐提醒失败:', error);
throw error;
}
}
/**
* 安排营养记录提醒
*/
static async scheduleNutritionReminders(userName: string) {
// 安排三餐提醒
const mealTimes = [
{ hour: 8, minute: 0, meal: '早餐' },
{ hour: 12, minute: 0, meal: '午餐' },
{ hour: 18, minute: 0, meal: '晚餐' },
];
const notifications = [];
for (const mealTime of mealTimes) {
const reminderTime = new Date();
reminderTime.setHours(mealTime.hour, mealTime.minute, 0, 0);
// 如果今天的时间已经过了,设置为明天
if (reminderTime.getTime() <= Date.now()) {
reminderTime.setDate(reminderTime.getDate() + 1);
}
// 构建深度链接
const mealTypeMap: Record<string, string> = {
'早餐': 'breakfast',
'午餐': 'lunch',
'晚餐': 'dinner'
};
const coachUrl = buildCoachDeepLink({
action: 'diet',
subAction: 'card',
meal: mealTypeMap[mealTime.meal] as 'breakfast' | 'lunch' | 'dinner'
});
const notificationId = await notificationService.scheduleRepeatingNotification(
{
title: `${mealTime.meal}提醒`,
body: `${userName},记得记录您的${mealTime.meal}情况`,
data: {
type: 'meal_reminder',
meal: mealTime.meal,
url: coachUrl
},
sound: true,
priority: 'normal',
},
{ days: 1 }
);
notifications.push(notificationId);
}
return notifications;
}
}
/**
* 心情相关的通知辅助函数
*/
export class MoodNotificationHelpers {
/**
* 安排每日心情提醒
* @param userName 用户名
* @param hour 小时 (默认21点)
* @param minute 分钟 (默认0分)
* @returns 通知ID
*/
static async scheduleDailyMoodReminder(
userName: string,
hour: number = 21,
minute: number = 0
): Promise<string | null> {
try {
// 检查是否已经存在心情提醒
const existingNotifications = await notificationService.getAllScheduledNotifications();
const existingMoodReminder = existingNotifications.find(
notification =>
notification.content.data?.type === 'mood_reminder' &&
notification.content.data?.isDailyReminder === true
);
if (existingMoodReminder) {
console.log('心情提醒已存在,跳过重复注册:', existingMoodReminder.identifier);
return existingMoodReminder.identifier;
}
// 创建心情提醒通知
const notificationId = await notificationService.scheduleCalendarRepeatingNotification(
{
title: '🌙 今天过得怎么样呀?',
body: `${userName},夜深了~来记录一下今天的心情吧!每一份情感都值得被珍藏 ✨💕`,
data: {
type: 'mood_reminder',
isDailyReminder: true,
url: '/mood-statistics' // 跳转到心情统计页面
},
sound: true,
priority: 'normal',
},
{
type: Notifications.SchedulableTriggerInputTypes.DAILY,
hour: hour,
minute: minute,
}
);
console.log('每日心情提醒已安排ID:', notificationId);
return notificationId;
} catch (error) {
console.error('安排每日心情提醒失败:', error);
throw error;
}
}
/**
* 发送心情记录提醒
*/
static async sendMoodReminder(userName: string) {
return notificationService.sendImmediateNotification({
title: '🌙 今天过得怎么样呀?',
body: `${userName},夜深了~来记录一下今天的心情吧!每一份情感都值得被珍藏 ✨💕`,
data: {
type: 'mood_reminder',
url: '/mood-statistics'
},
sound: true,
priority: 'normal',
});
}
/**
* 取消心情提醒
*/
static async cancelMoodReminder(): Promise<void> {
try {
const notifications = await notificationService.getAllScheduledNotifications();
for (const notification of notifications) {
if (notification.content.data?.type === 'mood_reminder' &&
notification.content.data?.isDailyReminder === true) {
await notificationService.cancelNotification(notification.identifier);
console.log('已取消心情提醒:', notification.identifier);
}
}
} catch (error) {
console.error('取消心情提醒失败:', error);
throw error;
}
}
}
/**
* 喝水相关的通知辅助函数
*/
export class WaterNotificationHelpers {
/**
* 检查喝水目标完成情况并发送提醒
* @param userName 用户名
* @param todayStats 今日喝水统计数据
* @param currentHour 当前小时(用于时间限制检查)
* @returns 是否发送了通知
*/
static async checkWaterGoalAndNotify(
userName: string,
todayStats: { totalAmount: number; dailyGoal: number; completionRate: number },
currentHour: number = new Date().getHours()
): Promise<boolean> {
try {
// 检查时间限制早上9点以前和晚上9点以后不通知
if (currentHour < 9 || currentHour >= 23) {
console.log(`当前时间${currentHour}不在通知时间范围内9:00-21:00跳过喝水提醒`);
return false;
}
// 检查喝水目标是否已达成
if (todayStats.completionRate >= 100) {
console.log('喝水目标已达成,无需发送提醒');
return false;
}
// 检查是否在过去2小时内已经发送过喝水提醒避免重复打扰
const lastNotificationKey = '@last_water_notification';
const AsyncStorage = (await import('@/utils/kvStore')).default;
const lastNotificationTime = await AsyncStorage.getItem(lastNotificationKey);
const now = new Date().getTime();
const twoHoursAgo = now - (2 * 60 * 60 * 1000); // 2小时前
if (lastNotificationTime && parseInt(lastNotificationTime) > twoHoursAgo) {
console.log('2小时内已发送过喝水提醒跳过本次通知');
return false;
}
// 计算还需要喝多少水
const remainingAmount = todayStats.dailyGoal - todayStats.totalAmount;
const completionPercentage = Math.round(todayStats.completionRate);
// 根据完成度生成不同的提醒消息
let title = '💧 该喝水啦!';
let body = '';
if (completionPercentage < 30) {
// 完成度低于30%
const encouragingMessages = [
`${userName},今天才喝了${completionPercentage}%的水哦!还需要${Math.round(remainingAmount)}ml记得多补水身体会感谢您的💙`,
`${userName},水分补充进度${completionPercentage}%,再喝${Math.round(remainingAmount)}ml就更健康啦来一大杯清水吧🚰`,
`${userName},喝水进度才${completionPercentage}%呢~身体需要更多水分,还差${Math.round(remainingAmount)}ml一起加油`,
];
body = encouragingMessages[Math.floor(Math.random() * encouragingMessages.length)];
} else if (completionPercentage < 60) {
// 完成度30-60%
const moderateMessages = [
`${userName},喝水进度${completionPercentage}%,还需要${Math.round(remainingAmount)}ml哦保持这个节奏您做得很棒👍`,
`${userName},水分补充已完成${completionPercentage}%,再来${Math.round(remainingAmount)}ml就达标了继续保持💪`,
`${userName},今日饮水${completionPercentage}%完成!距离目标还有${Math.round(remainingAmount)}ml加把劲🌊`,
];
body = moderateMessages[Math.floor(Math.random() * moderateMessages.length)];
} else {
// 完成度60-99%
const almostDoneMessages = [
`${userName},喝水进度${completionPercentage}%,太棒了!最后${Math.round(remainingAmount)}ml就达成目标啦🎉`,
`${userName},已经完成${completionPercentage}%了!还有${Math.round(remainingAmount)}ml就成功了您很快就能达成今天的目标🏆`,
`${userName},水分补充进度${completionPercentage}%,就差最后一点点!再喝${Math.round(remainingAmount)}ml就胜利了🥳`,
];
body = almostDoneMessages[Math.floor(Math.random() * almostDoneMessages.length)];
}
// 发送通知
const notificationId = await notificationService.sendImmediateNotification({
title,
body,
data: {
type: 'water_reminder',
completionRate: completionPercentage,
remainingAmount: Math.round(remainingAmount),
dailyGoal: todayStats.dailyGoal,
currentAmount: todayStats.totalAmount,
url: '/statistics' // 跳转到统计页面查看详情
},
sound: true,
priority: 'normal',
});
// 记录通知发送时间
await AsyncStorage.setItem(lastNotificationKey, now.toString());
console.log(`喝水提醒通知已发送ID: ${notificationId},完成度: ${completionPercentage}%`);
return true;
} catch (error) {
console.error('检查喝水目标并发送通知失败:', error);
return false;
}
}
/**
* 发送立即喝水提醒
* @param userName 用户名
* @param message 自定义消息(可选)
*/
static async sendWaterReminder(userName: string, message?: string) {
const defaultMessage = `${userName},记得要多喝水哦!保持身体水分充足很重要~💧`;
return notificationService.sendImmediateNotification({
title: '💧 喝水提醒',
body: message || defaultMessage,
data: {
type: 'water_reminder',
url: '/statistics'
},
sound: true,
priority: 'normal',
});
}
/**
* 安排定期喝水提醒每2小时一次在9:00-21:00之间
* @param userName 用户名
* @returns 通知ID数组
*/
static async scheduleRegularWaterReminders(userName: string): Promise<string[]> {
try {
const notificationIds: string[] = [];
// 检查是否已经存在定期喝水提醒
const existingNotifications = await notificationService.getAllScheduledNotifications();
const existingWaterReminders = existingNotifications.filter(
notification =>
notification.content.data?.type === 'regular_water_reminder' &&
notification.content.data?.isRegularReminder === true
);
if (existingWaterReminders.length > 0) {
console.log('定期喝水提醒已存在,跳过重复注册');
return existingWaterReminders.map(n => n.identifier);
}
// 创建多个时间点的喝水提醒9:00-21:00每2小时一次
const reminderHours = [9, 11, 13, 15, 17, 19, 21];
for (const hour of reminderHours) {
const notificationId = await notificationService.scheduleCalendarRepeatingNotification(
{
title: '💧 定时喝水提醒',
body: `${userName},该喝水啦!记得补充水分,保持身体健康~`,
data: {
type: 'regular_water_reminder',
isRegularReminder: true,
reminderHour: hour,
url: '/statistics'
},
sound: true,
priority: 'normal',
},
{
type: Notifications.SchedulableTriggerInputTypes.DAILY,
hour: hour,
minute: 0,
}
);
notificationIds.push(notificationId);
console.log(`已安排${hour}:00的定期喝水提醒通知ID: ${notificationId}`);
}
console.log(`定期喝水提醒设置完成,共${notificationIds.length}个通知`);
return notificationIds;
} catch (error) {
console.error('设置定期喝水提醒失败:', error);
throw error;
}
}
/**
* 取消所有喝水提醒
*/
static async cancelAllWaterReminders(): Promise<void> {
try {
const notifications = await notificationService.getAllScheduledNotifications();
for (const notification of notifications) {
if (notification.content.data?.type === 'water_reminder' ||
notification.content.data?.type === 'regular_water_reminder') {
await notificationService.cancelNotification(notification.identifier);
console.log('已取消喝水提醒:', notification.identifier);
}
}
} catch (error) {
console.error('取消喝水提醒失败:', error);
throw error;
}
}
}
/**
* 通用通知辅助函数
*/
export class GeneralNotificationHelpers {
/**
* 发送欢迎通知
*/
static async sendWelcomeNotification(userName: string) {
return notificationService.sendImmediateNotification({
title: '欢迎使用',
body: `${userName},欢迎来到普拉提世界!开始您的健康之旅吧`,
data: { type: 'welcome' },
sound: true,
priority: 'normal',
});
}
/**
* 发送成就通知
*/
static async sendAchievementNotification(userName: string, achievement: string) {
return notificationService.sendImmediateNotification({
title: '新成就',
body: `${userName},恭喜您获得了新成就:${achievement}`,
data: { type: 'achievement', achievement },
sound: true,
priority: 'high',
});
}
/**
* 发送系统维护通知
*/
static async sendMaintenanceNotification(message: string) {
return notificationService.sendImmediateNotification({
title: '系统通知',
body: message,
data: { type: 'maintenance' },
sound: true,
priority: 'high',
});
}
/**
* 取消特定类型的通知
*/
static async cancelNotificationsByType(type: string) {
const notifications = await notificationService.getAllScheduledNotifications();
for (const notification of notifications) {
if (notification.content.data?.type === type) {
await notificationService.cancelNotification(notification.identifier);
}
}
}
/**
* 批量发送通知
*/
static async sendBatchNotifications(notifications: NotificationData[]) {
const results = [];
for (const notification of notifications) {
try {
const id = await notificationService.sendImmediateNotification(notification);
results.push({ success: true, id, notification });
} catch (error) {
results.push({ success: false, error, notification });
}
}
return results;
}
}
/**
* 站立提醒通知助手
*/
export class StandReminderHelpers {
/**
* 检查站立状态并发送提醒通知
*/
static async checkStandStatusAndNotify(userName: string): Promise<boolean> {
try {
console.log('检查站立状态并发送提醒通知...');
// 动态导入健康工具,避免循环依赖
const { getCurrentHourStandStatus } = await import('@/utils/health');
// 获取当前小时站立状态
const standStatus = await getCurrentHourStandStatus();
console.log('当前站立状态:', standStatus);
// 如果已经站立过,不需要提醒
if (standStatus.hasStood) {
console.log('用户当前小时已经站立,无需提醒');
return false;
}
// 检查时间范围(工作时间内提醒,避免深夜或清晨打扰)
const currentHour = new Date().getHours();
if (currentHour < 9 || currentHour >= 22) {
console.log(`当前时间${currentHour}点,不在站立提醒时间范围内`);
return false;
}
// 检查是否启用了通知
if (!(await getNotificationEnabled())) {
console.log('用户未启用通知功能,跳过站立提醒');
return false;
}
// 生成提醒消息
const reminderMessage = this.generateStandReminderMessage(userName, standStatus.standHours, standStatus.standHoursGoal);
// 发送站立提醒通知
await notificationService.sendImmediateNotification({
title: '站立提醒',
body: reminderMessage,
data: {
type: 'stand_reminder',
currentStandHours: standStatus.standHours,
standHoursGoal: standStatus.standHoursGoal,
timestamp: Date.now()
},
sound: true,
priority: 'normal',
});
console.log('站立提醒通知发送成功');
return true;
} catch (error) {
console.error('检查站立状态并发送提醒失败:', error);
return false;
}
}
/**
* 生成站立提醒消息
*/
private static generateStandReminderMessage(userName: string, currentStandHours: number, goalHours: number): string {
const currentHour = new Date().getHours();
const progress = Math.round((currentStandHours / goalHours) * 100);
const messages = [
`${userName},该站起来活动一下了!当前已完成${progress}%的站立目标`,
`${userName},久坐伤身,起来走走吧~已站立${currentStandHours}/${goalHours}小时`,
`${userName},站立一会儿对健康有益,目前进度${currentStandHours}/${goalHours}小时`,
`${userName},记得起身活动哦!今日站立进度${progress}%`
];
// 根据时间选择不同的消息
const messageIndex = currentHour % messages.length;
return messages[messageIndex];
}
/**
* 取消所有站立提醒通知
*/
static async cancelStandReminders(): Promise<void> {
try {
await GeneralNotificationHelpers.cancelNotificationsByType('stand_reminder');
console.log('已取消所有站立提醒通知');
} catch (error) {
console.error('取消站立提醒通知失败:', error);
}
}
}
/**
* 每日总结通知助手
*/
export class DailySummaryNotificationHelpers {
/**
* 获取当日数据汇总
*/
static async getDailySummaryData(date: string = new Date().toISOString().split('T')[0]) {
try {
console.log('获取每日汇总数据:', date);
// 动态导入相关服务,避免循环依赖
const { getDietRecords } = await import('@/services/dietRecords');
const { getDailyMoodCheckins } = await import('@/services/moodCheckins');
const { getWaterRecords } = await import('@/services/waterRecords');
const { workoutsApi } = await import('@/services/workoutsApi');
// 设置日期范围
const startDate = new Date(`${date}T00:00:00.000Z`).toISOString();
const endDate = new Date(`${date}T23:59:59.999Z`).toISOString();
// 并行获取各项数据
const [dietData, moodData, waterData, workoutData] = await Promise.allSettled([
getDietRecords({ startDate, endDate, limit: 100 }),
getDailyMoodCheckins(date),
getWaterRecords({ date, limit: 100 }),
workoutsApi.getTodayWorkout()
]);
// 处理饮食数据
const dietSummary = {
hasRecords: false,
mealCount: 0,
recordCount: 0
};
if (dietData.status === 'fulfilled' && dietData.value.records.length > 0) {
dietSummary.hasRecords = true;
dietSummary.recordCount = dietData.value.records.length;
dietSummary.mealCount = new Set(dietData.value.records.map(r => r.mealType)).size;
}
// 处理心情数据
const moodSummary = {
hasRecords: false,
recordCount: 0,
latestMood: null as string | null
};
if (moodData.status === 'fulfilled' && moodData.value.length > 0) {
moodSummary.hasRecords = true;
moodSummary.recordCount = moodData.value.length;
moodSummary.latestMood = moodData.value[0].moodType;
}
// 处理饮水数据
const waterSummary = {
hasRecords: false,
recordCount: 0,
totalAmount: 0,
completionRate: 0
};
if (waterData.status === 'fulfilled' && waterData.value.records.length > 0) {
waterSummary.hasRecords = true;
waterSummary.recordCount = waterData.value.records.length;
waterSummary.totalAmount = waterData.value.records.reduce((sum, r) => sum + r.amount, 0);
// 假设默认目标是2000ml实际应该从用户设置获取
const dailyGoal = 2000;
waterSummary.completionRate = Math.round((waterSummary.totalAmount / dailyGoal) * 100);
}
// 处理锻炼数据
const workoutSummary = {
hasWorkout: false,
isCompleted: false,
exerciseCount: 0,
completedCount: 0,
duration: 0
};
if (workoutData.status === 'fulfilled' && workoutData.value) {
workoutSummary.hasWorkout = true;
workoutSummary.isCompleted = workoutData.value.status === 'completed';
workoutSummary.exerciseCount = workoutData.value.exercises?.length || 0;
workoutSummary.completedCount = workoutData.value.exercises?.filter(e => e.status === 'completed').length || 0;
if (workoutData.value.completedAt && workoutData.value.startedAt) {
workoutSummary.duration = Math.round((new Date(workoutData.value.completedAt).getTime() - new Date(workoutData.value.startedAt).getTime()) / (1000 * 60));
}
}
return {
date,
diet: dietSummary,
mood: moodSummary,
water: waterSummary,
workout: workoutSummary
};
} catch (error) {
console.error('获取每日汇总数据失败:', error);
throw error;
}
}
/**
* 生成每日总结推送消息
*/
static generateDailySummaryMessage(userName: string, summaryData: any): { title: string; body: string } {
const { diet, mood, water, workout } = summaryData;
// 计算完成的项目数量
const completedItems = [];
const encouragementItems = [];
// 饮食记录检查
if (diet.hasRecords) {
completedItems.push(`记录了${diet.mealCount}餐饮食`);
} else {
encouragementItems.push('饮食记录');
}
// 心情记录检查
if (mood.hasRecords) {
completedItems.push(`记录了心情状态`);
} else {
encouragementItems.push('心情记录');
}
// 饮水记录检查
if (water.hasRecords) {
if (water.completionRate >= 80) {
completedItems.push(`完成了${water.completionRate}%的饮水目标`);
} else {
completedItems.push(`喝水${water.completionRate}%`);
encouragementItems.push('多喝水');
}
} else {
encouragementItems.push('饮水记录');
}
// 锻炼记录检查
if (workout.hasWorkout) {
if (workout.isCompleted) {
completedItems.push(`完成了${workout.duration}分钟锻炼`);
} else {
completedItems.push(`开始了锻炼训练`);
encouragementItems.push('完成锻炼');
}
} else {
encouragementItems.push('运动锻炼');
}
// 生成标题和内容
let title = '今日健康总结';
let body = '';
if (completedItems.length > 0) {
if (completedItems.length >= 3) {
// 完成度很高的鼓励
const titles = ['今天表现棒极了!', '健康习惯养成中!', '今日收获满满!'];
title = titles[Math.floor(Math.random() * titles.length)];
body = `${userName},今天您${completedItems.join('、')},真的很棒!`;
if (encouragementItems.length > 0) {
body += `明天在${encouragementItems.join('、')}方面再加把劲哦~`;
} else {
body += '继续保持这样的好习惯!🌟';
}
} else {
// 中等完成度的鼓励
title = '今日健康小结';
body = `${userName},今天您${completedItems.join('、')},已经很不错了!`;
if (encouragementItems.length > 0) {
body += `明天记得关注一下${encouragementItems.join('、')},让健康生活更完整~`;
}
}
} else {
// 完成度较低的温柔提醒
const titles = ['明天是新的开始', '健康从每一天开始', '小步前进也是进步'];
title = titles[Math.floor(Math.random() * titles.length)];
body = `${userName},今天可能比较忙碌。明天记得关注${encouragementItems.slice(0, 2).join('和')},每一个小改变都是向健康生活迈进的一步~💪`;
}
return { title, body };
}
/**
* 发送每日总结推送
*/
static async sendDailySummaryNotification(userName: string, date?: string): Promise<boolean> {
try {
console.log('开始发送每日总结推送...');
// 检查是否启用了通知
if (!(await getNotificationEnabled())) {
console.log('用户未启用通知功能,跳过每日总结推送');
return false;
}
// 获取当日数据汇总
const summaryData = await this.getDailySummaryData(date);
console.log('每日汇总数据:', summaryData);
// 生成推送消息
const { title, body } = this.generateDailySummaryMessage(userName, summaryData);
// 发送通知
await notificationService.sendImmediateNotification({
title,
body,
data: {
type: 'daily_summary',
date: summaryData.date,
summaryData,
url: '/statistics' // 跳转到统计页面
},
sound: true,
priority: 'normal',
});
console.log('每日总结推送发送成功');
return true;
} catch (error) {
console.error('发送每日总结推送失败:', error);
return false;
}
}
/**
* 安排每日总结推送每天晚上9点
*/
static async scheduleDailySummaryNotification(
userName: string,
hour: number = 21,
minute: number = 0
): Promise<string | null> {
try {
// 检查是否已经存在每日总结提醒
const existingNotifications = await notificationService.getAllScheduledNotifications();
const existingSummaryReminder = existingNotifications.find(
notification =>
notification.content.data?.type === 'daily_summary_reminder' &&
notification.content.data?.isDailyReminder === true
);
if (existingSummaryReminder) {
console.log('每日总结推送已存在,跳过重复注册:', existingSummaryReminder.identifier);
return existingSummaryReminder.identifier;
}
// 创建每日总结推送通知
const notificationId = await notificationService.scheduleCalendarRepeatingNotification(
{
title: '今日健康总结',
body: `${userName},来看看今天的健康生活总结吧~每一份记录都是成长的足迹!✨`,
data: {
type: 'daily_summary_reminder',
isDailyReminder: true,
url: '/statistics'
},
sound: true,
priority: 'normal',
},
{
type: Notifications.SchedulableTriggerInputTypes.DAILY,
hour: hour,
minute: minute,
}
);
console.log('每日总结推送已安排ID:', notificationId);
return notificationId;
} catch (error) {
console.error('安排每日总结推送失败:', error);
throw error;
}
}
/**
* 取消每日总结推送
*/
static async cancelDailySummaryNotification(): Promise<void> {
try {
const notifications = await notificationService.getAllScheduledNotifications();
for (const notification of notifications) {
if (notification.content.data?.type === 'daily_summary_reminder' &&
notification.content.data?.isDailyReminder === true) {
await notificationService.cancelNotification(notification.identifier);
console.log('已取消每日总结推送:', notification.identifier);
}
}
} catch (error) {
console.error('取消每日总结推送失败:', error);
throw error;
}
}
}
/**
* 通知模板
*/
export const NotificationTemplates = {
workout: {
start: (userName: string) => ({
title: '运动时间到',
body: `${userName},该开始今天的普拉提训练了!`,
data: { type: 'workout_start' },
sound: true,
priority: 'high' as const,
}),
complete: (userName: string, duration: number) => ({
title: '运动完成',
body: `${userName},恭喜您完成了${duration}分钟的训练!`,
data: { type: 'workout_complete' },
sound: true,
priority: 'normal' as const,
}),
},
goal: {
achievement: (userName: string, goalName: string) => ({
title: '目标达成',
body: `${userName},恭喜您达成了目标:${goalName}`,
data: { type: 'goal_achievement', goalName },
sound: true,
priority: 'high' as const,
}),
progress: (userName: string, goalName: string, progress: number) => ({
title: '目标进度',
body: `${userName},您的目标"${goalName}"已完成${progress}%`,
data: { type: 'goal_progress', goalName, progress },
sound: true,
priority: 'normal' as const,
}),
},
mood: {
reminder: (userName: string) => ({
title: '心情打卡',
body: `${userName},记得记录今天的心情状态哦`,
data: { type: 'mood_checkin_reminder' },
sound: true,
priority: 'normal' as const,
}),
},
nutrition: {
reminder: (userName: string, meal: string) => {
const mealTypeMap: Record<string, string> = {
'早餐': 'breakfast',
'午餐': 'lunch',
'晚餐': 'dinner',
'加餐': 'snack'
};
const coachUrl = buildCoachDeepLink({
action: 'diet',
subAction: 'card',
meal: mealTypeMap[meal] as 'breakfast' | 'lunch' | 'dinner' | 'snack'
});
return {
title: `${meal}提醒`,
body: `${userName},记得记录您的${meal}情况`,
data: {
type: 'meal_reminder',
meal,
url: coachUrl
},
sound: true,
priority: 'normal' as const,
};
},
lunch: (userName: string) => {
const coachUrl = buildCoachDeepLink({
action: 'diet',
subAction: 'card',
meal: 'lunch'
});
return {
title: '午餐记录提醒',
body: `${userName},记得记录今天的午餐情况哦!`,
data: {
type: 'lunch_reminder',
meal: '午餐',
url: coachUrl
},
sound: true,
priority: 'normal' as const,
};
},
},
water: {
reminder: (userName: string, completionRate: number, remainingAmount: number) => ({
title: '💧 该喝水啦!',
body: `${userName},今日喝水进度${completionRate}%,还需要${Math.round(remainingAmount)}ml记得补充水分`,
data: {
type: 'water_reminder',
completionRate,
remainingAmount: Math.round(remainingAmount),
url: '/statistics'
},
sound: true,
priority: 'normal' as const,
}),
regular: (userName: string) => ({
title: '💧 定时喝水提醒',
body: `${userName},该喝水啦!记得补充水分,保持身体健康~`,
data: {
type: 'regular_water_reminder',
url: '/statistics'
},
sound: true,
priority: 'normal' as const,
}),
achievement: (userName: string) => ({
title: '🎉 喝水目标达成!',
body: `${userName},恭喜您完成了今天的喝水目标!继续保持健康的饮水习惯~`,
data: {
type: 'water_achievement',
url: '/statistics'
},
sound: true,
priority: 'high' as const,
}),
},
dailySummary: {
reminder: (userName: string) => ({
title: '今日健康总结',
body: `${userName},来看看今天的健康生活总结吧~每一份记录都是成长的足迹!✨`,
data: {
type: 'daily_summary_reminder',
url: '/statistics'
},
sound: true,
priority: 'normal' as const,
}),
highCompletion: (userName: string, completedItems: string[]) => ({
title: '今天表现棒极了!',
body: `${userName},今天您${completedItems.join('、')},真的很棒!继续保持这样的好习惯!🌟`,
data: {
type: 'daily_summary',
completedItems,
url: '/statistics'
},
sound: true,
priority: 'normal' as const,
}),
mediumCompletion: (userName: string, completedItems: string[], encouragementItems: string[]) => ({
title: '今日健康小结',
body: `${userName},今天您${completedItems.join('、')},已经很不错了!明天记得关注一下${encouragementItems.join('、')},让健康生活更完整~`,
data: {
type: 'daily_summary',
completedItems,
encouragementItems,
url: '/statistics'
},
sound: true,
priority: 'normal' as const,
}),
lowCompletion: (userName: string, encouragementItems: string[]) => ({
title: '明天是新的开始',
body: `${userName},今天可能比较忙碌。明天记得关注${encouragementItems.slice(0, 2).join('和')},每一个小改变都是向健康生活迈进的一步~💪`,
data: {
type: 'daily_summary',
encouragementItems,
url: '/statistics'
},
sound: true,
priority: 'normal' as const,
}),
},
};