feat(admin,api,player): 赛事分组管理、盘口独立页与多语言展示优化

- 管理端按联赛展示单场,新增赛事/单场流程与列表展开状态保持

- 盘口赔率迁至独立页面,保存按钮仅在有修改时高亮

- API 新增联赛列表与子场查询,按 locale 返回队名并修复编译

- 波胆其它选项与促销标签等 i18n 补齐,文案更易懂
This commit is contained in:
2026-06-04 16:25:03 +08:00
parent c68abadceb
commit cc737e2924
39 changed files with 3330 additions and 378 deletions

View File

@@ -49,6 +49,22 @@ export class MarketsService {
return created;
}
private formatHandicapName(side: 'home' | 'away', line: number, half = false) {
const sideLabel = side === 'home' ? '主队' : '客队';
const value = side === 'home' ? line : -line;
const lineText = value > 0 ? `+${value}` : `${value}`;
return half ? `半场${sideLabel} ${lineText}` : `${sideLabel} ${lineText}`;
}
private formatOuName(side: 'over' | 'under', line: number, half = false) {
const sideLabel = side === 'over' ? '大' : '小';
return half ? `半场${sideLabel} ${line}` : `${sideLabel} ${line}`;
}
private formatScoreName(code: string) {
return code.replace('SCORE_', '').replace('_', '-');
}
private getMarketConfig(marketType: string) {
const configs: Record<string, {
period: string;
@@ -62,9 +78,9 @@ export class MarketsService {
allowParlay: true,
sortOrder: 1,
selections: [
{ code: 'HOME', name: 'Home', odds: 2.5 },
{ code: 'DRAW', name: 'Draw', odds: 3.2 },
{ code: 'AWAY', name: 'Away', odds: 2.8 },
{ code: 'HOME', name: '主胜', odds: 2.5 },
{ code: 'DRAW', name: '', odds: 3.2 },
{ code: 'AWAY', name: '客胜', odds: 2.8 },
],
},
HT_1X2: {
@@ -72,9 +88,9 @@ export class MarketsService {
allowParlay: true,
sortOrder: 5,
selections: [
{ code: 'HOME', name: 'HT Home', odds: 3.0 },
{ code: 'DRAW', name: 'HT Draw', odds: 2.0 },
{ code: 'AWAY', name: 'HT Away', odds: 3.5 },
{ code: 'HOME', name: '半场主胜', odds: 3.0 },
{ code: 'DRAW', name: '半场和', odds: 2.0 },
{ code: 'AWAY', name: '半场客胜', odds: 3.5 },
],
},
FT_HANDICAP: {
@@ -83,8 +99,8 @@ export class MarketsService {
allowParlay: true,
sortOrder: 2,
selections: [
{ code: 'HOME', name: 'Home -0.5', odds: 1.9 },
{ code: 'AWAY', name: 'Away +0.5', odds: 1.9 },
{ code: 'HOME', name: this.formatHandicapName('home', -0.5), odds: 1.9 },
{ code: 'AWAY', name: this.formatHandicapName('away', -0.5), odds: 1.9 },
],
},
HT_HANDICAP: {
@@ -93,8 +109,8 @@ export class MarketsService {
allowParlay: true,
sortOrder: 6,
selections: [
{ code: 'HOME', name: 'HT Home -0.5', odds: 1.9 },
{ code: 'AWAY', name: 'HT Away +0.5', odds: 1.9 },
{ code: 'HOME', name: this.formatHandicapName('home', -0.5, true), odds: 1.9 },
{ code: 'AWAY', name: this.formatHandicapName('away', -0.5, true), odds: 1.9 },
],
},
FT_OVER_UNDER: {
@@ -103,8 +119,8 @@ export class MarketsService {
allowParlay: true,
sortOrder: 3,
selections: [
{ code: 'OVER', name: 'Over 2.5', odds: 1.85 },
{ code: 'UNDER', name: 'Under 2.5', odds: 1.95 },
{ code: 'OVER', name: this.formatOuName('over', 2.5), odds: 1.85 },
{ code: 'UNDER', name: this.formatOuName('under', 2.5), odds: 1.95 },
],
},
HT_OVER_UNDER: {
@@ -113,8 +129,8 @@ export class MarketsService {
allowParlay: true,
sortOrder: 7,
selections: [
{ code: 'OVER', name: 'HT Over 1.5', odds: 2.0 },
{ code: 'UNDER', name: 'HT Under 1.5', odds: 1.75 },
{ code: 'OVER', name: this.formatOuName('over', 1.5, true), odds: 2.0 },
{ code: 'UNDER', name: this.formatOuName('under', 1.5, true), odds: 1.75 },
],
},
FT_ODD_EVEN: {
@@ -122,8 +138,8 @@ export class MarketsService {
allowParlay: true,
sortOrder: 4,
selections: [
{ code: 'ODD', name: 'Odd', odds: 1.9 },
{ code: 'EVEN', name: 'Even', odds: 1.9 },
{ code: 'ODD', name: '', odds: 1.9 },
{ code: 'EVEN', name: '', odds: 1.9 },
],
},
FT_CORRECT_SCORE: {
@@ -132,7 +148,7 @@ export class MarketsService {
sortOrder: 8,
selections: FT_CORRECT_SCORE_TEMPLATE.map((code) => ({
code,
name: code.replace('SCORE_', '').replace('_', '-') || code,
name: this.formatScoreName(code),
odds: 8.0,
})),
},
@@ -142,7 +158,7 @@ export class MarketsService {
sortOrder: 9,
selections: HT_CORRECT_SCORE_TEMPLATE.map((code) => ({
code,
name: code.replace('SCORE_', '').replace('_', '-') || code,
name: this.formatScoreName(code),
odds: 6.0,
})),
},
@@ -152,7 +168,7 @@ export class MarketsService {
sortOrder: 10,
selections: HT_CORRECT_SCORE_TEMPLATE.map((code) => ({
code,
name: code.replace('SCORE_', '').replace('_', '-') || code,
name: this.formatScoreName(code),
odds: 6.0,
})),
},
@@ -206,4 +222,45 @@ export class MarketsService {
}
return results;
}
async updateMarket(
marketId: bigint,
data: { promoLabel?: string | null; status?: string; lineValue?: number | null },
) {
const market = await this.prisma.market.findUnique({ where: { id: marketId } });
if (!market) throw new NotFoundException('Market not found');
return this.prisma.market.update({
where: { id: marketId },
data: {
...(data.promoLabel !== undefined ? { promoLabel: data.promoLabel?.trim() || null } : {}),
...(data.status !== undefined ? { status: data.status } : {}),
...(data.lineValue !== undefined ? { lineValue: data.lineValue } : {}),
},
});
}
async updateSelection(
selectionId: bigint,
data: { selectionName?: string; odds?: number; status?: string },
operatorId?: bigint,
) {
const selection = await this.prisma.marketSelection.findUnique({
where: { id: selectionId },
});
if (!selection) throw new NotFoundException('Selection not found');
if (data.odds != null) {
if (!operatorId) throw new BadRequestException('Operator required for odds update');
return this.updateOdds(selectionId, data.odds, operatorId);
}
return this.prisma.marketSelection.update({
where: { id: selectionId },
data: {
...(data.selectionName !== undefined ? { selectionName: data.selectionName.trim() } : {}),
...(data.status !== undefined ? { status: data.status } : {}),
},
});
}
}