1. 后台/API:赛事详情返回 allowSingle/allowParlay,管理端 mapMarkets 正确回读 2. 玩家端:仅关单关时仍可点选加入串关;投注抽屉禁用单关提交并提示 3. 玩家端:盘口暂停/已关闭状态标签;MarketTypeTile 展示 statusLabel Co-authored-by: Cursor <cursoragent@cursor.com>
311 lines
9.2 KiB
TypeScript
311 lines
9.2 KiB
TypeScript
/** 后台手动新增赛事(投注平台最小字段) */
|
|
|
|
import {
|
|
countryDisplayName,
|
|
countryLogoUrl,
|
|
hasCountryCrest,
|
|
type BuiltinCountry,
|
|
} from '../data/builtinCountries';
|
|
import { FormValidationError } from '../i18n/form-validation';
|
|
import {
|
|
isoToPlatformPickerDateTime,
|
|
platformPickerDateTimeToIso,
|
|
} from '@thebet365/shared';
|
|
|
|
export interface MatchCreateForm {
|
|
leagueId: string;
|
|
leagueEn: string;
|
|
leagueZh: string;
|
|
leagueMs: string;
|
|
startTime: string;
|
|
homeTeamCode: string;
|
|
awayTeamCode: 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: '',
|
|
homeTeamCode: '',
|
|
awayTeamCode: '',
|
|
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;
|
|
nameI18n?: Record<string, string>;
|
|
odds: number;
|
|
status: string;
|
|
sortOrder?: number;
|
|
}
|
|
|
|
export interface AdminMarket {
|
|
id: string;
|
|
marketType: string;
|
|
marketKey?: string;
|
|
lineKey?: string | null;
|
|
period: string;
|
|
lineValue: number | null;
|
|
paramsJson?: Record<string, unknown> | null;
|
|
status: string;
|
|
allowSingle?: boolean;
|
|
allowParlay?: boolean;
|
|
showOnPlayer: boolean;
|
|
promoLabel: string;
|
|
promoLabelI18n?: Record<string, string>;
|
|
nameI18n?: Record<string, string>;
|
|
sortOrder?: number;
|
|
selections: AdminMarketSelection[];
|
|
}
|
|
|
|
export type AdminMatchDetail = {
|
|
id: string;
|
|
status: string;
|
|
isOutright: boolean;
|
|
isHot: boolean;
|
|
displayOrder: number;
|
|
startTime: string;
|
|
leagueId?: string;
|
|
leagueCode?: 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;
|
|
score?: {
|
|
htHome: number;
|
|
htAway: number;
|
|
ftHome: number;
|
|
ftAway: number;
|
|
homeCorners?: number | null;
|
|
awayCorners?: number | null;
|
|
homeYellowCards?: number | null;
|
|
awayYellowCards?: number | null;
|
|
homeRedCards?: number | null;
|
|
awayRedCards?: number | null;
|
|
homeCards?: number | null;
|
|
awayCards?: number | null;
|
|
winnerTeamId?: string | null;
|
|
} | null;
|
|
markets?: AdminMarket[];
|
|
};
|
|
|
|
export function normalizeStartTimeForPicker(iso?: string): string {
|
|
return isoToPlatformPickerDateTime(iso);
|
|
}
|
|
|
|
export function normalizeStartTimeForApi(value: string): string {
|
|
return platformPickerDateTimeToIso(value);
|
|
}
|
|
|
|
export function formFromDetail(d: AdminMatchDetail): MatchCreateForm {
|
|
return {
|
|
leagueId: d.leagueId ?? '',
|
|
leagueEn: d.leagueEn,
|
|
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 ?? '',
|
|
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 ?? '',
|
|
};
|
|
}
|
|
|
|
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 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() &&
|
|
!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,
|
|
homeTeamCode: homeCode || undefined,
|
|
awayTeamCode: awayCode || undefined,
|
|
homeTeamEn: form.homeTeamEn.trim(),
|
|
homeTeamZh: form.homeTeamZh.trim(),
|
|
homeTeamMs: form.homeTeamMs.trim() || undefined,
|
|
awayTeamEn: form.awayTeamEn.trim(),
|
|
awayTeamZh: form.awayTeamZh.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,
|
|
};
|
|
}
|
|
|
|
/** 编辑单场基本信息(不含联赛字段,联赛在赛事列表单独维护) */
|
|
export function buildMatchUpdatePayload(form: MatchCreateForm) {
|
|
if (!form.startTime.trim()) {
|
|
throw new FormValidationError('err.kickoff_required');
|
|
}
|
|
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');
|
|
}
|
|
}
|
|
|
|
return {
|
|
homeTeamEn: form.homeTeamEn.trim(),
|
|
homeTeamZh: form.homeTeamZh.trim(),
|
|
homeTeamMs: form.homeTeamMs.trim() || undefined,
|
|
awayTeamEn: form.awayTeamEn.trim(),
|
|
awayTeamZh: form.awayTeamZh.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,
|
|
homeTeamLogoUrl: form.homeTeamLogoUrl.trim() || undefined,
|
|
awayTeamLogoUrl: form.awayTeamLogoUrl.trim() || undefined,
|
|
};
|
|
}
|