feat(notifications): 新增晚餐和心情提醒功能,支持HRV压力检测和后台处理

- 新增晚餐提醒(18:00)和心情提醒(21:00)的定时通知
- 实现基于HRV数据的压力检测和智能鼓励通知
- 添加后台任务处理支持,修改iOS后台模式为processing
- 优化营养记录页面使用Redux状态管理,支持实时数据更新
- 重构卡路里计算公式,移除目标卡路里概念,改为基代+运动-饮食
- 新增营养目标动态计算功能,基于用户身体数据智能推荐
- 完善通知点击跳转逻辑,支持多种提醒类型的路由处理
This commit is contained in:
richarjiang
2025-09-01 10:29:13 +08:00
parent fe634ba258
commit a34ca556e8
12 changed files with 867 additions and 189 deletions

View File

@@ -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
},