feat(admin,api,player): 优胜赛配置、赛事管理重构与玩家端投注体验优化

管理端拆分赛事/优胜赛 Tab,新增联赛优胜赔率面板(批量、排序、外侧删除);统一 list-chrome 工具栏对齐与列表页布局;Dashboard 失败重试、Users 操作下拉、小屏侧栏等体验修复。

API 扩展优胜赛与赛事目录接口,完善投注与钱包查询;玩家端重构赛事卡片、串关面板、注单/钱包页,新增注单详情、下注成功动画与下拉刷新。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-08 09:55:56 +08:00
parent efff7c27e6
commit 24fa1b275c
66 changed files with 6289 additions and 1426 deletions

View File

@@ -1,5 +1,11 @@
/** 后台手动新增赛事(投注平台最小字段) */
import {
countryDisplayName,
countryLogoUrl,
hasCountryCrest,
type BuiltinCountry,
} from '../data/builtinCountries';
import { FormValidationError } from '../i18n/form-validation';
export interface MatchCreateForm {
@@ -8,6 +14,8 @@ export interface MatchCreateForm {
leagueZh: string;
leagueMs: string;
startTime: string;
homeTeamCode: string;
awayTeamCode: string;
homeTeamZh: string;
homeTeamEn: string;
homeTeamMs: string;
@@ -31,6 +39,8 @@ export function emptyMatchForm(): MatchCreateForm {
leagueZh: '2026 世界杯',
leagueMs: 'Piala Dunia 2026',
startTime: '',
homeTeamCode: '',
awayTeamCode: '',
homeTeamZh: '',
homeTeamEn: '',
homeTeamMs: '',
@@ -122,6 +132,8 @@ export function formFromDetail(d: AdminMatchDetail): MatchCreateForm {
leagueZh: d.leagueZh,
leagueMs: d.leagueMs ?? '',
startTime: normalizeStartTimeForPicker(d.startTime),
homeTeamCode: d.homeTeamCode ?? '',
awayTeamCode: d.awayTeamCode ?? '',
homeTeamZh: d.homeTeamZh,
homeTeamEn: d.homeTeamEn,
homeTeamMs: d.homeTeamMs ?? '',
@@ -139,19 +151,67 @@ export function formFromDetail(d: AdminMatchDetail): MatchCreateForm {
};
}
export function fillBuiltinTeam(
form: MatchCreateForm,
side: 'home' | 'away',
country: BuiltinCountry,
) {
const msName = countryDisplayName(country, 'ms-MY');
const logo = countryLogoUrl(country, hasCountryCrest(country) ? 'crest' : 'flag');
if (side === 'home') {
form.homeTeamCode = country.code;
form.homeTeamZh = country.nameZh;
form.homeTeamEn = country.nameEn;
form.homeTeamMs = msName;
form.homeTeamLogoUrl = logo;
} else {
form.awayTeamCode = country.code;
form.awayTeamZh = country.nameZh;
form.awayTeamEn = country.nameEn;
form.awayTeamMs = msName;
form.awayTeamLogoUrl = logo;
}
}
export function clearBuiltinTeam(form: MatchCreateForm, side: 'home' | 'away') {
if (side === 'home') {
form.homeTeamCode = '';
form.homeTeamZh = '';
form.homeTeamEn = '';
form.homeTeamMs = '';
form.homeTeamLogoUrl = '';
} else {
form.awayTeamCode = '';
form.awayTeamZh = '';
form.awayTeamEn = '';
form.awayTeamMs = '';
form.awayTeamLogoUrl = '';
}
}
export function buildPlatformPayload(form: MatchCreateForm) {
if (!form.startTime.trim()) {
throw new FormValidationError('err.kickoff_required');
}
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');
}
const homeKey = `${form.homeTeamZh.trim()}|${form.homeTeamEn.trim()}|${form.homeTeamMs.trim()}`.toLowerCase();
const awayKey = `${form.awayTeamZh.trim()}|${form.awayTeamEn.trim()}|${form.awayTeamMs.trim()}`.toLowerCase();
if (homeKey === awayKey) {
throw new FormValidationError('err.teams_same');
const homeCode = form.homeTeamCode.trim().toUpperCase();
const awayCode = form.awayTeamCode.trim().toUpperCase();
if (homeCode && awayCode) {
if (homeCode === awayCode) {
throw new FormValidationError('err.teams_same');
}
} else if (homeCode || awayCode) {
throw new FormValidationError('err.team_country_required');
} else {
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.team_country_required');
}
const homeKey = `${form.homeTeamZh.trim()}|${form.homeTeamEn.trim()}|${form.homeTeamMs.trim()}`.toLowerCase();
const awayKey = `${form.awayTeamZh.trim()}|${form.awayTeamEn.trim()}|${form.awayTeamMs.trim()}`.toLowerCase();
if (homeKey === awayKey) {
throw new FormValidationError('err.teams_same');
}
}
if (
!form.leagueId.trim() &&
@@ -167,6 +227,8 @@ export function buildPlatformPayload(form: MatchCreateForm) {
leagueEn: form.leagueEn.trim(),
leagueZh: form.leagueZh.trim(),
leagueMs: form.leagueMs.trim() || undefined,
homeTeamCode: homeCode || undefined,
awayTeamCode: awayCode || undefined,
homeTeamEn: form.homeTeamEn.trim(),
homeTeamZh: form.homeTeamZh.trim(),
homeTeamMs: form.homeTeamMs.trim() || undefined,