import { formatAspectRatio, normalizeRatioToken } from "./ratioUtils"; export type ProductSetOutputKey = "set" | "detail" | "model" | "video"; export type CloneOutputKey = ProductSetOutputKey | "hot"; export type PlatformRatioModeKey = ProductSetOutputKey | "hot"; export interface PlatformRatioGroup { ratios: string[]; defaultRatio: string; } export interface EcommercePlatformSpec { label: string; ratios: string[]; defaultRatio: string; ratioGroups?: Partial>; specs: string[]; tip?: string; aliases?: string[]; } export const platformSpecOptions: EcommercePlatformSpec[] = [ { 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"], }, ]; export const platformOptions = platformSpecOptions.map((option) => option.label); const getPlatformLogoText = (value: string) => { const normalized = value.toLowerCase(); if (value.includes("淘宝") || value.includes("天猫")) return "淘"; if (value.includes("京东")) return "京"; if (value.includes("拼多多") || value.includes("鎷煎澶")) return "拼"; if (value.includes("抖音")) return "抖"; if (normalized.includes("amazon")) return "a"; if (normalized.includes("shopee")) return "S"; if (normalized.includes("lazada")) return "L"; if (normalized.includes("instagram")) return "IG"; if (value.includes("速卖通") || value.includes("閫熷崠閫")) return "AE"; if (normalized.includes("ebay")) return "eB"; if (normalized.includes("tiktok")) return "♪"; return value.trim().slice(0, 1).toUpperCase() || "商"; }; const getPlatformLogoVariant = (value: string) => { const normalized = value.toLowerCase(); if (value.includes("淘宝") || value.includes("天猫")) return "taobao"; if (value.includes("京东")) return "jd"; if (value.includes("拼多多") || value.includes("鎷煎澶")) return "pdd"; if (value.includes("抖音")) return "douyin"; if (normalized.includes("amazon")) return "amazon"; if (normalized.includes("shopee")) return "shopee"; if (normalized.includes("lazada")) return "lazada"; if (normalized.includes("instagram")) return "instagram"; if (value.includes("速卖通") || value.includes("閫熷崠閫")) return "aliexpress"; if (normalized.includes("ebay")) return "ebay"; if (normalized.includes("tiktok")) return "tiktok"; return "default"; }; const getPlatformLogoMarks = (value: string) => { if (value.includes("淘宝") || value.includes("天猫")) return ["淘", "猫"]; return [getPlatformLogoText(value)]; }; export const marketLanguageOptions: Array<{ country: string; languages: string[] }> = [ { 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: ["波兰语"] }, ]; export const marketOptions = marketLanguageOptions.map((option) => option.country); export const languageOptions = Array.from(new Set(marketLanguageOptions.flatMap((option) => option.languages))); export const languageAliases: Record = { "英文": "英文", "中文": "中文", "英语": "英文", "日语": "日文", "日文": "日文", "德语": "德文", "德文": "德文", "法语": "法文", "法文": "法文", "韩语": "韩文", "韩文": "韩文", "西文": "西班牙语", "西班牙语": "西班牙语", "葡文": "葡萄牙语", "葡萄牙语": "葡萄牙语", "印尼语": "印度尼西亚语", "印度尼西亚语": "印度尼西亚语", "菲律宾语": "菲律宾语(他加禄语)", "菲律宾语(他加禄语)": "菲律宾语(他加禄语)", }; export const defaultPlatformSpec = platformSpecOptions[0]!; export const getPlatformSpec = (value: string) => platformSpecOptions.find((option) => option.label === value || option.aliases?.includes(value)) ?? defaultPlatformSpec; export const legacyPlatformAliases: Record = { "淘宝/天猫": "淘宝/天猫", "京东": "京东", "拼多多": "拼多多", "抖音电商": "抖音电商", "亚马逊Amazon": "亚马逊 Amazon", "速卖通": "速卖通", }; export const normalizePlatform = (value: string) => getPlatformSpec(legacyPlatformAliases[value] ?? value).label; export const domesticPlatformLabels = new Set(["淘宝/天猫", "京东", "拼多多", "抖音电商"]); export const domesticPlatformLanguages = ["中文"]; export const isDomesticPlatform = (platformValue: string) => domesticPlatformLabels.has(normalizePlatform(platformValue)); export const getPlatformRatioGroup = (value: string, mode?: PlatformRatioModeKey): PlatformRatioGroup => { const platformSpec = getPlatformSpec(value); return (mode ? platformSpec.ratioGroups?.[mode] : null) ?? { ratios: platformSpec.ratios, defaultRatio: platformSpec.defaultRatio, }; }; export const getPlatformRatioOptions = (value: string, mode?: PlatformRatioModeKey) => getPlatformRatioGroup(value, mode).ratios; export const getPlatformDefaultRatio = (value: string, mode?: PlatformRatioModeKey) => getPlatformRatioGroup(value, mode).defaultRatio; export const getUniqueRatioOptions = (ratios: string[]) => Array.from(new Set(ratios)); export const normalizeRatioForPlatform = (platformValue: string, ratioValue: string, mode?: PlatformRatioModeKey) => { const platformRatios = getPlatformRatioOptions(platformValue, mode); if (platformRatios.includes(ratioValue)) return ratioValue; const normalizedRatio = normalizeRatioToken(ratioValue); const matchedRatio = platformRatios.find((option) => normalizeRatioToken(option).includes(normalizedRatio)); return matchedRatio ?? getPlatformDefaultRatio(platformValue, mode); }; export const defaultMarketLanguageOption = marketLanguageOptions[0]!; export const normalizeMarket = (value: string) => marketLanguageOptions.some((option) => option.country === value) ? value : defaultMarketLanguageOption.country; export const normalizeLanguage = (value: string) => languageAliases[value] ?? value; export const uniqueLanguages = (languages: string[]) => Array.from(new Set(languages)); export const appendEnglish = (languages: string[]) => Array.from(new Set([...languages, "英文"])); export const getMarketLanguageOptions = (marketValue: string) => appendEnglish((marketLanguageOptions.find((option) => option.country === marketValue) ?? defaultMarketLanguageOption).languages); export const getPlatformLanguageOptions = (platformValue: string, marketValue: string) => { const marketLanguages = getMarketLanguageOptions(marketValue); if (!isDomesticPlatform(platformValue)) return marketLanguages; const localLanguages = marketLanguages.filter((item) => item !== "英文"); return uniqueLanguages([...localLanguages, ...domesticPlatformLanguages, "英文"]); }; export const getPlatformDefaultLanguage = (platformValue: string, marketValue: string) => isDomesticPlatform(platformValue) ? "中文" : (getPlatformLanguageOptions(platformValue, marketValue)[0] ?? languageOptions[0] ?? "英文"); export const normalizeLanguageForPlatform = (platformValue: string, marketValue: string, languageValue: string) => { const normalizedLanguage = normalizeLanguage(languageValue); const platformLanguages = getPlatformLanguageOptions(platformValue, marketValue); return platformLanguages.includes(normalizedLanguage) ? normalizedLanguage : getPlatformDefaultLanguage(platformValue, marketValue); }; export const defaultEcommercePlatform = "淘宝/天猫"; export const defaultProductSetOutput: ProductSetOutputKey = "set"; export const defaultCloneOutput: CloneOutputKey = "set"; export const formatUploadedImageRatio = (image?: { width?: number; height?: number; format?: string }) => { if (!image) return null; const format = image.format ? `\u00a0\u00a0\u00a0${image.format}` : ""; if (!image.width || !image.height) return `上传图片\u00a0\u00a0\u00a0原图比例${format}`; return `上传图片 ${image.width}×${image.height}px\u00a0\u00a0\u00a0${formatAspectRatio(image.width, image.height)}${format}`; };