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

@@ -0,0 +1,127 @@
/** 内置国家队(世界杯 48 强 + 常用队),供管理端选择国旗与自动填充队名 */
export type BuiltinCountry = {
code: string;
nameZh: string;
nameEn: string;
iso: string;
};
export const BUILTIN_COUNTRIES: BuiltinCountry[] = [
{ code: 'FRA', nameZh: '法国', nameEn: 'France', iso: 'fr' },
{ code: 'ESP', nameZh: '西班牙', nameEn: 'Spain', iso: 'es' },
{ code: 'ENG', nameZh: '英格兰', nameEn: 'England', iso: 'gb-eng' },
{ code: 'BRA', nameZh: '巴西', nameEn: 'Brazil', iso: 'br' },
{ code: 'ARG', nameZh: '阿根廷', nameEn: 'Argentina', iso: 'ar' },
{ code: 'POR', nameZh: '葡萄牙', nameEn: 'Portugal', iso: 'pt' },
{ code: 'GER', nameZh: '德国', nameEn: 'Germany', iso: 'de' },
{ code: 'NED', nameZh: '荷兰', nameEn: 'Netherlands', iso: 'nl' },
{ code: 'NOR', nameZh: '挪威', nameEn: 'Norway', iso: 'no' },
{ code: 'BEL', nameZh: '比利时', nameEn: 'Belgium', iso: 'be' },
{ code: 'COL', nameZh: '哥伦比亚', nameEn: 'Colombia', iso: 'co' },
{ code: 'JPN', nameZh: '日本', nameEn: 'Japan', iso: 'jp' },
{ code: 'URU', nameZh: '乌拉圭', nameEn: 'Uruguay', iso: 'uy' },
{ code: 'USA', nameZh: '美国', nameEn: 'USA', iso: 'us' },
{ code: 'MAR', nameZh: '摩洛哥', nameEn: 'Morocco', iso: 'ma' },
{ code: 'CRO', nameZh: '克罗地亚', nameEn: 'Croatia', iso: 'hr' },
{ code: 'MEX', nameZh: '墨西哥', nameEn: 'Mexico', iso: 'mx' },
{ code: 'SUI', nameZh: '瑞士', nameEn: 'Switzerland', iso: 'ch' },
{ code: 'TUR', nameZh: '土耳其', nameEn: 'Turkey', iso: 'tr' },
{ code: 'SEN', nameZh: '塞内加尔', nameEn: 'Senegal', iso: 'sn' },
{ code: 'KOR', nameZh: '韩国', nameEn: 'South Korea', iso: 'kr' },
{ code: 'AUT', nameZh: '奥地利', nameEn: 'Austria', iso: 'at' },
{ code: 'ECU', nameZh: '厄瓜多尔', nameEn: 'Ecuador', iso: 'ec' },
{ code: 'SWE', nameZh: '瑞典', nameEn: 'Sweden', iso: 'se' },
{ code: 'IRN', nameZh: '伊朗', nameEn: 'Iran', iso: 'ir' },
{ code: 'GHA', nameZh: '加纳', nameEn: 'Ghana', iso: 'gh' },
{ code: 'ALG', nameZh: '阿尔及利亚', nameEn: 'Algeria', iso: 'dz' },
{ code: 'BIH', nameZh: '波黑', nameEn: 'Bosnia', iso: 'ba' },
{ code: 'EGY', nameZh: '埃及', nameEn: 'Egypt', iso: 'eg' },
{ code: 'TUN', nameZh: '突尼斯', nameEn: 'Tunisia', iso: 'tn' },
{ code: 'CAN', nameZh: '加拿大', nameEn: 'Canada', iso: 'ca' },
{ code: 'PAN', nameZh: '巴拿马', nameEn: 'Panama', iso: 'pa' },
{ code: 'AUS', nameZh: '澳大利亚', nameEn: 'Australia', iso: 'au' },
{ code: 'CZE', nameZh: '捷克', nameEn: 'Czech Republic', iso: 'cz' },
{ code: 'KSA', nameZh: '沙特阿拉伯', nameEn: 'Saudi Arabia', iso: 'sa' },
{ code: 'NZL', nameZh: '新西兰', nameEn: 'New Zealand', iso: 'nz' },
{ code: 'COD', nameZh: '刚果(金)', nameEn: 'DR Congo', iso: 'cd' },
{ code: 'UZB', nameZh: '乌兹别克斯坦', nameEn: 'Uzbekistan', iso: 'uz' },
{ code: 'IRQ', nameZh: '伊拉克', nameEn: 'Iraq', iso: 'iq' },
{ code: 'RSA', nameZh: '南非', nameEn: 'South Africa', iso: 'za' },
{ code: 'CIV', nameZh: '科特迪瓦', nameEn: 'Ivory Coast', iso: 'ci' },
{ code: 'JOR', nameZh: '约旦', nameEn: 'Jordan', iso: 'jo' },
{ code: 'PAR', nameZh: '巴拉圭', nameEn: 'Paraguay', iso: 'py' },
{ code: 'HAI', nameZh: '海地', nameEn: 'Haiti', iso: 'ht' },
{ code: 'QAT', nameZh: '卡塔尔', nameEn: 'Qatar', iso: 'qa' },
{ code: 'CPV', nameZh: '佛得角', nameEn: 'Cape Verde', iso: 'cv' },
{ code: 'CUW', nameZh: '库拉索', nameEn: 'Curacao', iso: 'cw' },
{ code: 'SCO', nameZh: '苏格兰', nameEn: 'Scotland', iso: 'gb-sct' },
{ code: 'CHN', nameZh: '中国', nameEn: 'China', iso: 'cn' },
{ code: 'ITA', nameZh: '意大利', nameEn: 'Italy', iso: 'it' },
{ code: 'WAL', nameZh: '威尔士', nameEn: 'Wales', iso: 'gb-wls' },
{ code: 'UKR', nameZh: '乌克兰', nameEn: 'Ukraine', iso: 'ua' },
{ code: 'POL', nameZh: '波兰', nameEn: 'Poland', iso: 'pl' },
{ code: 'DEN', nameZh: '丹麦', nameEn: 'Denmark', iso: 'dk' },
{ code: 'FIN', nameZh: '芬兰', nameEn: 'Finland', iso: 'fi' },
{ code: 'IRL', nameZh: '爱尔兰', nameEn: 'Ireland', iso: 'ie' },
{ code: 'ISL', nameZh: '冰岛', nameEn: 'Iceland', iso: 'is' },
{ code: 'GRE', nameZh: '希腊', nameEn: 'Greece', iso: 'gr' },
{ code: 'SRB', nameZh: '塞尔维亚', nameEn: 'Serbia', iso: 'rs' },
{ code: 'ROU', nameZh: '罗马尼亚', nameEn: 'Romania', iso: 'ro' },
{ code: 'HUN', nameZh: '匈牙利', nameEn: 'Hungary', iso: 'hu' },
{ code: 'SVK', nameZh: '斯洛伐克', nameEn: 'Slovakia', iso: 'sk' },
{ code: 'SVN', nameZh: '斯洛文尼亚', nameEn: 'Slovenia', iso: 'si' },
{ code: 'NGA', nameZh: '尼日利亚', nameEn: 'Nigeria', iso: 'ng' },
{ code: 'CMR', nameZh: '喀麦隆', nameEn: 'Cameroon', iso: 'cm' },
{ code: 'CHI', nameZh: '智利', nameEn: 'Chile', iso: 'cl' },
{ code: 'PER', nameZh: '秘鲁', nameEn: 'Peru', iso: 'pe' },
{ code: 'VEN', nameZh: '委内瑞拉', nameEn: 'Venezuela', iso: 've' },
{ code: 'CRC', nameZh: '哥斯达黎加', nameEn: 'Costa Rica', iso: 'cr' },
{ code: 'JAM', nameZh: '牙买加', nameEn: 'Jamaica', iso: 'jm' },
{ code: 'UAE', nameZh: '阿联酋', nameEn: 'UAE', iso: 'ae' },
{ code: 'THA', nameZh: '泰国', nameEn: 'Thailand', iso: 'th' },
{ code: 'VIE', nameZh: '越南', nameEn: 'Vietnam', iso: 'vn' },
{ code: 'IDN', nameZh: '印度尼西亚', nameEn: 'Indonesia', iso: 'id' },
{ code: 'MAS', nameZh: '马来西亚', nameEn: 'Malaysia', iso: 'my' },
];
const byCode = new Map(BUILTIN_COUNTRIES.map((c) => [c.code, c]));
export function getBuiltinCountry(code?: string | null): BuiltinCountry | undefined {
const key = (code ?? '').trim().toUpperCase();
return key ? byCode.get(key) : undefined;
}
export function countryFlagUrl(country: BuiltinCountry | string): string {
const c = typeof country === 'string' ? getBuiltinCountry(country) : country;
if (!c) return '';
return `https://flagcdn.com/w40/${c.iso}.png`;
}
export function searchBuiltinCountries(keyword: string): BuiltinCountry[] {
const k = keyword.trim().toLowerCase();
if (!k) return BUILTIN_COUNTRIES;
return BUILTIN_COUNTRIES.filter(
(c) =>
c.code.toLowerCase().includes(k) ||
c.nameZh.includes(keyword.trim()) ||
c.nameEn.toLowerCase().includes(k) ||
c.iso.toLowerCase().includes(k),
);
}
export function resolveCountryCode(
teamCode?: string,
logoUrl?: string | null,
): string {
const fromCode = getBuiltinCountry(teamCode);
if (fromCode) return fromCode.code;
if (logoUrl) {
const m = logoUrl.match(/flagcdn\.com\/w\d+\/([a-z0-9-]+)\.png/i);
if (m) {
const iso = m[1].toLowerCase();
const hit = BUILTIN_COUNTRIES.find((c) => c.iso === iso);
if (hit) return hit.code;
}
}
return (teamCode ?? '').trim().toUpperCase();
}