feat(admin,api,player): 赛事分组管理、盘口独立页与多语言展示优化
- 管理端按联赛展示单场,新增赛事/单场流程与列表展开状态保持 - 盘口赔率迁至独立页面,保存按钮仅在有修改时高亮 - API 新增联赛列表与子场查询,按 locale 返回队名并修复编译 - 波胆其它选项与促销标签等 i18n 补齐,文案更易懂
This commit is contained in:
@@ -3,54 +3,133 @@
|
||||
import { FormValidationError } from '../i18n/form-validation';
|
||||
|
||||
export interface MatchCreateForm {
|
||||
leagueId: string;
|
||||
leagueEn: string;
|
||||
leagueZh: string;
|
||||
leagueMs: string;
|
||||
startTime: string;
|
||||
homeTeamZh: string;
|
||||
homeTeamEn: string;
|
||||
homeTeamMs: string;
|
||||
awayTeamZh: string;
|
||||
awayTeamEn: string;
|
||||
awayTeamMs: string;
|
||||
isHot: boolean;
|
||||
displayOrder: number;
|
||||
matchName: string;
|
||||
stage: string;
|
||||
groupName: string;
|
||||
leagueLogoUrl: string;
|
||||
homeTeamLogoUrl: string;
|
||||
awayTeamLogoUrl: string;
|
||||
}
|
||||
|
||||
export function emptyMatchForm(): MatchCreateForm {
|
||||
return {
|
||||
leagueId: '',
|
||||
leagueEn: 'FIFA World Cup 2026',
|
||||
leagueZh: '2026 世界杯',
|
||||
leagueMs: 'Piala Dunia 2026',
|
||||
startTime: '',
|
||||
homeTeamZh: '',
|
||||
homeTeamEn: '',
|
||||
homeTeamMs: '',
|
||||
awayTeamZh: '',
|
||||
awayTeamEn: '',
|
||||
awayTeamMs: '',
|
||||
isHot: false,
|
||||
displayOrder: 0,
|
||||
matchName: '',
|
||||
stage: '',
|
||||
groupName: '',
|
||||
leagueLogoUrl: '',
|
||||
homeTeamLogoUrl: '',
|
||||
awayTeamLogoUrl: '',
|
||||
};
|
||||
}
|
||||
|
||||
export interface AdminMarketSelection {
|
||||
id: string;
|
||||
selectionCode: string;
|
||||
selectionName: string;
|
||||
odds: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface AdminMarket {
|
||||
id: string;
|
||||
marketType: string;
|
||||
period: string;
|
||||
lineValue: number | null;
|
||||
status: string;
|
||||
promoLabel: string;
|
||||
selections: AdminMarketSelection[];
|
||||
}
|
||||
|
||||
export type AdminMatchDetail = {
|
||||
id: string;
|
||||
status: string;
|
||||
isOutright: boolean;
|
||||
isHot: boolean;
|
||||
displayOrder: number;
|
||||
startTime: string;
|
||||
leagueEn: string;
|
||||
leagueZh: string;
|
||||
leagueMs: string;
|
||||
leagueLogoUrl?: string;
|
||||
homeTeamEn: string;
|
||||
homeTeamZh: string;
|
||||
homeTeamMs: string;
|
||||
homeTeamCode?: string;
|
||||
homeTeamLogoUrl?: string;
|
||||
awayTeamEn: string;
|
||||
awayTeamZh: string;
|
||||
awayTeamMs: string;
|
||||
awayTeamCode?: string;
|
||||
awayTeamLogoUrl?: string;
|
||||
matchName: string;
|
||||
stage?: string;
|
||||
groupName?: string;
|
||||
markets?: AdminMarket[];
|
||||
};
|
||||
|
||||
export function normalizeStartTimeForPicker(iso?: string): string {
|
||||
if (!iso?.trim()) return '';
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return iso.slice(0, 19);
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
||||
}
|
||||
|
||||
export function normalizeStartTimeForApi(value: string): string {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return '';
|
||||
const d = new Date(trimmed);
|
||||
if (Number.isNaN(d.getTime())) return trimmed;
|
||||
return d.toISOString();
|
||||
}
|
||||
|
||||
export function formFromDetail(d: AdminMatchDetail): MatchCreateForm {
|
||||
return {
|
||||
leagueId: '',
|
||||
leagueEn: d.leagueEn,
|
||||
leagueZh: d.leagueZh,
|
||||
startTime: d.startTime,
|
||||
leagueMs: d.leagueMs ?? '',
|
||||
startTime: normalizeStartTimeForPicker(d.startTime),
|
||||
homeTeamZh: d.homeTeamZh,
|
||||
homeTeamEn: d.homeTeamEn,
|
||||
homeTeamMs: d.homeTeamMs ?? '',
|
||||
awayTeamZh: d.awayTeamZh,
|
||||
awayTeamEn: d.awayTeamEn,
|
||||
awayTeamMs: d.awayTeamMs ?? '',
|
||||
isHot: d.isHot,
|
||||
displayOrder: d.displayOrder ?? 0,
|
||||
matchName: d.matchName ?? '',
|
||||
stage: d.stage ?? '',
|
||||
groupName: d.groupName ?? '',
|
||||
leagueLogoUrl: d.leagueLogoUrl ?? '',
|
||||
homeTeamLogoUrl: d.homeTeamLogoUrl ?? '',
|
||||
awayTeamLogoUrl: d.awayTeamLogoUrl ?? '',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -58,23 +137,39 @@ export function buildPlatformPayload(form: MatchCreateForm) {
|
||||
if (!form.startTime.trim()) {
|
||||
throw new FormValidationError('err.kickoff_required');
|
||||
}
|
||||
const homeOk = form.homeTeamZh.trim() || form.homeTeamEn.trim();
|
||||
const awayOk = form.awayTeamZh.trim() || form.awayTeamEn.trim();
|
||||
const homeOk = form.homeTeamZh.trim() || form.homeTeamEn.trim() || form.homeTeamMs.trim();
|
||||
const awayOk = form.awayTeamZh.trim() || form.awayTeamEn.trim() || form.awayTeamMs.trim();
|
||||
if (!homeOk || !awayOk) {
|
||||
throw new FormValidationError('err.teams_required');
|
||||
}
|
||||
if (!form.leagueZh.trim() && !form.leagueEn.trim()) {
|
||||
if (
|
||||
!form.leagueId.trim() &&
|
||||
!form.leagueZh.trim() &&
|
||||
!form.leagueEn.trim() &&
|
||||
!form.leagueMs.trim()
|
||||
) {
|
||||
throw new FormValidationError('err.league_required');
|
||||
}
|
||||
|
||||
return {
|
||||
leagueId: form.leagueId.trim() || undefined,
|
||||
leagueEn: form.leagueEn.trim(),
|
||||
leagueZh: form.leagueZh.trim(),
|
||||
leagueMs: form.leagueMs.trim() || undefined,
|
||||
homeTeamEn: form.homeTeamEn.trim(),
|
||||
homeTeamZh: form.homeTeamZh.trim(),
|
||||
homeTeamMs: form.homeTeamMs.trim() || undefined,
|
||||
awayTeamEn: form.awayTeamEn.trim(),
|
||||
awayTeamZh: form.awayTeamZh.trim(),
|
||||
startTime: form.startTime.trim(),
|
||||
awayTeamMs: form.awayTeamMs.trim() || undefined,
|
||||
startTime: normalizeStartTimeForApi(form.startTime),
|
||||
isHot: form.isHot,
|
||||
displayOrder: form.displayOrder,
|
||||
matchName: form.matchName.trim() || undefined,
|
||||
stage: form.stage.trim() || undefined,
|
||||
groupName: form.groupName.trim() || undefined,
|
||||
leagueLogoUrl: form.leagueLogoUrl.trim() || undefined,
|
||||
homeTeamLogoUrl: form.homeTeamLogoUrl.trim() || undefined,
|
||||
awayTeamLogoUrl: form.awayTeamLogoUrl.trim() || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user