import { CloseOutlined, CloudUploadOutlined, FileImageOutlined, MenuFoldOutlined, MenuUnfoldOutlined, SettingOutlined, } from "@ant-design/icons"; import { useEffect, useMemo, useRef, useState, type ChangeEvent, type DragEvent } from "react"; import type { WebViewKey } from "../../types"; import ImageMentionMenu, { getImageMentionQuery, insertImageMentionValue, type MentionImageOption } from "../ecommerce/ImageMentionMenu"; import "../../styles/pages/ecommerce.css"; import "../../styles/pages/size-template.css"; import "../../styles/pages/local-theme-parity.css"; interface SizeTemplatePageProps { isAuthenticated?: boolean; onOpenMore?: () => void; onOpenEcommerce?: () => void; onSelectView?: (view: WebViewKey) => void; } interface SizeTemplatePreset { title: string; group: string; category: string; mainSpec: string; ratio: string; ratioCss: string; limit: string; summary: string; details: string[]; tone: "green" | "cyan" | "violet" | "amber"; } interface SizeTemplateSizeOption { label: string; mainSpec: string; ratio: string; ratioCss: string; } interface SizeTemplateUploadImage { id: string; src: string; name: string; } const sizeTemplateGroups = [ { key: "socialCn", label: "社交/内容平台" }, { key: "socialGlobal", label: "国际社交平台" }, { key: "ecommerce", label: "电商平台" }, { key: "id", label: "证件照/官方证件" }, ]; const socialContentPlatformOptions = [ { label: "微信公众号", category: "微信公众号" }, { label: "小红书", category: "小红书" }, { label: "抖音", category: "抖音/TikTok" }, { label: "B站", category: "B站" }, { label: "微博", category: "微博" }, ]; const internationalSocialPlatformOptions = [ { label: "Instagram", category: "Instagram" }, { label: "YouTube", category: "YouTube" }, { label: "Twitter/X", category: "Twitter/X" }, { label: "Facebook", category: "Facebook" }, { label: "LinkedIn", category: "LinkedIn" }, ]; const ecommercePlatformOptions = [ { label: "淘宝/天猫", category: "淘宝/天猫" }, { label: "京东", category: "京东" }, { label: "拼多多", category: "拼多多" }, { label: "亚马逊", category: "亚马逊" }, { label: "虾皮 Shopee/Lazada", category: "虾皮 Shopee/Lazada" }, ]; const idPhotoPlatformOptions = [ { label: "常规证件照", category: "常规证件照" }, { label: "各国签证 / 护照电子照", category: "各国签证/护照电子照" }, { label: "考试报名照片", category: "考试报名照片" }, { label: "求职相关", category: "求职相关" }, ]; const wechatTypeOptions = [ { label: "头条封面", title: "微信公众号头条封面" }, { label: "次条封面", title: "微信公众号次条封面" }, { label: "正文图", title: "微信公众号正文图" }, ]; const xiaohongshuTypeOptions = [ { label: "笔记封面", title: "小红书笔记封面" }, { label: "头像", title: "小红书头像" }, ]; const douyinTypeOptions = [ { label: "视频封面", title: "抖音/TikTok 视频封面" }, { label: "头像", title: "抖音/TikTok 头像" }, ]; const bilibiliTypeOptions = [ { label: "视频封面", title: "B站视频封面" }, { label: "头像", title: "B站头像" }, { label: "专栏配图", title: "B站专栏配图" }, ]; const weiboTypeOptions = [ { label: "正文图", title: "微博正文图" }, { label: "动图", title: "微博动图" }, { label: "头像", title: "微博头像" }, { label: "背景图", title: "微博背景图" }, ]; const instagramTypeOptions = [ { label: "常规帖子", title: "Instagram 常规帖子" }, { label: "动态 / Reels 封面", title: "Instagram 动态/Reels 封面" }, { label: "头像", title: "Instagram 头像" }, ]; const youtubeTypeOptions = [ { label: "视频缩略图", title: "YouTube 视频缩略图" }, { label: "频道横幅", title: "YouTube 频道横幅" }, { label: "频道头像", title: "YouTube 频道头像" }, ]; const twitterTypeOptions = [ { label: "推文图", title: "Twitter/X 推文图" }, { label: "头部图", title: "Twitter/X 头部图" }, { label: "卡片图", title: "Twitter/X 卡片图" }, { label: "头像", title: "Twitter/X 头像" }, ]; const facebookTypeOptions = [ { label: "帖子图", title: "Facebook 帖子图" }, { label: "桌面封面", title: "Facebook 桌面封面" }, { label: "移动端封面", title: "Facebook 移动端封面" }, { label: "故事", title: "Facebook 故事" }, { label: "群组封面", title: "Facebook 群组封面" }, { label: "头像", title: "Facebook 头像" }, ]; const linkedInTypeOptions = [ { label: "帖子图", title: "LinkedIn 帖子图" }, { label: "文章封面", title: "LinkedIn 文章封面" }, { label: "个人背景图", title: "LinkedIn 个人背景图" }, { label: "个人头像", title: "LinkedIn 个人头像" }, { label: "企业 Logo", title: "LinkedIn 企业 Logo" }, { label: "企业横幅", title: "LinkedIn 企业横幅" }, ]; const taobaoTypeOptions = [ { label: "主图 / SKU 图", title: "淘宝/天猫主图/SKU图" }, { label: "详情页", title: "淘宝/天猫详情页" }, ]; const jingdongTypeOptions = [ { label: "主图 / SKU 图", title: "京东主图/SKU图" }, { label: "详情页", title: "京东详情页" }, ]; const pinduoduoTypeOptions = [ { label: "主图", title: "拼多多主图" }, { label: "详情页", title: "拼多多详情页" }, ]; const amazonTypeOptions = [{ label: "主图", title: "亚马逊主图" }]; const shopeeLazadaTypeOptions = [{ label: "商品主图", title: "虾皮 Shopee/Lazada 商品主图" }]; const regularIdPhotoTypeOptions = [ { label: "一寸照", title: "一寸照" }, { label: "二寸照", title: "二寸照" }, { label: "小二寸", title: "小二寸(护照/签证)" }, ]; const visaPassportTypeOptions = [ { label: "中国护照", title: "中国护照" }, { label: "美国签证", title: "美国签证" }, { label: "英国签证", title: "英国签证" }, { label: "加拿大签证", title: "加拿大签证" }, ]; const examPhotoTypeOptions = [ { label: "国考", title: "国考报名照片" }, { label: "英语四六级", title: "英语四六级报名照片" }, { label: "教资", title: "教资报名照片" }, ]; const jobPhotoTypeOptions = [ { label: "简历照片", title: "简历照片" }, { label: "LinkedIn 头像", title: "求职 LinkedIn 头像" }, ]; const typeOptionsByCategory = { 微信公众号: wechatTypeOptions, 小红书: xiaohongshuTypeOptions, "抖音/TikTok": douyinTypeOptions, B站: bilibiliTypeOptions, 微博: weiboTypeOptions, Instagram: instagramTypeOptions, YouTube: youtubeTypeOptions, "Twitter/X": twitterTypeOptions, Facebook: facebookTypeOptions, LinkedIn: linkedInTypeOptions, "淘宝/天猫": taobaoTypeOptions, 京东: jingdongTypeOptions, 拼多多: pinduoduoTypeOptions, 亚马逊: amazonTypeOptions, "虾皮 Shopee/Lazada": shopeeLazadaTypeOptions, 常规证件照: regularIdPhotoTypeOptions, "各国签证/护照电子照": visaPassportTypeOptions, 考试报名照片: examPhotoTypeOptions, 求职相关: jobPhotoTypeOptions, }; const sizeOptionsByPresetTitle: Record = { 小红书笔记封面: [ { label: "1080×1440px", mainSpec: "1080×1440px", ratio: "3:4", ratioCss: "3 / 4" }, { label: "1080×1080px", mainSpec: "1080×1080px", ratio: "1:1", ratioCss: "1 / 1" }, ], "Instagram 常规帖子": [ { label: "1080×1350px", mainSpec: "1080×1350px", ratio: "4:5", ratioCss: "4 / 5" }, { label: "1080×1080px", mainSpec: "1080×1080px", ratio: "1:1", ratioCss: "1 / 1" }, ], "淘宝/天猫详情页": [ { label: "宽750px", mainSpec: "宽750px", ratio: "高≤1546px", ratioCss: "750 / 1546" }, { label: "宽790px", mainSpec: "宽790px", ratio: "高≤1546px", ratioCss: "790 / 1546" }, ], 拼多多主图: [ { label: "750×352px", mainSpec: "750×352px", ratio: "750:352", ratioCss: "750 / 352" }, { label: "800×800px", mainSpec: "800×800px", ratio: "1:1", ratioCss: "1 / 1" }, ], "虾皮 Shopee/Lazada 商品主图": [ { label: "推荐 1024×1024px", mainSpec: "1024×1024px", ratio: "1:1", ratioCss: "1 / 1" }, { label: "基础 800×800px", mainSpec: "800×800px", ratio: "1:1", ratioCss: "1 / 1" }, ], }; const sizeTemplatePresets: SizeTemplatePreset[] = [ { title: "微信公众号头条封面", group: "socialCn", category: "微信公众号", mainSpec: "900×383px", ratio: "2.35:1", ratioCss: "900 / 383", limit: "≤5MB", summary: "头条封面:900×383px,2.35:1,≤5MB。", details: ["次条封面:200×200px,1:1", "正文图:宽≤1080px,单张≤10MB"], tone: "green", }, { title: "微信公众号次条封面", group: "socialCn", category: "微信公众号", mainSpec: "200×200px", ratio: "1:1", ratioCss: "1 / 1", limit: "按平台限制导出", summary: "次条封面:200×200px,1:1。", details: ["头条封面:900×383px,2.35:1,≤5MB", "正文图:宽≤1080px,单张≤10MB"], tone: "green", }, { title: "微信公众号正文图", group: "socialCn", category: "微信公众号", mainSpec: "宽≤1080px", ratio: "自适应", ratioCss: "4 / 3", limit: "单张≤10MB", summary: "正文图:宽≤1080px,单张≤10MB。", details: ["头条封面:900×383px,2.35:1,≤5MB", "次条封面:200×200px,1:1"], tone: "green", }, { title: "小红书笔记封面", group: "socialCn", category: "小红书", mainSpec: "1080×1440px", ratio: "3:4", ratioCss: "3 / 4", limit: "单张≤20MB", summary: "笔记封面:1080×1440px(3:4),单张≤20MB。", details: ["可在尺寸中切换 1080×1080px,1:1", "头像:400×400px,1:1"], tone: "cyan", }, { title: "小红书头像", group: "socialCn", category: "小红书", mainSpec: "400×400px", ratio: "1:1", ratioCss: "1 / 1", limit: "按平台限制导出", summary: "头像:400×400px,1:1。", details: ["笔记封面:1080×1440px(3:4)、1080×1080px(1:1),单张≤20MB"], tone: "cyan", }, { title: "抖音/TikTok 视频封面", group: "socialCn", category: "抖音/TikTok", mainSpec: "1080×1920px", ratio: "9:16", ratioCss: "9 / 16", limit: "≤2MB", summary: "视频封面:1080×1920px,9:16,≤2MB(上下 15% 区域会被遮挡)。", details: ["头像:200×200px,1:1"], tone: "violet", }, { title: "抖音/TikTok 头像", group: "socialCn", category: "抖音/TikTok", mainSpec: "200×200px", ratio: "1:1", ratioCss: "1 / 1", limit: "按平台限制导出", summary: "头像:200×200px,1:1。", details: ["视频封面:1080×1920px,9:16,≤2MB(上下 15% 区域会被遮挡)"], tone: "violet", }, { title: "B站视频封面", group: "socialCn", category: "B站", mainSpec: "1146×717px", ratio: "16:10", ratioCss: "1146 / 717", limit: "≤2MB", summary: "视频封面:1146×717px,16:10,≤2MB。", details: ["头像:300×300px,1:1", "专栏配图:宽度≤1000px"], tone: "amber", }, { title: "B站头像", group: "socialCn", category: "B站", mainSpec: "300×300px", ratio: "1:1", ratioCss: "1 / 1", limit: "按平台限制导出", summary: "头像:300×300px,1:1。", details: ["视频封面:1146×717px,16:10,≤2MB", "专栏配图:宽度≤1000px"], tone: "amber", }, { title: "B站专栏配图", group: "socialCn", category: "B站", mainSpec: "宽度≤1000px", ratio: "自适应", ratioCss: "4 / 3", limit: "按平台限制导出", summary: "专栏配图:宽度≤1000px。", details: ["视频封面:1146×717px,16:10,≤2MB", "头像:300×300px,1:1"], tone: "amber", }, { title: "微博正文图", group: "socialCn", category: "微博", mainSpec: "推荐宽1080px", ratio: "长图≤1:3", ratioCss: "4 / 3", limit: "单张≤20MB", summary: "正文图:推荐宽 1080px,单张≤20MB;长图比例≤1:3。", details: ["动图:≤5MB", "头像:200×200px,1:1", "背景图:920×300px"], tone: "green", }, { title: "微博动图", group: "socialCn", category: "微博", mainSpec: "按内容导出", ratio: "自适应", ratioCss: "4 / 3", limit: "≤5MB", summary: "动图:≤5MB。", details: ["正文图:推荐宽 1080px,单张≤20MB;长图比例≤1:3", "头像:200×200px,1:1", "背景图:920×300px"], tone: "green", }, { title: "微博头像", group: "socialCn", category: "微博", mainSpec: "200×200px", ratio: "1:1", ratioCss: "1 / 1", limit: "按平台限制导出", summary: "头像:200×200px,1:1。", details: ["正文图:推荐宽 1080px,单张≤20MB;长图比例≤1:3", "动图:≤5MB", "背景图:920×300px"], tone: "green", }, { title: "微博背景图", group: "socialCn", category: "微博", mainSpec: "920×300px", ratio: "920:300", ratioCss: "920 / 300", limit: "按平台限制导出", summary: "背景图:920×300px。", details: ["正文图:推荐宽 1080px,单张≤20MB;长图比例≤1:3", "动图:≤5MB", "头像:200×200px,1:1"], tone: "green", }, { title: "Instagram 常规帖子", group: "socialGlobal", category: "Instagram", mainSpec: "1080×1350px", ratio: "4:5", ratioCss: "4 / 5", limit: "图片≤1MB", summary: "常规帖子:1080×1350px(4:5 推荐),图片≤1MB。", details: ["可在尺寸中切换 1080×1080px,1:1", "动态 / Reels 封面:1080×1920px,9:16", "头像:320×320px,1:1"], tone: "cyan", }, { title: "Instagram 动态/Reels 封面", group: "socialGlobal", category: "Instagram", mainSpec: "1080×1920px", ratio: "9:16", ratioCss: "9 / 16", limit: "图片≤1MB", summary: "动态 / Reels 封面:1080×1920px,9:16,图片≤1MB。", details: ["常规帖子:1080×1350px(4:5 推荐)或 1080×1080px(1:1)", "头像:320×320px,1:1"], tone: "cyan", }, { title: "Instagram 头像", group: "socialGlobal", category: "Instagram", mainSpec: "320×320px", ratio: "1:1", ratioCss: "1 / 1", limit: "图片≤1MB", summary: "头像:320×320px,1:1,图片≤1MB。", details: ["常规帖子:1080×1350px(4:5 推荐)或 1080×1080px(1:1)", "动态 / Reels 封面:1080×1920px,9:16"], tone: "cyan", }, { title: "YouTube 视频缩略图", group: "socialGlobal", category: "YouTube", mainSpec: "1280×720px", ratio: "16:9", ratioCss: "16 / 9", limit: "宽≥640px,≤2MB", summary: "视频缩略图:1280×720px,16:9,宽≥640px,≤2MB。", details: ["频道横幅:2560×1440px;安全区 1546×423px", "频道头像:800×800px,1:1"], tone: "amber", }, { title: "YouTube 频道横幅", group: "socialGlobal", category: "YouTube", mainSpec: "2560×1440px", ratio: "16:9", ratioCss: "16 / 9", limit: "安全区1546×423px", summary: "频道横幅:2560×1440px;安全区 1546×423px。", details: ["视频缩略图:1280×720px,16:9,宽≥640px,≤2MB", "频道头像:800×800px,1:1"], tone: "amber", }, { title: "YouTube 频道头像", group: "socialGlobal", category: "YouTube", mainSpec: "800×800px", ratio: "1:1", ratioCss: "1 / 1", limit: "按平台限制导出", summary: "频道头像:800×800px,1:1。", details: ["视频缩略图:1280×720px,16:9,宽≥640px,≤2MB", "频道横幅:2560×1440px;安全区 1546×423px"], tone: "amber", }, { title: "Twitter/X 推文图", group: "socialGlobal", category: "Twitter/X", mainSpec: "1600×900px", ratio: "16:9", ratioCss: "16 / 9", limit: "GIF≤15MB,视频≤512MB", summary: "推文图:1600×900px,16:9。素材限制:GIF≤15MB,视频≤512MB。", details: ["头部图:1500×500px,3:1", "卡片图:800×418px", "头像:400×400px,1:1"], tone: "violet", }, { title: "Twitter/X 头部图", group: "socialGlobal", category: "Twitter/X", mainSpec: "1500×500px", ratio: "3:1", ratioCss: "3 / 1", limit: "GIF≤15MB,视频≤512MB", summary: "头部图:1500×500px,3:1。素材限制:GIF≤15MB,视频≤512MB。", details: ["推文图:1600×900px,16:9", "卡片图:800×418px", "头像:400×400px,1:1"], tone: "violet", }, { title: "Twitter/X 卡片图", group: "socialGlobal", category: "Twitter/X", mainSpec: "800×418px", ratio: "800:418", ratioCss: "800 / 418", limit: "GIF≤15MB,视频≤512MB", summary: "卡片图:800×418px。素材限制:GIF≤15MB,视频≤512MB。", details: ["推文图:1600×900px,16:9", "头部图:1500×500px,3:1", "头像:400×400px,1:1"], tone: "violet", }, { title: "Twitter/X 头像", group: "socialGlobal", category: "Twitter/X", mainSpec: "400×400px", ratio: "1:1", ratioCss: "1 / 1", limit: "GIF≤15MB,视频≤512MB", summary: "头像:400×400px,1:1。素材限制:GIF≤15MB,视频≤512MB。", details: ["推文图:1600×900px,16:9", "头部图:1500×500px,3:1", "卡片图:800×418px"], tone: "violet", }, { title: "Facebook 帖子图", group: "socialGlobal", category: "Facebook", mainSpec: "1200×630px", ratio: "1.91:1", ratioCss: "1200 / 630", limit: "按平台压缩规则导出", summary: "帖子图:1200×630px。", details: ["桌面封面:820×312px", "移动端封面:640×360px", "故事:1080×1920px,9:16", "群组封面:1640×856px", "头像:170×170px,1:1"], tone: "green", }, { title: "Facebook 桌面封面", group: "socialGlobal", category: "Facebook", mainSpec: "820×312px", ratio: "820:312", ratioCss: "820 / 312", limit: "按平台压缩规则导出", summary: "桌面封面:820×312px。", details: ["帖子图:1200×630px", "移动端封面:640×360px", "故事:1080×1920px,9:16", "群组封面:1640×856px", "头像:170×170px,1:1"], tone: "green", }, { title: "Facebook 移动端封面", group: "socialGlobal", category: "Facebook", mainSpec: "640×360px", ratio: "16:9", ratioCss: "16 / 9", limit: "按平台压缩规则导出", summary: "移动端封面:640×360px。", details: ["帖子图:1200×630px", "桌面封面:820×312px", "故事:1080×1920px,9:16", "群组封面:1640×856px", "头像:170×170px,1:1"], tone: "green", }, { title: "Facebook 故事", group: "socialGlobal", category: "Facebook", mainSpec: "1080×1920px", ratio: "9:16", ratioCss: "9 / 16", limit: "按平台压缩规则导出", summary: "故事:1080×1920px,9:16。", details: ["帖子图:1200×630px", "桌面封面:820×312px", "移动端封面:640×360px", "群组封面:1640×856px", "头像:170×170px,1:1"], tone: "green", }, { title: "Facebook 群组封面", group: "socialGlobal", category: "Facebook", mainSpec: "1640×856px", ratio: "1640:856", ratioCss: "1640 / 856", limit: "按平台压缩规则导出", summary: "群组封面:1640×856px。", details: ["帖子图:1200×630px", "桌面封面:820×312px", "移动端封面:640×360px", "故事:1080×1920px,9:16", "头像:170×170px,1:1"], tone: "green", }, { title: "Facebook 头像", group: "socialGlobal", category: "Facebook", mainSpec: "170×170px", ratio: "1:1", ratioCss: "1 / 1", limit: "按平台压缩规则导出", summary: "头像:170×170px,1:1。", details: ["帖子图:1200×630px", "桌面封面:820×312px", "移动端封面:640×360px", "故事:1080×1920px,9:16", "群组封面:1640×856px"], tone: "green", }, { title: "LinkedIn 帖子图", group: "socialGlobal", category: "LinkedIn", mainSpec: "1200×627px", ratio: "1.91:1", ratioCss: "1200 / 627", limit: "按平台压缩规则导出", summary: "帖子图:1200×627px。", details: ["文章封面:1200×644px", "个人背景图:1584×396px", "个人头像:400×400px,1:1", "企业 Logo:300×300px,1:1", "企业横幅:1128×191px"], tone: "cyan", }, { title: "LinkedIn 文章封面", group: "socialGlobal", category: "LinkedIn", mainSpec: "1200×644px", ratio: "1200:644", ratioCss: "1200 / 644", limit: "按平台压缩规则导出", summary: "文章封面:1200×644px。", details: ["帖子图:1200×627px", "个人背景图:1584×396px", "个人头像:400×400px,1:1", "企业 Logo:300×300px,1:1", "企业横幅:1128×191px"], tone: "cyan", }, { title: "LinkedIn 个人背景图", group: "socialGlobal", category: "LinkedIn", mainSpec: "1584×396px", ratio: "4:1", ratioCss: "4 / 1", limit: "按平台限制导出", summary: "个人背景图:1584×396px。", details: ["帖子图:1200×627px", "文章封面:1200×644px", "个人头像:400×400px,1:1", "企业 Logo:300×300px,1:1", "企业横幅:1128×191px"], tone: "cyan", }, { title: "LinkedIn 个人头像", group: "socialGlobal", category: "LinkedIn", mainSpec: "400×400px", ratio: "1:1", ratioCss: "1 / 1", limit: "按平台限制导出", summary: "个人头像:400×400px,1:1。", details: ["帖子图:1200×627px", "文章封面:1200×644px", "个人背景图:1584×396px", "企业 Logo:300×300px,1:1", "企业横幅:1128×191px"], tone: "cyan", }, { title: "LinkedIn 企业 Logo", group: "socialGlobal", category: "LinkedIn", mainSpec: "300×300px", ratio: "1:1", ratioCss: "1 / 1", limit: "按平台限制导出", summary: "企业 Logo:300×300px,1:1。", details: ["帖子图:1200×627px", "文章封面:1200×644px", "个人背景图:1584×396px", "个人头像:400×400px,1:1", "企业横幅:1128×191px"], tone: "cyan", }, { title: "LinkedIn 企业横幅", group: "socialGlobal", category: "LinkedIn", mainSpec: "1128×191px", ratio: "1128:191", ratioCss: "1128 / 191", limit: "按平台限制导出", summary: "企业横幅:1128×191px。", details: ["帖子图:1200×627px", "文章封面:1200×644px", "个人背景图:1584×396px", "个人头像:400×400px,1:1", "企业 Logo:300×300px,1:1"], tone: "cyan", }, { title: "淘宝/天猫主图/SKU图", group: "ecommerce", category: "淘宝/天猫", mainSpec: "800×800px", ratio: "1:1", ratioCss: "1 / 1", limit: "≤3MB", summary: "主图 / SKU 图:800×800px,1:1,≤3MB。", details: ["详情页:宽 750px/790px,单张高≤1546px"], tone: "green", }, { title: "淘宝/天猫详情页", group: "ecommerce", category: "淘宝/天猫", mainSpec: "宽750px", ratio: "高≤1546px", ratioCss: "750 / 1546", limit: "单张高≤1546px", summary: "详情页:宽 750px/790px,单张高≤1546px。", details: ["主图 / SKU 图:800×800px,1:1,≤3MB"], tone: "green", }, { title: "京东主图/SKU图", group: "ecommerce", category: "京东", mainSpec: "800×800px", ratio: "1:1", ratioCss: "1 / 1", limit: "≤1MB(白底)", summary: "主图 / SKU 图:800×800px,1:1,≤1MB(白底)。", details: ["详情页:宽 750px"], tone: "cyan", }, { title: "京东详情页", group: "ecommerce", category: "京东", mainSpec: "宽750px", ratio: "自适应", ratioCss: "4 / 3", limit: "按平台限制导出", summary: "详情页:宽 750px。", details: ["主图 / SKU 图:800×800px,1:1,≤1MB(白底)"], tone: "cyan", }, { title: "拼多多主图", group: "ecommerce", category: "拼多多", mainSpec: "750×352px", ratio: "750:352", ratioCss: "750 / 352", limit: "≤1MB", summary: "主图:750×352px,≤1MB。", details: ["可在尺寸中切换 800×800px,1:1", "详情页:宽 750px"], tone: "amber", }, { title: "拼多多详情页", group: "ecommerce", category: "拼多多", mainSpec: "宽750px", ratio: "自适应", ratioCss: "4 / 3", limit: "按平台限制导出", summary: "详情页:宽 750px。", details: ["主图:750×352px、800×800px(1:1),≤1MB"], tone: "amber", }, { title: "亚马逊主图", group: "ecommerce", category: "亚马逊", mainSpec: "≥1600×1600px", ratio: "1:1", ratioCss: "1 / 1", limit: "≤10MB(纯白底)", summary: "主图:≥1600×1600px,1:1,≤10MB(纯白底),最小 500×500px。", details: ["最小 500×500px"], tone: "violet", }, { title: "虾皮 Shopee/Lazada 商品主图", group: "ecommerce", category: "虾皮 Shopee/Lazada", mainSpec: "1024×1024px", ratio: "1:1", ratioCss: "1 / 1", limit: "≤2MB", summary: "商品主图:推荐 1024×1024px,1:1,≤2MB。", details: ["可在尺寸中切换基础 800×800px,1:1"], tone: "green", }, { title: "一寸照", group: "id", category: "常规证件照", mainSpec: "295×413px", ratio: "295:413", ratioCss: "295 / 413", limit: "按平台限制导出", summary: "一寸照:295×413px。", details: ["二寸照:413×579px", "小二寸(护照 / 签证):413×531px"], tone: "cyan", }, { title: "二寸照", group: "id", category: "常规证件照", mainSpec: "413×579px", ratio: "413:579", ratioCss: "413 / 579", limit: "按平台限制导出", summary: "二寸照:413×579px。", details: ["一寸照:295×413px", "小二寸(护照 / 签证):413×531px"], tone: "cyan", }, { title: "小二寸(护照/签证)", group: "id", category: "常规证件照", mainSpec: "413×531px", ratio: "413:531", ratioCss: "413 / 531", limit: "按平台限制导出", summary: "小二寸(护照 / 签证):413×531px。", details: ["一寸照:295×413px", "二寸照:413×579px"], tone: "cyan", }, { title: "中国护照", group: "id", category: "各国签证/护照电子照", mainSpec: "354×472px", ratio: "354:472", ratioCss: "354 / 472", limit: "≤40KB(白底)", summary: "中国护照:354×472px,≤40KB(白底)。", details: ["美国签证:600×600px,1:1,≤240KB", "英国签证:600×750px", "加拿大签证:420×540px"], tone: "violet", }, { title: "美国签证", group: "id", category: "各国签证/护照电子照", mainSpec: "600×600px", ratio: "1:1", ratioCss: "1 / 1", limit: "≤240KB", summary: "美国签证:600×600px,1:1,≤240KB。", details: ["中国护照:354×472px,≤40KB(白底)", "英国签证:600×750px", "加拿大签证:420×540px"], tone: "violet", }, { title: "英国签证", group: "id", category: "各国签证/护照电子照", mainSpec: "600×750px", ratio: "600:750", ratioCss: "600 / 750", limit: "按平台限制导出", summary: "英国签证:600×750px。", details: ["中国护照:354×472px,≤40KB(白底)", "美国签证:600×600px,1:1,≤240KB", "加拿大签证:420×540px"], tone: "violet", }, { title: "加拿大签证", group: "id", category: "各国签证/护照电子照", mainSpec: "420×540px", ratio: "420:540", ratioCss: "420 / 540", limit: "按平台限制导出", summary: "加拿大签证:420×540px。", details: ["中国护照:354×472px,≤40KB(白底)", "美国签证:600×600px,1:1,≤240KB", "英国签证:600×750px"], tone: "violet", }, { title: "国考报名照片", group: "id", category: "考试报名照片", mainSpec: "宽130px", ratio: "按公告", ratioCss: "4 / 5", limit: "≤30KB", summary: "国考:宽 130px,≤30KB。", details: ["英语四六级:192×144px,≤20KB", "教资:宽 114-480px,≤200KB"], tone: "amber", }, { title: "英语四六级报名照片", group: "id", category: "考试报名照片", mainSpec: "192×144px", ratio: "4:3", ratioCss: "4 / 3", limit: "≤20KB", summary: "英语四六级:192×144px,≤20KB。", details: ["国考:宽 130px,≤30KB", "教资:宽 114-480px,≤200KB"], tone: "amber", }, { title: "教资报名照片", group: "id", category: "考试报名照片", mainSpec: "宽114-480px", ratio: "按公告", ratioCss: "4 / 5", limit: "≤200KB", summary: "教资:宽 114-480px,≤200KB。", details: ["国考:宽 130px,≤30KB", "英语四六级:192×144px,≤20KB"], tone: "amber", }, { title: "简历照片", group: "id", category: "求职相关", mainSpec: "300dpi起", ratio: "按简历要求", ratioCss: "4 / 5", limit: "≤200KB(部分≤100KB)", summary: "简历照片:≤200KB(部分≤100KB),300dpi 起。", details: ["LinkedIn 头像:400×400px,1:1"], tone: "green", }, { title: "求职 LinkedIn 头像", group: "id", category: "求职相关", mainSpec: "400×400px", ratio: "1:1", ratioCss: "1 / 1", limit: "按平台限制导出", summary: "LinkedIn 头像:400×400px,1:1。", details: ["简历照片:≤200KB(部分≤100KB),300dpi 起"], tone: "green", }, ]; function createSizeTemplateUploadItems(files: File[], remainingSlots: number): SizeTemplateUploadImage[] { return files .filter((file) => file.type.startsWith("image/")) .slice(0, remainingSlots) .map((file, index) => ({ id: `size-template-${Date.now()}-${index}-${file.name}`, src: URL.createObjectURL(file), name: file.name, })); } function SizeTemplatePage({ onOpenEcommerce }: SizeTemplatePageProps) { const uploadInputRef = useRef(null); const requirementTextareaRef = useRef(null); const uploadObjectUrlsRef = useRef([]); const [activeGroup, setActiveGroup] = useState(sizeTemplateGroups[0]!.key); const [activePresetTitle, setActivePresetTitle] = useState(sizeTemplatePresets[0]!.title); const [activePanelTab, setActivePanelTab] = useState<"preset" | "detail">("preset"); const [platformDialogOpen, setPlatformDialogOpen] = useState(false); const [typeDialogOpen, setTypeDialogOpen] = useState(false); const [sizeDialogOpen, setSizeDialogOpen] = useState(false); const [activeSizeOptionByTitle, setActiveSizeOptionByTitle] = useState>({}); const [uploadedImages, setUploadedImages] = useState([]); const [isUploadDragging, setIsUploadDragging] = useState(false); const [isSettingsCollapsed, setIsSettingsCollapsed] = useState(false); const [requirement, setRequirement] = useState(""); const [requirementImageMentionQuery, setRequirementImageMentionQuery] = useState(null); const filteredTemplates = useMemo( () => sizeTemplatePresets.filter((item) => item.group === activeGroup), [activeGroup], ); const selectedPreset = filteredTemplates.find((item) => item.title === activePresetTitle) ?? filteredTemplates[0] ?? sizeTemplatePresets[0]!; const platformOptions = activeGroup === "socialCn" ? socialContentPlatformOptions : activeGroup === "socialGlobal" ? internationalSocialPlatformOptions : activeGroup === "ecommerce" ? ecommercePlatformOptions : activeGroup === "id" ? idPhotoPlatformOptions : []; const selectedPlatformLabel = platformOptions.length ? (platformOptions.find((item) => item.category === selectedPreset.category)?.label ?? selectedPreset.category) : selectedPreset.category; const typeOptions = typeOptionsByCategory[selectedPreset.category as keyof typeof typeOptionsByCategory] ?? []; const selectedTypeLabel = typeOptions.find((option) => option.title === selectedPreset.title)?.label ?? selectedPreset.title; const sizeOptions = sizeOptionsByPresetTitle[selectedPreset.title] ?? []; const selectedSizeOption = sizeOptions.find((option) => option.mainSpec === activeSizeOptionByTitle[selectedPreset.title]) ?? sizeOptions[0]; const selectedMainSpec = selectedSizeOption?.mainSpec ?? selectedPreset.mainSpec; const selectedRatio = selectedSizeOption?.ratio ?? selectedPreset.ratio; const primaryUploadedImage = uploadedImages[0] ?? null; const sizeTemplateMentionImages: MentionImageOption[] = uploadedImages.map((image, index) => ({ ...image, label: `上传图 ${index + 1}`, })); const syncRequirementMentionQuery = (value: string, selectionStart: number | null | undefined) => { setRequirementImageMentionQuery(sizeTemplateMentionImages.length ? getImageMentionQuery(value, selectionStart) : null); }; const insertRequirementImageMention = (image: MentionImageOption) => { const textarea = requirementTextareaRef.current; const cursor = textarea?.selectionStart ?? requirement.length; const next = insertImageMentionValue(requirement, cursor, image.name, 500); setRequirement(next.value); setRequirementImageMentionQuery(null); window.requestAnimationFrame(() => { requirementTextareaRef.current?.focus(); requirementTextareaRef.current?.setSelectionRange(next.selectionStart, next.selectionStart); }); }; useEffect( () => () => { uploadObjectUrlsRef.current.forEach((url) => URL.revokeObjectURL(url)); uploadObjectUrlsRef.current = []; }, [], ); const addUploadedImages = (files: File[]) => { const remainingSlots = 7 - uploadedImages.length; if (remainingSlots <= 0) return; const nextImages = createSizeTemplateUploadItems(files, remainingSlots); if (!nextImages.length) return; uploadObjectUrlsRef.current.push(...nextImages.map((item) => item.src)); setUploadedImages((current) => [...current, ...nextImages].slice(0, 7)); }; const handleUploadChange = (event: ChangeEvent) => { const files = event.target.files; if (!files?.length) return; addUploadedImages(Array.from(files)); event.target.value = ""; }; const handleUploadDrop = (event: DragEvent) => { event.preventDefault(); setIsUploadDragging(false); const files = Array.from(event.dataTransfer.files); if (files.length) addUploadedImages(files); }; const removeUploadedImage = (imageId: string) => { setUploadedImages((current) => { const removed = current.find((item) => item.id === imageId); if (removed) { URL.revokeObjectURL(removed.src); uploadObjectUrlsRef.current = uploadObjectUrlsRef.current.filter((url) => url !== removed.src); } return current.filter((item) => item.id !== imageId); }); }; const selectGroup = (group: string) => { setActiveGroup(group); setPlatformDialogOpen(false); setTypeDialogOpen(false); setSizeDialogOpen(false); const firstPreset = sizeTemplatePresets.find((item) => item.group === group); if (firstPreset) setActivePresetTitle(firstPreset.title); }; const selectPlatform = (category: string) => { const nextPreset = sizeTemplatePresets.find((item) => item.group === activeGroup && item.category === category); if (nextPreset) setActivePresetTitle(nextPreset.title); setPlatformDialogOpen(false); setTypeDialogOpen(false); setSizeDialogOpen(false); }; const selectType = (title: string) => { setActivePresetTitle(title); setPlatformDialogOpen(false); setTypeDialogOpen(false); setSizeDialogOpen(false); }; const selectSizeOption = (mainSpec: string) => { setActiveSizeOptionByTitle((current) => ({ ...current, [selectedPreset.title]: mainSpec })); setPlatformDialogOpen(false); setTypeDialogOpen(false); setSizeDialogOpen(false); }; return (