diff --git a/src/challenges/challenges.service.ts b/src/challenges/challenges.service.ts index 52ccbde..3b84b6d 100644 --- a/src/challenges/challenges.service.ts +++ b/src/challenges/challenges.service.ts @@ -164,7 +164,7 @@ export class ChallengesService { image: challenge.image, periodLabel: challenge.periodLabel, durationLabel: challenge.durationLabel, - requirementLabel: challenge.requirementLabel, + requirementLabel: challenge.requirementLabel || '', status, unit: challenge.progressUnit, startAt: new Date(challenge.startAt).getTime(), @@ -278,7 +278,7 @@ export class ChallengesService { image: challenge.image, periodLabel: challenge.periodLabel, durationLabel: challenge.durationLabel, - requirementLabel: challenge.requirementLabel, + requirementLabel: challenge.requirementLabel || '', summary: challenge.summary, rankingDescription: challenge.rankingDescription, highlightTitle: challenge.highlightTitle, @@ -1205,7 +1205,7 @@ export class ChallengesService { endAt: new Date(challenge.endAt).getTime(), periodLabel: challenge.periodLabel, durationLabel: challenge.durationLabel, - requirementLabel: challenge.requirementLabel, + requirementLabel: challenge.requirementLabel || '', summary: challenge.summary, targetValue: challenge.targetValue, progressUnit: challenge.progressUnit, diff --git a/src/challenges/dto/create-custom-challenge.dto.ts b/src/challenges/dto/create-custom-challenge.dto.ts index bfeb76f..902d717 100644 --- a/src/challenges/dto/create-custom-challenge.dto.ts +++ b/src/challenges/dto/create-custom-challenge.dto.ts @@ -34,10 +34,10 @@ export class CreateCustomChallengeDto { @Max(1000) targetValue: number; - @ApiProperty({ description: '最少打卡天数', example: 21, minimum: 1, maximum: 365 }) + @ApiProperty({ description: '最少打卡天数', example: 21, minimum: 1, maximum: 1000 }) @IsNumber() @Min(1) - @Max(365) + @Max(1000) minimumCheckInDays: number; @ApiProperty({ description: '持续时间标签', example: '持续21天' }) @@ -48,7 +48,7 @@ export class CreateCustomChallengeDto { @ApiProperty({ description: '挑战要求标签', example: '每日喝水8杯' }) @IsString() - @IsNotEmpty() + @IsOptional() @MaxLength(255) requirementLabel: string; diff --git a/src/challenges/dto/update-custom-challenge.dto.ts b/src/challenges/dto/update-custom-challenge.dto.ts index 764f119..ee339d9 100644 --- a/src/challenges/dto/update-custom-challenge.dto.ts +++ b/src/challenges/dto/update-custom-challenge.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsString, IsOptional, IsBoolean, MaxLength, IsNumber, Min, Max } from 'class-validator'; +import { IsString, IsOptional, IsBoolean, MaxLength, IsNumber, Min, Max, IsEnum } from 'class-validator'; export class UpdateCustomChallengeDto { @ApiProperty({ description: '挑战标题', required: false }) diff --git a/src/challenges/models/challenge.model.ts b/src/challenges/models/challenge.model.ts index 2f6f052..44faa24 100644 --- a/src/challenges/models/challenge.model.ts +++ b/src/challenges/models/challenge.model.ts @@ -15,6 +15,7 @@ export enum ChallengeType { MOOD = 'mood', SLEEP = 'sleep', WEIGHT = 'weight', + CUSTOM = 'custom', } export enum ChallengeSource { @@ -84,10 +85,10 @@ export class Challenge extends Model { @Column({ type: DataType.STRING(255), - allowNull: false, + allowNull: true, comment: '挑战要求标签,例如「每日练习 1 次」', }) - declare requirementLabel: string; + declare requirementLabel?: string; @Column({ type: DataType.TEXT, @@ -112,7 +113,7 @@ export class Challenge extends Model { declare progressUnit: string; @Column({ - type: DataType.INTEGER, + type: DataType.INTEGER.UNSIGNED, allowNull: false, defaultValue: 0, comment: '最低打卡天数,用于判断挑战成功', @@ -148,7 +149,7 @@ export class Challenge extends Model { declare ctaLabel: string; @Column({ - type: DataType.ENUM('water', 'exercise', 'diet', 'mood', 'sleep', 'weight'), + type: DataType.ENUM('water', 'exercise', 'diet', 'mood', 'sleep', 'weight', 'custom'), allowNull: false, defaultValue: ChallengeType.WATER, comment: '挑战类型', diff --git a/src/push-notifications/challenge-reminder.service.ts b/src/push-notifications/challenge-reminder.service.ts index 09ef42b..5306af8 100644 --- a/src/push-notifications/challenge-reminder.service.ts +++ b/src/push-notifications/challenge-reminder.service.ts @@ -216,7 +216,7 @@ export class ChallengeReminderService { } // 获取鼓励文案 - const template = getEncouragementTemplate(participant.challenge.type); + const template = getEncouragementTemplate(participant.challenge.type, participant.challenge.title); // 发送推送 const result = await this.pushNotificationsService.sendBatchNotificationToDevices({ @@ -281,7 +281,7 @@ export class ChallengeReminderService { // 获取邀请文案 const template = reminderType === ReminderType.GENERAL_INVITATION ? getGeneralInvitationTemplate() - : getInvitationTemplate(randomChallenge.type); + : getInvitationTemplate(randomChallenge.type, randomChallenge.title); // 发送推送 const result = await this.pushNotificationsService.sendBatchNotificationToDevices({ diff --git a/src/push-notifications/templates/challenge-templates.ts b/src/push-notifications/templates/challenge-templates.ts index 949039e..7ca4c66 100644 --- a/src/push-notifications/templates/challenge-templates.ts +++ b/src/push-notifications/templates/challenge-templates.ts @@ -46,6 +46,9 @@ export const ENCOURAGEMENT_TEMPLATES = { { title: '团队体重挑战', body: '今天体重挑战群里又有很多人完成了目标!一起健康生活!' }, { title: '健康生活记录', body: '挑战者们都在坚持健康饮食+适量运动,你也是其中一员!' }, ], + [ChallengeType.CUSTOM]: [ + // 自定义挑战使用动态模板,不在此处定义 + ], }; /** @@ -82,6 +85,9 @@ export const INVITATION_TEMPLATES = { { title: '健康体重挑战', body: '21天体重管理,见证自己的变化!' }, { title: '体重目标挑战', body: '科学管理体重,享受健康生活!' }, ], + [ChallengeType.CUSTOM]: [ + // 自定义挑战使用动态模板,不在此处定义 + ], }; /** @@ -106,7 +112,12 @@ export function getRandomTemplate(templates: T[]): T { /** * 根据挑战类型获取鼓励文案 */ -export function getEncouragementTemplate(challengeType: ChallengeType) { +export function getEncouragementTemplate(challengeType: ChallengeType, challengeTitle?: string) { + // 自定义挑战使用动态模板 + if (challengeType === ChallengeType.CUSTOM && challengeTitle) { + return getCustomEncouragementTemplate(challengeTitle); + } + const templates = ENCOURAGEMENT_TEMPLATES[challengeType] || ENCOURAGEMENT_TEMPLATES[ChallengeType.EXERCISE]; return getRandomTemplate(templates); } @@ -114,7 +125,12 @@ export function getEncouragementTemplate(challengeType: ChallengeType) { /** * 根据挑战类型获取邀请文案 */ -export function getInvitationTemplate(challengeType: ChallengeType) { +export function getInvitationTemplate(challengeType: ChallengeType, challengeTitle?: string) { + // 自定义挑战使用动态模板 + if (challengeType === ChallengeType.CUSTOM && challengeTitle) { + return getCustomInvitationTemplate(challengeTitle); + } + const templates = INVITATION_TEMPLATES[challengeType] || INVITATION_TEMPLATES[ChallengeType.EXERCISE]; return getRandomTemplate(templates); } @@ -124,4 +140,30 @@ export function getInvitationTemplate(challengeType: ChallengeType) { */ export function getGeneralInvitationTemplate() { return getRandomTemplate(GENERAL_INVITATION_TEMPLATES); +} + +/** + * 生成自定义挑战的鼓励文案模板 + */ +export function getCustomEncouragementTemplate(challengeTitle: string) { + const templates = [ + { title: `${challengeTitle}进行中`, body: `今天已有多人参与「${challengeTitle}」!你也要加油哦!` }, + { title: `${challengeTitle}提醒`, body: `挑战伙伴们都在坚持「${challengeTitle}」,每一步努力都值得!` }, + { title: `${challengeTitle}打卡`, body: `看到很多挑战者都在参与「${challengeTitle}」,你也是其中一员!` }, + { title: `团队挑战`, body: `「${challengeTitle}」挑战群里又有很多人完成了目标!别掉队,一起加油!` }, + { title: `挑战分享`, body: `挑战者们都在分享「${challengeTitle}」的心得,你今天打卡了吗?` }, + ]; + return getRandomTemplate(templates); +} + +/** + * 生成自定义挑战的邀请文案模板 + */ +export function getCustomInvitationTemplate(challengeTitle: string) { + const templates = [ + { title: `${challengeTitle}邀请`, body: `加入「${challengeTitle}」,一起挑战自我,收获成长!` }, + { title: `挑战邀请`, body: `「${challengeTitle}」正在进行中,快来加入我们吧!` }, + { title: `一起挑战`, body: `开启「${challengeTitle}」之旅,遇见更好的自己!` }, + ]; + return getRandomTemplate(templates); } \ No newline at end of file