// 电商平台规格 + 市场语言业务数据客户端(AGENTS.md 规则4合规)。 // 数据由后端 GET /api/public/config/profile?name=web-ecommerce-platform-rules 下发, // 不硬编码在前端业务逻辑里。 // // 时序设计(启动 gating):App 启动 boot splash 期间 await preloadPlatformRules(), // 数据就绪后才渲染 EcommercePage(React.lazy)。因此 platformRules.ts 模块求值时 // (随 EcommercePage chunk 加载)缓存已填充,其顶层派生常量拿到的是 API 数据。 // // FALLBACK = 完整当前生产数据:API 超时/失败时仍能正常工作(fallback 即正确值)。 import type { EcommercePlatformSpec } from "../features/ecommerce/utils/platformRules"; import { serverRequest } from "./serverConnection"; export interface MarketLanguageOption { country: string; languages: string[]; } export interface PlatformRulesData { platformSpecOptions: EcommercePlatformSpec[]; marketLanguageOptions: MarketLanguageOption[]; languageAliases: Record; legacyPlatformAliases: Record; domesticPlatformLabels: string[]; domesticPlatformLanguages: string[]; defaultEcommercePlatform: string; } // ── FALLBACK:完整当前数据,逐字迁移自原 platformRules.ts ────────────── const FALLBACK_PLATFORM_RULES: PlatformRulesData = { platformSpecOptions: [ { label: "淘宝/天猫", ratios: ["淘宝主图 / SKU 图 800×800px", "详情页宽 750px", "详情页宽 790px"], defaultRatio: "淘宝主图 / SKU 图 800×800px", ratioGroups: { set: { ratios: ["1000×1000px\u00a0\u00a0\u00a01:1", "800×800px\u00a0\u00a0\u00a01:1"], defaultRatio: "1000×1000px\u00a0\u00a0\u00a01:1", }, detail: { ratios: [ "750×1000px\u00a0\u00a0\u00a03:4", "790×1053px\u00a0\u00a0\u00a03:4", "750×1125px\u00a0\u00a0\u00a02:3", "790×1185px\u00a0\u00a0\u00a02:3", ], defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4", }, model: { ratios: ["750×1000px\u00a0\u00a0\u00a03:4"], defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4", }, video: { ratios: ["1080×1920px\u00a0\u00a0\u00a09:16", "1080×1440px\u00a0\u00a0\u00a03:4", "1080×1080px\u00a0\u00a0\u00a01:1"], defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16", }, hot: { ratios: ["800×800px\u00a0\u00a0\u00a01:1"], defaultRatio: "800×800px\u00a0\u00a0\u00a01:1", }, }, specs: ["主图 / SKU 图 800×800px,≤3MB", "详情页宽 750px 或 790px,单张高度≤1546px"], tip: "建议主图 200-400KB JPG,超过 500KB 会影响加载速度。", }, { label: "京东", ratios: ["京东主图 / SKU 图 800×800px", "详情页宽 750px", "首图主体占比 ≥80%"], defaultRatio: "京东主图 / SKU 图 800×800px", ratioGroups: { set: { ratios: ["1000×1000px\u00a0\u00a0\u00a01:1"], defaultRatio: "1000×1000px\u00a0\u00a0\u00a01:1", }, detail: { ratios: [ "750×1000px\u00a0\u00a0\u00a03:4", "990×1320px\u00a0\u00a0\u00a03:4", "750×1125px\u00a0\u00a0\u00a02:3", "990×1485px\u00a0\u00a0\u00a02:3", ], defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4", }, model: { ratios: ["750×1125px\u00a0\u00a0\u00a02:3", "990×1485px\u00a0\u00a0\u00a02:3"], defaultRatio: "750×1125px\u00a0\u00a0\u00a02:3", }, video: { ratios: ["1080×1920px\u00a0\u00a0\u00a09:16", "1920×1080px\u00a0\u00a0\u00a016:9"], defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16", }, hot: { ratios: ["800×800px\u00a0\u00a0\u00a01:1"], defaultRatio: "800×800px\u00a0\u00a0\u00a01:1", }, }, specs: ["主图 / SKU 图 800×800px,白底,≤3MB", "详情页宽 750px,首图主体占比 ≥80%"], }, { label: "拼多多", ratios: ["主图 750×352px", "主图 800×800px", "详情页宽 750px"], defaultRatio: "主图 750×352px", ratioGroups: { set: { ratios: ["800×800px\u00a0\u00a0\u00a01:1", "750×1000px\u00a0\u00a0\u00a03:4"], defaultRatio: "800×800px\u00a0\u00a0\u00a01:1", }, detail: { ratios: ["750×1000px\u00a0\u00a0\u00a03:4", "750×1125px\u00a0\u00a0\u00a02:3"], defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4", }, model: { ratios: ["750×1000px\u00a0\u00a0\u00a03:4"], defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4", }, video: { ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"], defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16", }, hot: { ratios: ["800×800px\u00a0\u00a0\u00a01:1"], defaultRatio: "800×800px\u00a0\u00a0\u00a01:1", }, }, specs: ["主图 750×352px 或 800×800px,≤1MB", "详情页宽 750px,要求纯白底、无水印、无拼接"], }, { label: "抖音电商", ratios: ["短视频1080×1920px"], defaultRatio: "短视频1080×1920px", ratioGroups: { video: { ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"], defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16", }, hot: { ratios: ["800×800px\u00a0\u00a0\u00a01:1"], defaultRatio: "800×800px\u00a0\u00a0\u00a01:1", }, }, specs: ["短视频 1080×1920px,9:16", "30s 内最佳"], }, { label: "亚马逊 Amazon", ratios: ["主图 ≥1600×1600px", "建议 2000×2000px+", "最小 500×500px"], defaultRatio: "主图 ≥1600×1600px", ratioGroups: { set: { ratios: ["1600×1600px\u00a0\u00a0\u00a01:1"], defaultRatio: "1600×1600px\u00a0\u00a0\u00a01:1", }, detail: { ratios: ["1600×1600px\u00a0\u00a0\u00a01:1", "1200×1800px\u00a0\u00a0\u00a02:3", "1200×1600px\u00a0\u00a0\u00a03:4"], defaultRatio: "1200×1800px\u00a0\u00a0\u00a02:3", }, model: { ratios: ["1200×1800px\u00a0\u00a0\u00a02:3"], defaultRatio: "1200×1800px\u00a0\u00a0\u00a02:3", }, video: { ratios: ["1920×1080px\u00a0\u00a0\u00a016:9"], defaultRatio: "1920×1080px\u00a0\u00a0\u00a016:9", }, hot: { ratios: ["1600×1600px\u00a0\u00a0\u00a01:1"], defaultRatio: "1600×1600px\u00a0\u00a0\u00a01:1", }, }, specs: ["主图 1600×1600px+,纯白底,≤10MB", "最小 500×500px,建议 2000px+ 以支持缩放"], aliases: ["亚马逊"], }, { label: "Shopee", ratios: ["商品主图 1024×1024px", "基础主图 800×800px"], defaultRatio: "商品主图 1024×1024px", ratioGroups: { set: { ratios: ["800×800px\u00a0\u00a0\u00a01:1"], defaultRatio: "800×800px\u00a0\u00a0\u00a01:1", }, detail: { ratios: ["750×1000px\u00a0\u00a0\u00a03:4", "750×1125px\u00a0\u00a0\u00a02:3"], defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4", }, model: { ratios: ["750×1000px\u00a0\u00a0\u00a03:4"], defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4", }, video: { ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"], defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16", }, hot: { ratios: ["800×800px\u00a0\u00a0\u00a01:1"], defaultRatio: "800×800px\u00a0\u00a0\u00a01:1", }, }, specs: ["商品主图推荐 1024×1024px,基础 800×800px", "≤2MB,白底或浅色底"], aliases: ["虾皮 Shopee/Lazada", "虾皮"], }, { label: "Lazada", ratios: ["商品主图 800×800px"], defaultRatio: "商品主图 800×800px", ratioGroups: { set: { ratios: ["800×800px\u00a0\u00a0\u00a01:1"], defaultRatio: "800×800px\u00a0\u00a0\u00a01:1", }, detail: { ratios: ["750×1000px\u00a0\u00a0\u00a03:4", "750×1125px\u00a0\u00a0\u00a02:3"], defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4", }, model: { ratios: ["750×1000px\u00a0\u00a0\u00a03:4"], defaultRatio: "750×1000px\u00a0\u00a0\u00a03:4", }, video: { ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"], defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16", }, hot: { ratios: ["800×800px\u00a0\u00a0\u00a01:1"], defaultRatio: "800×800px\u00a0\u00a0\u00a01:1", }, }, specs: ["商品主图 800×800px,1:1"], }, { label: "Instagram", ratios: ["帖子 1080×1350px", "帖子 1080×1080px", "Stories / Reels 1080×1920px", "头像 320×320px"], defaultRatio: "帖子 1080×1350px", ratioGroups: { set: { ratios: ["1080×1080px\u00a0\u00a0\u00a01:1", "1080×1350px\u00a0\u00a0\u00a04:5"], defaultRatio: "1080×1080px\u00a0\u00a0\u00a01:1", }, detail: { ratios: ["1080×1350px\u00a0\u00a0\u00a04:5"], defaultRatio: "1080×1350px\u00a0\u00a0\u00a04:5", }, model: { ratios: ["1080×1350px\u00a0\u00a0\u00a04:5"], defaultRatio: "1080×1350px\u00a0\u00a0\u00a04:5", }, video: { ratios: ["1080×1920px\u00a0\u00a0\u00a09:16", "1080×1350px\u00a0\u00a0\u00a04:5"], defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16", }, }, specs: ["帖子 1080×1350px 或 1080×1080px", "Stories / Reels 封面 1080×1920px,头像 320×320px"], tip: "建议 ≤8MB JPG。", aliases: ["Instagram Reels"], }, { label: "速卖通", ratios: ["主图 800×800px", "主图 1000×1000px+"], defaultRatio: "主图 800×800px", ratioGroups: { set: { ratios: ["1000×1000px\u00a0\u00a0\u00a01:1"], defaultRatio: "1000×1000px\u00a0\u00a0\u00a01:1", }, detail: { ratios: ["750×1125px\u00a0\u00a0\u00a02:3", "750×1000px\u00a0\u00a0\u00a03:4"], defaultRatio: "750×1125px\u00a0\u00a0\u00a02:3", }, model: { ratios: ["750×1125px\u00a0\u00a0\u00a02:3"], defaultRatio: "750×1125px\u00a0\u00a0\u00a02:3", }, video: { ratios: ["1080×1920px\u00a0\u00a0\u00a09:16", "1920×1080px\u00a0\u00a0\u00a016:9"], defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16", }, hot: { ratios: ["800×800px\u00a0\u00a0\u00a01:1"], defaultRatio: "800×800px\u00a0\u00a0\u00a01:1", }, }, specs: ["主图建议 800×800px 或更高,1:1", "适合跨境电商主图、SKU 图和场景图"], }, { label: "eBay", ratios: ["商品图1:1", "白底多角度展示图 1:1"], defaultRatio: "商品图1:1", ratioGroups: { set: { ratios: ["1600×1600px\u00a0\u00a0\u00a01:1"], defaultRatio: "1600×1600px\u00a0\u00a0\u00a01:1", }, detail: { ratios: ["1000×1500px\u00a0\u00a0\u00a02:3", "1200×1600px\u00a0\u00a0\u00a03:4"], defaultRatio: "1000×1500px\u00a0\u00a0\u00a02:3", }, model: { ratios: ["1000×1500px\u00a0\u00a0\u00a02:3"], defaultRatio: "1000×1500px\u00a0\u00a0\u00a02:3", }, video: { ratios: ["1920×1080px\u00a0\u00a0\u00a016:9", "1080×1920px\u00a0\u00a0\u00a09:16"], defaultRatio: "1920×1080px\u00a0\u00a0\u00a016:9", }, hot: { ratios: ["1600×1600px\u00a0\u00a0\u00a01:1"], defaultRatio: "1600×1600px\u00a0\u00a0\u00a01:1", }, }, specs: ["商品图建议 1:1,主体清晰居中", "适合白底主图和多角度展示图"], }, { label: "TikTok Shop", ratios: ["商品主图 1:1", "短视频/ 竖版封面 9:16"], defaultRatio: "商品主图 1:1", ratioGroups: { set: { ratios: ["1280×1280px\u00a0\u00a0\u00a01:1"], defaultRatio: "1280×1280px\u00a0\u00a0\u00a01:1", }, detail: { ratios: ["1080×1350px\u00a0\u00a0\u00a04:5"], defaultRatio: "1080×1350px\u00a0\u00a0\u00a04:5", }, model: { ratios: ["1080×1350px\u00a0\u00a0\u00a04:5"], defaultRatio: "1080×1350px\u00a0\u00a0\u00a04:5", }, video: { ratios: ["1080×1920px\u00a0\u00a0\u00a09:16"], defaultRatio: "1080×1920px\u00a0\u00a0\u00a09:16", }, hot: { ratios: ["800×800px\u00a0\u00a0\u00a01:1"], defaultRatio: "800×800px\u00a0\u00a0\u00a01:1", }, }, specs: ["商品主图建议 1:1", "短视频竖版封面建议 9:16"], }, ], marketLanguageOptions: [ { country: "中国", languages: ["中文"] }, { country: "美国", languages: ["英文"] }, { country: "加拿大", languages: ["英文", "法文"] }, { country: "英国", languages: ["英文"] }, { country: "德国", languages: ["德文"] }, { country: "法国", languages: ["法文"] }, { country: "意大利", languages: ["意大利语"] }, { country: "西班牙", languages: ["西班牙语"] }, { country: "日本", languages: ["日文"] }, { country: "韩国", languages: ["韩文"] }, { country: "澳大利亚", languages: ["英文"] }, { country: "新加坡", languages: ["英文", "中文"] }, { country: "马来西亚", languages: ["马来语", "英文", "中文"] }, { country: "印尼", languages: ["印度尼西亚语", "英文"] }, { country: "越南", languages: ["越南语", "英文"] }, { country: "泰国", languages: ["泰语", "英文"] }, { country: "菲律宾", languages: ["菲律宾语(他加禄语)", "英文"] }, { country: "巴西", languages: ["葡萄牙语"] }, { country: "墨西哥", languages: ["西班牙语"] }, { country: "智利", languages: ["西班牙语"] }, { country: "哥伦比亚", languages: ["西班牙语"] }, { country: "阿联酋", languages: ["阿拉伯语", "英文"] }, { country: "沙特阿拉伯", languages: ["阿拉伯语", "英文"] }, { country: "俄罗斯", languages: ["俄语"] }, { country: "波兰", languages: ["波兰语"] }, ], languageAliases: { "英文": "英文", "中文": "中文", "英语": "英文", "日语": "日文", "日文": "日文", "德语": "德文", "德文": "德文", "法语": "法文", "法文": "法文", "韩语": "韩文", "韩文": "韩文", "西文": "西班牙语", "西班牙语": "西班牙语", "葡文": "葡萄牙语", "葡萄牙语": "葡萄牙语", "印尼语": "印度尼西亚语", "印度尼西亚语": "印度尼西亚语", "菲律宾语": "菲律宾语(他加禄语)", "菲律宾语(他加禄语)": "菲律宾语(他加禄语)", }, legacyPlatformAliases: { "淘宝/天猫": "淘宝/天猫", "京东": "京东", "拼多多": "拼多多", "抖音电商": "抖音电商", "亚马逊Amazon": "亚马逊 Amazon", "速卖通": "速卖通", }, domesticPlatformLabels: ["淘宝/天猫", "京东", "拼多多", "抖音电商"], domesticPlatformLanguages: ["中文"], defaultEcommercePlatform: "淘宝/天猫", }; interface PlatformRulesPayload { name: string; config: Partial; } let cached: PlatformRulesData | null = null; let loadPromise: Promise | null = null; function isNonEmptyArray(value: unknown): boolean { return Array.isArray(value) && value.length > 0; } // 合并 API 返回与 fallback:仅当 API 字段有效(非空)时覆盖,避免后端漏配某字段导致 UI 空白。 function mergeWithFallback(config: Partial): PlatformRulesData { return { platformSpecOptions: isNonEmptyArray(config.platformSpecOptions) ? (config.platformSpecOptions as EcommercePlatformSpec[]) : FALLBACK_PLATFORM_RULES.platformSpecOptions, marketLanguageOptions: isNonEmptyArray(config.marketLanguageOptions) ? (config.marketLanguageOptions as MarketLanguageOption[]) : FALLBACK_PLATFORM_RULES.marketLanguageOptions, languageAliases: config.languageAliases && typeof config.languageAliases === "object" ? config.languageAliases : FALLBACK_PLATFORM_RULES.languageAliases, legacyPlatformAliases: config.legacyPlatformAliases && typeof config.legacyPlatformAliases === "object" ? config.legacyPlatformAliases : FALLBACK_PLATFORM_RULES.legacyPlatformAliases, domesticPlatformLabels: isNonEmptyArray(config.domesticPlatformLabels) ? (config.domesticPlatformLabels as string[]) : FALLBACK_PLATFORM_RULES.domesticPlatformLabels, domesticPlatformLanguages: isNonEmptyArray(config.domesticPlatformLanguages) ? (config.domesticPlatformLanguages as string[]) : FALLBACK_PLATFORM_RULES.domesticPlatformLanguages, defaultEcommercePlatform: typeof config.defaultEcommercePlatform === "string" && config.defaultEcommercePlatform.trim() ? config.defaultEcommercePlatform : FALLBACK_PLATFORM_RULES.defaultEcommercePlatform, }; } async function fetchPlatformRules(): Promise { const payload = await serverRequest( "public/config/profile?name=web-ecommerce-platform-rules", { maxRetries: 2, timeoutMs: 8_000, fallbackMessage: "加载电商平台规则失败" }, ); return mergeWithFallback(payload?.config ?? {}); } /** 预加载平台规则。App 启动 gating 调用,await 其完成(带超时,失败用 fallback)。可安全重复调用。 */ export async function preloadPlatformRules(): Promise { if (loadPromise) return loadPromise.then(() => undefined); loadPromise = fetchPlatformRules() .then((data) => { cached = data; return data; }) .catch((error) => { console.warn("[platformRules] 加载失败,使用 fallback 数据", error); cached = null; loadPromise = null; return FALLBACK_PLATFORM_RULES; }); return loadPromise.then(() => undefined); } /** 同步获取平台规则。未加载时返回 fallback(=当前生产值,永远可用)。 */ export function getPlatformRules(): PlatformRulesData { return cached ?? FALLBACK_PLATFORM_RULES; }