From ad38a4a0e389bfdefef320793f4c09cafb14c310 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 16 Jun 2026 21:47:07 +0800 Subject: [PATCH] feat(ecommerce): add one-click copywriting tool with quick-board entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add EcommerceCopywritingPanel component - Wire copywriting tool into EcommercePage routing and state - Add quick action entry; place before '更多功能' - Add copywriting styles aligned with quick-set/hot-clone pages - Merge latest main --- src/features/ecommerce/EcommercePage.tsx | 413 +++++- .../panels/EcommerceCopywritingPanel.tsx | 289 ++++ src/styles/ecommerce-standalone.css | 1204 +++++++++++++---- 3 files changed, 1606 insertions(+), 300 deletions(-) create mode 100644 src/features/ecommerce/panels/EcommerceCopywritingPanel.tsx diff --git a/src/features/ecommerce/EcommercePage.tsx b/src/features/ecommerce/EcommercePage.tsx index aaf32f7..522d327 100644 --- a/src/features/ecommerce/EcommercePage.tsx +++ b/src/features/ecommerce/EcommercePage.tsx @@ -39,6 +39,7 @@ import EcommerceDetailPanel from "./panels/EcommerceDetailPanel"; import EcommerceSetPanel from "./panels/EcommerceSetPanel"; import EcommerceTryOnPanel from "./panels/EcommerceTryOnPanel"; import EcommerceClonePanel from "./panels/EcommerceClonePanel"; +import EcommerceCopywritingPanel from "./panels/EcommerceCopywritingPanel"; import { ecommerceOssScopes, saveUnifiedEcommerceGenerationRecord, deleteEcommerceGenerationRecord } from "./ecommerceGenerationPersistence"; import { downloadResultAsset } from "../workbench/workbenchDownload"; @@ -1712,7 +1713,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const [selectedProductSetPreview, setSelectedProductSetPreview] = useState(null); const [showHostingModal, setShowHostingModal] = useState(false); const [productImages, setProductImages] = useState([]); - const [activeQuickTool, setActiveQuickTool] = useState<"cutout" | "detail" | "watermark" | "image-edit" | "translate" | "hot" | null>(null); + const [activeQuickTool, setActiveQuickTool] = useState<"cutout" | "detail" | "watermark" | "image-edit" | "translate" | "hot" | "quick-set" | "copywriting" | null>(null); const [smartCutoutImage, setSmartCutoutImage] = useState(null); const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState([]); const [smartCutoutBackgroundColor, setSmartCutoutBackgroundColor] = useState("#ffffff"); @@ -1786,6 +1787,11 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const [cloneVideoSmart, setCloneVideoSmart] = useState(true); const [isCloneSettingsCollapsed, setIsCloneSettingsCollapsed] = useState(false); const [isCloneConversationCollapsed, setIsCloneConversationCollapsed] = useState(false); + const [quickSetStatus, setQuickSetStatus] = useState<"idle" | "generating" | "done" | "failed">("idle"); + const [quickSetResultUrls, setQuickSetResultUrls] = useState([]); + const [quickSetProgress, setQuickSetProgress] = useState(0); + const [quickSetRequirement, setQuickSetRequirement] = useState(""); + const quickSetProgressRef = useRef(null); const [previewZoom, setPreviewZoom] = useState(1); const quickSetSelectTimerRef = useRef(null); const openQuickSetSelectRef = useRef(null); @@ -2271,6 +2277,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const canGenerateTryOn = garmentImages.length > 0 && tryOnStatus !== "generating" && tryOnStatus !== "modeling"; const canGenerateDetail = detailProductImages.length > 0 && detailStatus !== "generating"; const canGenerateHot = cloneReferenceImages.length > 0 && hotStatus !== "generating"; + const canGenerateQuickSet = productImages.length > 0 && quickSetStatus !== "generating"; const cloneVideoDurationProgress = ((cloneVideoDuration - cloneVideoDurationMin) / (cloneVideoDurationMax - cloneVideoDurationMin)) * 100; const cloneVideoDurationStyle: CSSProperties = useMemo( @@ -4608,6 +4615,12 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { ); }; + const handleQuickSetAiWrite = () => { + setQuickSetRequirement( + "1.产品名称:无线降噪蓝牙耳机\n2.核心卖点:主动降噪、24H续航、低延迟连接、舒适佩戴\n3.适用人群:通勤、办公、运动和旅行用户\n4.期望场景:地铁通勤、居家办公、户外运动\n5.具体参数:蓝牙5.3、IPX4防水、快充10分钟使用2小时", + ); + }; + const stopHotProgress = () => { if (hotProgressRef.current !== null) { window.clearInterval(hotProgressRef.current); @@ -4615,6 +4628,27 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { } }; + const stopQuickSetProgress = () => { + if (quickSetProgressRef.current !== null) { + window.clearInterval(quickSetProgressRef.current); + quickSetProgressRef.current = null; + } + }; + + const startQuickSetProgress = () => { + stopQuickSetProgress(); + setQuickSetProgress(0); + quickSetProgressRef.current = window.setInterval(() => { + setQuickSetProgress((prev) => { + if (prev >= 90) { + stopQuickSetProgress(); + return 90; + } + return prev + (90 - prev) * 0.06; + }); + }, 500); + }; + const startHotProgress = () => { stopHotProgress(); setHotProgress(0); @@ -4652,6 +4686,42 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { ); }; + const handleQuickSetGenerate = () => { + if (!canGenerateQuickSet) return; + imageAbortRef.current = { current: false }; + lastFailedActionRef.current = null; + startQuickSetProgress(); + setQuickSetStatus("generating"); + void generateSetImages( + productImages, cloneSetCounts, quickSetRequirement, + platform, ratio, language, market, + (s) => { + setQuickSetStatus(s as ProductCloneStatus); + if (s === "done") { + stopQuickSetProgress(); + setQuickSetProgress(100); + } else if (s === "failed") { + stopQuickSetProgress(); + setQuickSetProgress(0); + } + }, + (urls) => { + setQuickSetResultUrls(urls); + const validUrls = urls.filter(Boolean); + if (validUrls.length) { + setQuickSetStatus("done"); + stopQuickSetProgress(); + setQuickSetProgress(100); + } else { + setQuickSetStatus("failed"); + stopQuickSetProgress(); + setQuickSetProgress(0); + } + }, + ); + lastFailedActionRef.current = () => handleQuickSetGenerate(); + }; + const handleHotMaterialMouseEnter = (src: string, event: ReactMouseEvent) => { const rect = event.currentTarget.getBoundingClientRect(); const previewHalfWidth = 150; @@ -4716,6 +4786,37 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { setComposerMenu(null); }; + const openQuickSetPage = () => { + clearSmartCutoutTransition(); + setActiveQuickTool("quick-set"); + setComposerMenu(null); + setIsCloneSettingsCollapsed(false); + setIsQuickPanelCollapsed(false); + }; + + const closeQuickSetPage = () => { + stopQuickSetProgress(); + setActiveQuickTool(null); + setQuickSetStatus("idle"); + setQuickSetResultUrls([]); + setQuickSetProgress(0); + setQuickSetRequirement(""); + setComposerMenu(null); + }; + + const openCopywritingPage = () => { + clearSmartCutoutTransition(); + setActiveQuickTool("copywriting"); + setComposerMenu(null); + setIsCloneSettingsCollapsed(false); + setIsQuickPanelCollapsed(false); + }; + + const closeCopywritingPage = () => { + setActiveQuickTool(null); + setComposerMenu(null); + }; + const resetTask = () => { setSetImages([]); setProductSetRequirement(""); @@ -4779,6 +4880,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const isImageEditTool = isCloneTool && activeQuickTool === "image-edit"; const isTranslateTool = isCloneTool && activeQuickTool === "translate"; const isHotCloneTool = isCloneTool && activeQuickTool === "hot"; + const isQuickSetTool = isCloneTool && activeQuickTool === "quick-set"; + const isCopywritingTool = isCloneTool && activeQuickTool === "copywriting"; const pageLabel = isSetTool ? "商品套图" : isDetail ? "A+/详情页" : isTryOn ? "AI服饰穿戴" : activeToolMeta?.label || "商品工具"; const setPrimaryLabel = setImages.length === 0 @@ -5204,6 +5307,19 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { { key: "ratio", label: "尺寸/比例", value: getQuickSetRatioValue(hotRatio), options: quickSetRatioOptions, onChange: setHotRatio }, ]; + const quickSetBasicSelects: Array<{ + key: CloneBasicSelectKey; + label: string; + value: string; + options: string[]; + onChange: (value: string) => void; + }> = [ + { key: "platform", label: "平台", value: platform, options: platformOptions, onChange: setPlatform }, + { key: "market", label: "国家", value: market, options: marketOptions, onChange: setMarket }, + { key: "language", label: "语种", value: language, options: cloneLanguageOptions, onChange: setLanguage }, + { key: "ratio", label: "尺寸/比例", value: getQuickSetRatioValue(ratio), options: quickSetRatioOptions, onChange: setRatio }, + ]; + const cloneModelSelects: Array<{ key: CloneModelSelectKey; label: string; @@ -6229,15 +6345,23 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { { label: "智能抠图", tone: "cutout", icon: , onClick: openSmartCutoutUpload }, { label: "去除水印", tone: "watermark", icon: , onClick: openWatermarkRemovalPage }, { label: "图片翻译", tone: "translate", icon: , onClick: openImageTranslatePage }, + { label: "商品套图", tone: "product", icon: , onClick: openQuickSetPage }, + { label: "一键文案", tone: "copywriting", icon: , onClick: openCopywritingPage }, + { label: "更多功能", tone: "more", icon: , disabled: true }, ].map((item) => ( + + +
+ 上传商品原图 + {productImages.length ? ( +
productInputRef.current?.click()} + onKeyDown={(event) => openQuickUploadWithKeyboard(event, productInputRef)} + onDragOver={(event) => { event.preventDefault(); event.stopPropagation(); if (event.dataTransfer.types.includes("Files")) setIsProductUploadDragging(true); }} + onDragLeave={(event) => { event.preventDefault(); event.stopPropagation(); if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) setIsProductUploadDragging(false); }} + onDrop={(event) => { event.preventDefault(); event.stopPropagation(); setIsProductUploadDragging(false); const files = Array.from(event.dataTransfer.files); if (files.length) addProductImages(files); }} + > + {renderQuickUploadThumbs(productImages, removeProductImage)} + +
+ ) : ( +
productInputRef.current?.click()} + onKeyDown={(event) => openQuickUploadWithKeyboard(event, productInputRef)} + onDragOver={(event) => { event.preventDefault(); event.stopPropagation(); if (event.dataTransfer.types.includes("Files")) setIsProductUploadDragging(true); }} + onDragLeave={(event) => { event.preventDefault(); event.stopPropagation(); if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) setIsProductUploadDragging(false); }} + onDrop={(event) => { event.preventDefault(); event.stopPropagation(); setIsProductUploadDragging(false); const files = Array.from(event.dataTransfer.files); if (files.length) addProductImages(files); }} + > + + 拖拽或点击上传 + 同一产品,最多 7 张 + + 上传图片 +
+ )} + +
+
+ 基础设置 +
+
+ {quickSetBasicSelects.map((item) => ( + + ))} +
+ {quickSetVisibleSelect ? ( +
+ {quickSetVisibleSelect.options.map((option) => ( + + ))} +
+ ) : null} +
+
+
+ 图片数量 +

可自由调整各类型图片数量,总数 1-16 张

+
+ {cloneSetCountOptions.map((item) => { + const count = cloneSetCounts[item.key]; + const decrementDisabled = count <= 0 || cloneSetTotal <= minCloneSetTotal; + const incrementDisabled = cloneSetTotal >= maxCloneSetTotal; + return ( +
+
+ {item.title} + {item.desc} +
+
+ + {count} + +
+
+ ); + })} +
+
+
+
+ 商品卖点 & 需求 + +
+
+