feat(notifications): 新增晚餐和心情提醒功能,支持HRV压力检测和后台处理
- 新增晚餐提醒(18:00)和心情提醒(21:00)的定时通知 - 实现基于HRV数据的压力检测和智能鼓励通知 - 添加后台任务处理支持,修改iOS后台模式为processing - 优化营养记录页面使用Redux状态管理,支持实时数据更新 - 重构卡路里计算公式,移除目标卡路里概念,改为基代+运动-饮食 - 新增营养目标动态计算功能,基于用户身体数据智能推荐 - 完善通知点击跳转逻辑,支持多种提醒类型的路由处理
This commit is contained in:
@@ -12,12 +12,12 @@ export function buildCoachDeepLink(params: {
|
||||
}): 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;
|
||||
}
|
||||
@@ -286,48 +286,6 @@ export class GoalNotificationHelpers {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 心情相关的通知辅助函数
|
||||
*/
|
||||
export class MoodNotificationHelpers {
|
||||
/**
|
||||
* 发送心情打卡提醒
|
||||
*/
|
||||
static async sendMoodCheckinReminder(userName: string) {
|
||||
return notificationService.sendImmediateNotification({
|
||||
title: '心情打卡',
|
||||
body: `${userName},记得记录今天的心情状态哦`,
|
||||
data: { type: 'mood_checkin_reminder' },
|
||||
sound: true,
|
||||
priority: 'normal',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排每日心情打卡提醒
|
||||
*/
|
||||
static async scheduleDailyMoodReminder(userName: string, hour: number = 20, 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_mood_reminder' },
|
||||
sound: true,
|
||||
priority: 'normal',
|
||||
},
|
||||
{ days: 1 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 营养相关的通知辅助函数
|
||||
*/
|
||||
@@ -423,8 +381,8 @@ export class NutritionNotificationHelpers {
|
||||
return notificationService.sendImmediateNotification({
|
||||
title: '午餐记录提醒',
|
||||
body: `${userName},记得记录今天的午餐情况哦!`,
|
||||
data: {
|
||||
type: 'lunch_reminder',
|
||||
data: {
|
||||
type: 'lunch_reminder',
|
||||
meal: '午餐',
|
||||
url: coachUrl
|
||||
},
|
||||
@@ -453,6 +411,82 @@ export class NutritionNotificationHelpers {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排每日晚餐提醒
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排营养记录提醒
|
||||
*/
|
||||
@@ -478,10 +512,10 @@ export class NutritionNotificationHelpers {
|
||||
// 构建深度链接
|
||||
const mealTypeMap: Record<string, string> = {
|
||||
'早餐': 'breakfast',
|
||||
'午餐': 'lunch',
|
||||
'午餐': 'lunch',
|
||||
'晚餐': 'dinner'
|
||||
};
|
||||
|
||||
|
||||
const coachUrl = buildCoachDeepLink({
|
||||
action: 'diet',
|
||||
subAction: 'card',
|
||||
@@ -492,8 +526,8 @@ export class NutritionNotificationHelpers {
|
||||
{
|
||||
title: `${mealTime.meal}提醒`,
|
||||
body: `${userName},记得记录您的${mealTime.meal}情况`,
|
||||
data: {
|
||||
type: 'meal_reminder',
|
||||
data: {
|
||||
type: 'meal_reminder',
|
||||
meal: mealTime.meal,
|
||||
url: coachUrl
|
||||
},
|
||||
@@ -510,6 +544,102 @@ export class NutritionNotificationHelpers {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 心情相关的通知辅助函数
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用通知辅助函数
|
||||
*/
|
||||
@@ -634,11 +764,11 @@ export const NotificationTemplates = {
|
||||
reminder: (userName: string, meal: string) => {
|
||||
const mealTypeMap: Record<string, string> = {
|
||||
'早餐': 'breakfast',
|
||||
'午餐': 'lunch',
|
||||
'午餐': 'lunch',
|
||||
'晚餐': 'dinner',
|
||||
'加餐': 'snack'
|
||||
};
|
||||
|
||||
|
||||
const coachUrl = buildCoachDeepLink({
|
||||
action: 'diet',
|
||||
subAction: 'card',
|
||||
@@ -648,8 +778,8 @@ export const NotificationTemplates = {
|
||||
return {
|
||||
title: `${meal}提醒`,
|
||||
body: `${userName},记得记录您的${meal}情况`,
|
||||
data: {
|
||||
type: 'meal_reminder',
|
||||
data: {
|
||||
type: 'meal_reminder',
|
||||
meal,
|
||||
url: coachUrl
|
||||
},
|
||||
@@ -667,8 +797,8 @@ export const NotificationTemplates = {
|
||||
return {
|
||||
title: '午餐记录提醒',
|
||||
body: `${userName},记得记录今天的午餐情况哦!`,
|
||||
data: {
|
||||
type: 'lunch_reminder',
|
||||
data: {
|
||||
type: 'lunch_reminder',
|
||||
meal: '午餐',
|
||||
url: coachUrl
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user