feat(admin,player,api): 公共管理与优胜冠军国旗、玩家端内容对接

新增公共内容 CRUD 与批量操作;公告滚动合并管理;优胜冠军内置国家选择与单行保存;玩家端统一 usePlayerHome 对接轮播与跑马灯。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-04 10:25:42 +08:00
parent 27580b2479
commit f76728dc3e
21 changed files with 1966 additions and 136 deletions

View File

@@ -5,6 +5,7 @@ import {
Get,
Post,
Put,
Patch,
Body,
Param,
Query,
@@ -304,6 +305,113 @@ class AddOutrightSelectionDto {
@IsNumber()
@Min(1.01)
odds!: number;
@IsOptional()
@IsString()
logoUrl?: string;
}
class UpdateOutrightSelectionTeamDto {
@IsOptional()
@IsString()
teamCode?: string;
@IsOptional()
@IsString()
teamZh?: string;
@IsOptional()
@IsString()
teamEn?: string;
@IsOptional()
@IsString()
logoUrl?: string | null;
}
class ContentTranslationDto {
@IsString()
locale!: string;
@IsOptional()
@IsString()
title?: string;
@IsOptional()
@IsString()
body?: string;
@IsOptional()
@IsString()
imageUrl?: string;
}
class CreateContentDto {
@IsString()
@IsIn(['BANNER', 'NOTICE', 'TICKER'])
contentType!: string;
@IsOptional()
@IsNumber()
sortOrder?: number;
@IsOptional()
@IsIn(['DRAFT', 'ACTIVE', 'INACTIVE'])
status?: string;
@IsOptional()
@IsString()
linkType?: string | null;
@IsOptional()
@IsString()
linkTarget?: string | null;
@IsOptional()
@IsString()
startTime?: string | null;
@IsOptional()
@IsString()
endTime?: string | null;
@IsArray()
translations!: ContentTranslationDto[];
}
class UpdateContentDto {
@IsOptional()
@IsNumber()
sortOrder?: number;
@IsOptional()
@IsIn(['DRAFT', 'ACTIVE', 'INACTIVE'])
status?: string;
@IsOptional()
@IsString()
linkType?: string | null;
@IsOptional()
@IsString()
linkTarget?: string | null;
@IsOptional()
@IsString()
startTime?: string | null;
@IsOptional()
@IsString()
endTime?: string | null;
@IsOptional()
@IsArray()
translations?: ContentTranslationDto[];
}
class ContentStatusDto {
@IsIn(['DRAFT', 'ACTIVE', 'INACTIVE'])
status!: string;
}
class CashbackPreviewDto {
@@ -790,6 +898,20 @@ export class AdminController {
return jsonResponse(data);
}
@Patch('outrights/:matchId/selections/:selectionId')
async updateOutrightSelectionTeam(
@Param('matchId') matchId: string,
@Param('selectionId') selectionId: string,
@Body() dto: UpdateOutrightSelectionTeamDto,
) {
const data = await this.outright.updateSelectionTeam(
BigInt(matchId),
BigInt(selectionId),
dto,
);
return jsonResponse(data);
}
@Delete('outrights/:matchId/selections/:selectionId')
async removeOutrightSelection(
@Param('matchId') matchId: string,
@@ -875,17 +997,47 @@ export class AdminController {
}
@Get('contents')
async listContents(@Query('type') type?: string) {
const items = await this.content.listAll(type);
async listContents(
@Query('type') type?: string,
@Query('status') status?: string,
) {
const items = await this.content.listForAdmin(type, status);
return jsonResponse(items);
}
@Get('contents/:id')
async getContent(@Param('id') id: string) {
const item = await this.content.getForAdmin(BigInt(id));
return jsonResponse(item);
}
@Post('contents')
async createContent(@Body() dto: Parameters<ContentService['create']>[0]) {
async createContent(@Body() dto: CreateContentDto) {
const item = await this.content.create(dto);
return jsonResponse(item);
}
@Put('contents/:id')
async updateContent(@Param('id') id: string, @Body() dto: UpdateContentDto) {
const item = await this.content.update(BigInt(id), dto);
return jsonResponse(item);
}
@Patch('contents/:id/status')
async updateContentStatus(
@Param('id') id: string,
@Body() dto: ContentStatusDto,
) {
const item = await this.content.updateStatus(BigInt(id), dto.status);
return jsonResponse(item);
}
@Delete('contents/:id')
async deleteContent(@Param('id') id: string) {
const result = await this.content.remove(BigInt(id));
return jsonResponse(result);
}
@Get('i18n/messages')
async getMessages(@Query('locale') locale = 'en-US') {
const messages = await this.i18n.getMessages(locale);

View File

@@ -109,17 +109,19 @@ export class PlayerController {
@Get('home')
async home(@CurrentUser('locale') locale: string) {
const [banners, notices, ticker, hotMatches, todayMatches] = await Promise.all([
const [banners, announcements, hotMatches, todayMatches] = await Promise.all([
this.content.listActive('BANNER', locale),
this.content.listActive('NOTICE', locale),
this.content.listActive('TICKER', locale),
this.content.listActiveAnnouncements(locale),
this.matches.listPublished(locale),
this.matches.listPublished(locale),
]);
return jsonResponse({
banners,
notices,
ticker,
announcements,
/** @deprecated 使用 announcements */
ticker: announcements,
/** @deprecated 使用 announcements */
notices: announcements,
hotMatches: (hotMatches as Array<{ isHot?: boolean }>).filter((m) => m.isHot),
todayMatches,
});