From bac75f82baf3d5b17375ad16b8f9dbdda5eec154 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Fri, 14 Nov 2025 17:26:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(challenges):=20=E5=9C=A8=E6=8C=91=E6=88=98?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E5=92=8C=E8=AF=A6=E6=83=85=E4=B8=AD=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=8B=8B=E7=AB=A0=E4=BF=A1=E6=81=AF=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为睡眠挑战类型添加勋章信息支持,在挑战列表和详情接口中返回 sleepChallengeMonth 勋章配置数据。 - 在 ChallengesModule 中注册 BadgeConfig 模型 - 在 ChallengesService 中注入 BadgeConfig 仓库 - 查询列表时,若存在睡眠挑战则预加载勋章配置 - 查询详情时,若为睡眠挑战则附加勋章信息 - 在 DTO 中新增 BadgeInfoDto 接口定义勋章数据结构 - 仅对激活状态的 sleepChallengeMonth 勋章进行查询和展示 --- src/challenges/challenges.module.ts | 3 +- src/challenges/challenges.service.ts | 42 ++++++++++++++++++++++ src/challenges/dto/challenge-detail.dto.ts | 9 +++++ src/challenges/dto/challenge-list.dto.ts | 9 +++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/challenges/challenges.module.ts b/src/challenges/challenges.module.ts index cc375c4..66ea14b 100644 --- a/src/challenges/challenges.module.ts +++ b/src/challenges/challenges.module.ts @@ -7,10 +7,11 @@ import { ChallengeParticipant } from './models/challenge-participant.model'; import { ChallengeProgressReport } from './models/challenge-progress-report.model'; import { UsersModule } from '../users/users.module'; import { User } from '../users/models/user.model'; +import { BadgeConfig } from '../users/models/badge-config.model'; @Module({ imports: [ - SequelizeModule.forFeature([Challenge, ChallengeParticipant, ChallengeProgressReport, User]), + SequelizeModule.forFeature([Challenge, ChallengeParticipant, ChallengeProgressReport, User, BadgeConfig]), UsersModule, ], controllers: [ChallengesController], diff --git a/src/challenges/challenges.service.ts b/src/challenges/challenges.service.ts index b46cbd2..08630ee 100644 --- a/src/challenges/challenges.service.ts +++ b/src/challenges/challenges.service.ts @@ -15,6 +15,7 @@ import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import { Logger as WinstonLogger } from 'winston'; import { BadgeService } from '../users/services/badge.service'; import { BadgeSource } from '../users/models/user-badge.model'; +import { BadgeConfig } from '../users/models/badge-config.model'; @Injectable() export class ChallengesService { @@ -28,6 +29,8 @@ export class ChallengesService { private readonly participantModel: typeof ChallengeParticipant, @InjectModel(ChallengeProgressReport) private readonly progressReportModel: typeof ChallengeProgressReport, + @InjectModel(BadgeConfig) + private readonly badgeConfigModel: typeof BadgeConfig, private readonly badgeService: BadgeService, ) { } @@ -103,6 +106,25 @@ export class ChallengesService { } } + // 🎖️ 查询 sleepChallengeMonth 勋章信息(仅在有睡眠挑战时查询) + const hasSleepChallenge = challengesWithStatus.some(({ challenge }) => challenge.type === ChallengeType.SLEEP); + let sleepBadge: ChallengeListItemDto['badge'] = undefined; + if (hasSleepChallenge) { + const badgeConfig = await this.badgeConfigModel.findOne({ + where: { code: 'sleepChallengeMonth', isActive: true }, + }); + + if (badgeConfig) { + sleepBadge = { + code: badgeConfig.code, + name: badgeConfig.name, + description: badgeConfig.description, + imageUrl: badgeConfig.imageUrl, + category: badgeConfig.category, + }; + } + } + return challengesWithStatus.map(({ challenge, status }) => { const completionTarget = challenge.minimumCheckInDays const participation = participationMap.get(challenge.id); @@ -130,6 +152,7 @@ export class ChallengesService { progress, isJoined: Boolean(participation), type: challenge.type, + badge: challenge.type === ChallengeType.SLEEP ? sleepBadge : undefined, }; }); } @@ -198,6 +221,24 @@ export class ChallengesService { const userRank = participation ? await this.calculateUserRank(challengeId, participation) : undefined; + // 🎖️ 如果是睡眠挑战,获取 sleepChallengeMonth 勋章信息 + let badge: ChallengeDetailDto['badge'] = undefined; + if (challenge.type === ChallengeType.SLEEP) { + const badgeConfig = await this.badgeConfigModel.findOne({ + where: { code: 'sleepChallengeMonth', isActive: true }, + }); + + if (badgeConfig) { + badge = { + code: badgeConfig.code, + name: badgeConfig.name, + description: badgeConfig.description, + imageUrl: badgeConfig.imageUrl, + category: badgeConfig.category, + }; + } + } + return { id: challenge.id, title: challenge.title, @@ -217,6 +258,7 @@ export class ChallengesService { userRank, unit: challenge.progressUnit, type: challenge.type, + badge, }; } diff --git a/src/challenges/dto/challenge-detail.dto.ts b/src/challenges/dto/challenge-detail.dto.ts index 8e29a0a..7515657 100644 --- a/src/challenges/dto/challenge-detail.dto.ts +++ b/src/challenges/dto/challenge-detail.dto.ts @@ -1,6 +1,14 @@ import { ChallengeProgressDto, RankingItemDto } from './challenge-progress.dto'; import { ChallengeType } from '../models/challenge.model'; +export interface BadgeInfoDto { + code: string; + name: string; + description: string; + imageUrl: string; + category: string; +} + export interface ChallengeDetailDto { id: string; title: string; @@ -20,4 +28,5 @@ export interface ChallengeDetailDto { userRank?: number; type: ChallengeType; unit: string; + badge?: BadgeInfoDto; } diff --git a/src/challenges/dto/challenge-list.dto.ts b/src/challenges/dto/challenge-list.dto.ts index 43173eb..ad16910 100644 --- a/src/challenges/dto/challenge-list.dto.ts +++ b/src/challenges/dto/challenge-list.dto.ts @@ -1,6 +1,14 @@ import { ChallengeStatus, ChallengeType } from '../models/challenge.model'; import { ChallengeProgressDto } from './challenge-progress.dto'; +export interface BadgeInfoDto { + code: string; + name: string; + description: string; + imageUrl: string; + category: string; +} + export interface ChallengeListItemDto { id: string; title: string; @@ -21,6 +29,7 @@ export interface ChallengeListItemDto { isJoined: boolean; type: ChallengeType; unit: string; + badge?: BadgeInfoDto; } export interface ChallengeListResponseDto {