2026-06-02 12:38:01 +08:00
|
|
|
|
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";
|
2026-06-05 19:45:56 +08:00
|
|
|
|
import "../../styles/pages/ecommerce.css";
|
2026-06-05 19:34:36 +08:00
|
|
|
|
import "../../styles/pages/size-template.css";
|
2026-06-05 19:45:56 +08:00
|
|
|
|
import "../../styles/pages/local-theme-parity.css";
|
2026-06-02 12:38:01 +08:00
|
|
|
|
|
|
|
|
|
|
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<string, SizeTemplateSizeOption[]> = {
|
|
|
|
|
|
小红书笔记封面: [
|
|
|
|
|
|
{ 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<HTMLInputElement>(null);
|
|
|
|
|
|
const requirementTextareaRef = useRef<HTMLTextAreaElement>(null);
|
|
|
|
|
|
const uploadObjectUrlsRef = useRef<string[]>([]);
|
|
|
|
|
|
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<Record<string, string>>({});
|
|
|
|
|
|
const [uploadedImages, setUploadedImages] = useState<SizeTemplateUploadImage[]>([]);
|
|
|
|
|
|
const [isUploadDragging, setIsUploadDragging] = useState(false);
|
|
|
|
|
|
const [isSettingsCollapsed, setIsSettingsCollapsed] = useState(false);
|
|
|
|
|
|
const [requirement, setRequirement] = useState("");
|
|
|
|
|
|
const [requirementImageMentionQuery, setRequirementImageMentionQuery] = useState<string | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
const filteredTemplates = useMemo(
|
|
|
|
|
|
() => sizeTemplatePresets.filter((item) => item.group === activeGroup),
|
|
|
|
|
|
[activeGroup],
|
|
|
|
|
|
);
|
|
|
|
|
|
const selectedPreset =
|
|
|
|
|
|
filteredTemplates.find((item) => item.title === activePresetTitle) ?? filteredTemplates[0] ?? sizeTemplatePresets[0]!;
|
|
|
|
|
|
const activeGroupLabel = sizeTemplateGroups.find((item) => item.key === selectedPreset.group)?.label ?? "尺寸模板";
|
|
|
|
|
|
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<HTMLInputElement>) => {
|
|
|
|
|
|
const files = event.target.files;
|
|
|
|
|
|
if (!files?.length) return;
|
|
|
|
|
|
addUploadedImages(Array.from(files));
|
|
|
|
|
|
event.target.value = "";
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleUploadDrop = (event: DragEvent<HTMLElement>) => {
|
|
|
|
|
|
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 (
|
|
|
|
|
|
<section
|
|
|
|
|
|
className={`product-clone-page page-motion size-template-workbench${isSettingsCollapsed ? " is-settings-collapsed" : ""}`}
|
|
|
|
|
|
data-tool="clone"
|
|
|
|
|
|
aria-label="尺寸模板"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="product-clone-shell">
|
|
|
|
|
|
<aside
|
|
|
|
|
|
id="size-template-settings-panel"
|
|
|
|
|
|
className="product-clone-panel"
|
|
|
|
|
|
aria-label="尺寸模板参数"
|
|
|
|
|
|
aria-hidden={isSettingsCollapsed ? true : undefined}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="product-clone-panel__scroll clone-ai-panel size-template-settings-panel">
|
|
|
|
|
|
<section className="clone-ai-card size-template-upload-card">
|
|
|
|
|
|
<h2>
|
|
|
|
|
|
<CloudUploadOutlined />
|
|
|
|
|
|
上传商品原图
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<div
|
|
|
|
|
|
role="button"
|
|
|
|
|
|
tabIndex={0}
|
|
|
|
|
|
className={`clone-ai-upload-zone size-template-upload-zone${isUploadDragging ? " is-dragging" : ""}`}
|
|
|
|
|
|
onClick={() => uploadInputRef.current?.click()}
|
|
|
|
|
|
onKeyDown={(event) => {
|
|
|
|
|
|
if (event.target !== event.currentTarget) return;
|
|
|
|
|
|
if (event.key === "Enter" || event.key === " ") {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
uploadInputRef.current?.click();
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
onDragEnter={(event) => {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
setIsUploadDragging(true);
|
|
|
|
|
|
}}
|
|
|
|
|
|
onDragOver={(event) => event.preventDefault()}
|
|
|
|
|
|
onDragLeave={() => setIsUploadDragging(false)}
|
|
|
|
|
|
onDrop={handleUploadDrop}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="clone-ai-upload-main">
|
|
|
|
|
|
<span className="clone-ai-upload-icon">
|
|
|
|
|
|
<FileImageOutlined />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="clone-ai-upload-title">拖拽或点击上传</span>
|
|
|
|
|
|
<strong>
|
|
|
|
|
|
<span aria-hidden="true">+</span>
|
|
|
|
|
|
上传图片
|
|
|
|
|
|
</strong>
|
|
|
|
|
|
<span className="clone-ai-upload-hint">同一产品,最多 7 张</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{uploadedImages.length ? (
|
|
|
|
|
|
<div className="clone-ai-uploaded-files size-template-uploaded-files" aria-label="已上传商品原图">
|
|
|
|
|
|
{uploadedImages.map((item) => (
|
|
|
|
|
|
<figure key={item.id} className="clone-ai-uploaded-file">
|
|
|
|
|
|
<img src={item.src} alt={item.name} />
|
|
|
|
|
|
<span className="uploaded-image-zoom" aria-hidden="true">
|
|
|
|
|
|
<img src={item.src} alt="" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
aria-label={`移除 ${item.name}`}
|
|
|
|
|
|
onClick={(event) => {
|
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
|
removeUploadedImage(item.id);
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<CloseOutlined />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</figure>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<input ref={uploadInputRef} type="file" accept="image/*" multiple onChange={handleUploadChange} />
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="clone-ai-card size-template-generate-card">
|
|
|
|
|
|
<h2>
|
|
|
|
|
|
<SettingOutlined />
|
|
|
|
|
|
生成设置
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<div className="clone-ai-settings-section">
|
|
|
|
|
|
<span className="clone-ai-settings-label">生成内容</span>
|
|
|
|
|
|
<div className="clone-ai-tag-group size-template-category-grid" role="tablist" aria-label="尺寸模板分类">
|
|
|
|
|
|
{sizeTemplateGroups.map((group) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={group.key}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
role="tab"
|
|
|
|
|
|
aria-selected={activeGroup === group.key}
|
|
|
|
|
|
className={activeGroup === group.key ? "is-active" : ""}
|
|
|
|
|
|
onClick={() => selectGroup(group.key)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{group.label}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="clone-ai-settings-section">
|
|
|
|
|
|
<span className="clone-ai-settings-label">基础信息</span>
|
|
|
|
|
|
<div className="clone-ai-select-group">
|
|
|
|
|
|
{[
|
|
|
|
|
|
{ key: "platform", label: "平台", value: selectedPlatformLabel },
|
|
|
|
|
|
{ key: "type", label: "类型", value: typeOptions.length > 0 ? selectedTypeLabel : selectedPreset.title },
|
|
|
|
|
|
{ key: "size", label: "尺寸", value: selectedMainSpec },
|
|
|
|
|
|
{ key: "ratio", label: "比例", value: selectedRatio },
|
|
|
|
|
|
].map((item) => {
|
|
|
|
|
|
const isPlatformSelect = platformOptions.length > 0 && item.key === "platform";
|
|
|
|
|
|
const isTypeSelect = typeOptions.length > 0 && item.key === "type";
|
|
|
|
|
|
const isSizeSelect = sizeOptions.length > 0 && item.key === "size";
|
|
|
|
|
|
const isClickable = isPlatformSelect || isTypeSelect || isSizeSelect;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={item.label}
|
|
|
|
|
|
className={`clone-ai-basic-select size-template-static-field${isClickable ? " is-clickable" : ""}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
aria-expanded={
|
|
|
|
|
|
isClickable
|
|
|
|
|
|
? isPlatformSelect
|
|
|
|
|
|
? platformDialogOpen
|
|
|
|
|
|
: isTypeSelect
|
|
|
|
|
|
? typeDialogOpen
|
|
|
|
|
|
: sizeDialogOpen
|
|
|
|
|
|
: undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
aria-haspopup={isClickable ? "dialog" : undefined}
|
|
|
|
|
|
onClick={
|
|
|
|
|
|
isPlatformSelect
|
|
|
|
|
|
? () => {
|
|
|
|
|
|
setTypeDialogOpen(false);
|
|
|
|
|
|
setSizeDialogOpen(false);
|
|
|
|
|
|
setPlatformDialogOpen((current) => !current);
|
|
|
|
|
|
}
|
|
|
|
|
|
: isTypeSelect
|
|
|
|
|
|
? () => {
|
|
|
|
|
|
setPlatformDialogOpen(false);
|
|
|
|
|
|
setSizeDialogOpen(false);
|
|
|
|
|
|
setTypeDialogOpen((current) => !current);
|
|
|
|
|
|
}
|
|
|
|
|
|
: isSizeSelect
|
|
|
|
|
|
? () => {
|
|
|
|
|
|
setPlatformDialogOpen(false);
|
|
|
|
|
|
setTypeDialogOpen(false);
|
|
|
|
|
|
setSizeDialogOpen((current) => !current);
|
|
|
|
|
|
}
|
|
|
|
|
|
: undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>{item.label}</span>
|
|
|
|
|
|
<strong>{item.value}</strong>
|
|
|
|
|
|
<i aria-hidden="true" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
{isPlatformSelect && platformDialogOpen ? (
|
|
|
|
|
|
<div className="size-template-platform-dialog" role="dialog" aria-label="选择平台">
|
|
|
|
|
|
{platformOptions.map((option) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={option.category}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={selectedPreset.category === option.category ? "is-active" : ""}
|
|
|
|
|
|
onClick={() => selectPlatform(option.category)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{option.label}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
{isTypeSelect && typeDialogOpen ? (
|
|
|
|
|
|
<div className="size-template-platform-dialog" role="dialog" aria-label="选择类型">
|
|
|
|
|
|
{typeOptions.map((option) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={option.title}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={selectedPreset.title === option.title ? "is-active" : ""}
|
|
|
|
|
|
onClick={() => selectType(option.title)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{option.label}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
{isSizeSelect && sizeDialogOpen ? (
|
|
|
|
|
|
<div className="size-template-platform-dialog" role="dialog" aria-label="选择尺寸">
|
|
|
|
|
|
{sizeOptions.map((option) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={option.mainSpec}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={selectedMainSpec === option.mainSpec ? "is-active" : ""}
|
|
|
|
|
|
onClick={() => selectSizeOption(option.mainSpec)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{option.label}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="clone-ai-model-panel size-template-option-panel" aria-label="平台尺寸设置">
|
|
|
|
|
|
<div className="clone-ai-model-tabs" role="tablist" aria-label="尺寸模板设置类型">
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={activePanelTab === "preset" ? "is-active" : ""}
|
|
|
|
|
|
aria-selected={activePanelTab === "preset"}
|
|
|
|
|
|
onClick={() => setActivePanelTab("preset")}
|
|
|
|
|
|
>
|
|
|
|
|
|
平台尺寸
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={activePanelTab === "detail" ? "is-active" : ""}
|
|
|
|
|
|
aria-selected={activePanelTab === "detail"}
|
|
|
|
|
|
onClick={() => setActivePanelTab("detail")}
|
|
|
|
|
|
>
|
|
|
|
|
|
补充规格
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="clone-ai-model-scroll">
|
|
|
|
|
|
{activePanelTab === "preset" ? (
|
|
|
|
|
|
<div className="clone-ai-model-scene-grid size-template-option-grid">
|
|
|
|
|
|
{filteredTemplates.map((item) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={item.title}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={selectedPreset.title === item.title ? "is-active" : ""}
|
|
|
|
|
|
aria-pressed={selectedPreset.title === item.title}
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
setActivePresetTitle(item.title);
|
|
|
|
|
|
setPlatformDialogOpen(false);
|
|
|
|
|
|
setTypeDialogOpen(false);
|
|
|
|
|
|
setSizeDialogOpen(false);
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span aria-hidden="true" />
|
|
|
|
|
|
{item.title}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="clone-ai-model-scene-grid size-template-option-grid">
|
|
|
|
|
|
{selectedPreset.details.map((item) => (
|
|
|
|
|
|
<button key={item} type="button" className="is-active">
|
|
|
|
|
|
<span aria-hidden="true" />
|
|
|
|
|
|
{item}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<button type="button" className="clone-ai-generate size-template-submit" onClick={onOpenEcommerce}>
|
|
|
|
|
|
✦ 开始生成
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className="clone-ai-settings-toggle"
|
|
|
|
|
|
onClick={() => setIsSettingsCollapsed((current) => !current)}
|
|
|
|
|
|
aria-label={isSettingsCollapsed ? "展开设置面板" : "收起设置面板"}
|
|
|
|
|
|
aria-controls="size-template-settings-panel"
|
|
|
|
|
|
aria-expanded={!isSettingsCollapsed}
|
|
|
|
|
|
title={isSettingsCollapsed ? "展开设置面板" : "收起设置面板"}
|
|
|
|
|
|
>
|
|
|
|
|
|
{isSettingsCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
|
|
<main className="product-clone-preview clone-ai-preview size-template-preview" aria-label="尺寸模板预览">
|
|
|
|
|
|
<header className="clone-ai-preview-header size-template-preview-header">
|
|
|
|
|
|
<strong>预览</strong>
|
|
|
|
|
|
<span>
|
|
|
|
|
|
上传商品图,AI 即刻生成 <b>符合多电商平台规范</b> 的高转化率商品素材。
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<section className={`clone-ai-empty-state size-template-empty-state${primaryUploadedImage ? " has-upload" : ""}`} aria-live="polite">
|
|
|
|
|
|
{primaryUploadedImage ? (
|
|
|
|
|
|
<img className="size-template-empty-state__image" src={primaryUploadedImage.src} alt={primaryUploadedImage.name} />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<FileImageOutlined />
|
|
|
|
|
|
)}
|
|
|
|
|
|
<strong>{primaryUploadedImage ? "已添加参考图" : "等待生成"}</strong>
|
|
|
|
|
|
<span>{primaryUploadedImage ? `已上传 ${uploadedImages.length}/7 张,开始生成后将套用当前尺寸。` : "上传商品原图并填写信息后,AI 将在这里展示生成结果。"}</span>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="clone-ai-bottom-input" aria-label="信息详情">
|
|
|
|
|
|
<div className="clone-ai-input-wrapper">
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
ref={requirementTextareaRef}
|
|
|
|
|
|
value={requirement}
|
|
|
|
|
|
onChange={(event) => {
|
|
|
|
|
|
const nextValue = event.target.value.slice(0, 500);
|
|
|
|
|
|
setRequirement(nextValue);
|
|
|
|
|
|
syncRequirementMentionQuery(nextValue, event.target.selectionStart);
|
|
|
|
|
|
}}
|
|
|
|
|
|
onClick={(event) => syncRequirementMentionQuery(requirement, event.currentTarget.selectionStart)}
|
|
|
|
|
|
onKeyUp={(event) => syncRequirementMentionQuery(requirement, event.currentTarget.selectionStart)}
|
|
|
|
|
|
onKeyDown={(event) => {
|
|
|
|
|
|
if (event.key === "Escape") setRequirementImageMentionQuery(null);
|
|
|
|
|
|
}}
|
|
|
|
|
|
maxLength={500}
|
|
|
|
|
|
placeholder="补充尺寸用途,例如平台、商品类型、是否需要留白或安全区"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{requirementImageMentionQuery !== null && sizeTemplateMentionImages.length ? (
|
|
|
|
|
|
<ImageMentionMenu images={sizeTemplateMentionImages} query={requirementImageMentionQuery} onSelect={insertRequirementImageMention} />
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
<button type="button" className="clone-ai-send-button" onClick={onOpenEcommerce} aria-label="应用到电商生成">
|
|
|
|
|
|
↑
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="clone-ai-char-count">{requirement.length}/500</span>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</main>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default SizeTemplatePage;
|