feat(challenges): 在挑战列表和详情中添加勋章信息展示

为睡眠挑战类型添加勋章信息支持,在挑战列表和详情接口中返回 sleepChallengeMonth 勋章配置数据。

- 在 ChallengesModule 中注册 BadgeConfig 模型
- 在 ChallengesService 中注入 BadgeConfig 仓库
- 查询列表时,若存在睡眠挑战则预加载勋章配置
- 查询详情时,若为睡眠挑战则附加勋章信息
- 在 DTO 中新增 BadgeInfoDto 接口定义勋章数据结构
- 仅对激活状态的 sleepChallengeMonth 勋章进行查询和展示
This commit is contained in:
richarjiang
2025-11-14 17:26:41 +08:00
parent 7b4d7c4459
commit bac75f82ba
4 changed files with 62 additions and 1 deletions

View File

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

View File

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

View File

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

View File

@@ -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 {