From 6dd2a107fd3c1c9e1d7b5c2bc5eef59670462d78 Mon Sep 17 00:00:00 2001 From: ludan <251918489@qq.com> Date: Wed, 17 Jun 2026 17:02:10 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E7=94=B5=E5=95=86=E5=88=9B?= =?UTF-8?q?=E4=BD=9C=E5=9C=BA=E6=99=AF=E6=94=AF=E6=8C=81=E5=B8=A6=E8=B4=A7?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E6=97=B6=E9=95=BF=E3=80=81=E7=A7=BB=E5=8A=A8?= =?UTF-8?q?=E7=AB=AF=E9=9A=90=E8=97=8F=E8=AE=BE=E7=BD=AE=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E3=80=81=E4=BC=98=E5=8C=96=E5=B9=B3=E5=8F=B0=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E5=99=A8=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增带货视频时长选项(5/10/15秒)及时长选择 popover - 创作标签在移动端(<=640px)隐藏平台/语种/比例/设置/时长按钮行,仅保留文本输入框 - 重构平台选择器为单列滚动列表,移除平台 logo,统一比例/语种/平台 active 高亮样式 - 优化 composer 整体布局节奏(素材紧凑、工具栏底部固定、响应式高度) - 调整 AI 帮写提交按钮为青色系渐变样式 --- src/features/ecommerce/EcommercePage.tsx | 54 ++- src/styles/ecommerce-standalone.css | 425 ++++++++++++++++++++--- src/styles/pages/ecommerce.css | 187 +++++++++- src/styles/standalone/overrides.css | 54 +-- 4 files changed, 617 insertions(+), 103 deletions(-) diff --git a/src/features/ecommerce/EcommercePage.tsx b/src/features/ecommerce/EcommercePage.tsx index 81d5cc7..e703ea0 100644 --- a/src/features/ecommerce/EcommercePage.tsx +++ b/src/features/ecommerce/EcommercePage.tsx @@ -1429,6 +1429,7 @@ const maxCloneProductImages = 20; const maxCloneReferenceImages = 20; const cloneVideoDurationMin = 5; const cloneVideoDurationMax = 45; +const composerDurationOptions = [5, 10, 15]; const defaultEcommercePlatform = "淘宝/天猫"; const defaultProductSetOutput: ProductSetOutputKey = "set"; const defaultCloneOutput: CloneOutputKey = "set"; @@ -2411,13 +2412,13 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { const cloneRatioOptions = baseCloneRatioOptions; const composerRatioOptions = useMemo( () => [ - "1000×1000px 1:1", - "800×1200px 2:3", - "1200×800px 3:2", - "1200×900px 4:3", - "900×1200px 3:4", - "1080×1920px 9:16", - "1920×1080px 16:9", + "1000×1000px\u00a0\u00a0\u00a01:1", + "800×1200px\u00a0\u00a0\u00a02:3", + "1200×800px\u00a0\u00a0\u00a03:2", + "1200×900px\u00a0\u00a0\u00a04:3", + "900×1200px\u00a0\u00a0\u00a03:4", + "1080×1920px\u00a0\u00a0\u00a09:16", + "1920×1080px\u00a0\u00a0\u00a016:9", ], [], ); @@ -5975,11 +5976,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { } if (menuToRender === "platform") { return ( -
+
{platformOptions.map((option) => ( ))}
@@ -6010,6 +6010,17 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
); } + if (menuToRender === "settings" && activeCommerceScenario === "salesVideo") { + return ( +
+ {composerDurationOptions.map((option) => ( + + ))} +
+ ); + } return (
{cloneOutput === "set" ? ( @@ -6599,14 +6610,18 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { ) : null} {shouldShowScenarioSettings ? (
- - + {activeCommerceScenario !== "salesVideo" && activeCommerceScenario !== "poster" ? ( + + ) : null} + {activeCommerceScenario !== "salesVideo" ? ( + + ) : null} ) : null}
diff --git a/src/styles/ecommerce-standalone.css b/src/styles/ecommerce-standalone.css index ddb620b..46f6904 100644 --- a/src/styles/ecommerce-standalone.css +++ b/src/styles/ecommerce-standalone.css @@ -14328,9 +14328,8 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d row-gap: 10px !important; } - html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings button { - flex: 1 1 calc(50% - 5px) !important; - justify-content: flex-start !important; + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings { + display: none !important; } html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-toolbar { @@ -14339,40 +14338,6 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d } @media (max-width: 420px) { - html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings { - display: flex !important; - flex-wrap: nowrap !important; - gap: 7px !important; - justify-content: stretch !important; - overflow: visible !important; - } - - html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings button { - display: inline-flex !important; - flex: 1 1 0 !important; - width: auto !important; - min-width: 0 !important; - max-width: none !important; - height: 42px !important; - min-height: 42px !important; - padding: 0 !important; - justify-content: center !important; - font-size: 0 !important; - } - - html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings button > span:not(.ecom-command-option-icon) { - display: none !important; - } - - html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings .ecom-command-option-icon { - display: inline-grid !important; - width: 22px !important; - height: 22px !important; - min-width: 22px !important; - margin: 0 !important; - font-size: 14px !important; - } - html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-toolbar { flex-direction: row !important; align-items: center !important; @@ -19482,9 +19447,16 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d border: 0 !important; border-radius: 14px !important; color: #ffffff !important; - background: #181b1f !important; - box-shadow: 0 12px 28px rgba(16, 32, 44, 0.14) !important; + background: linear-gradient(135deg, #1ebddb 0%, #0f829b 100%) !important; + box-shadow: 0 12px 28px rgba(15, 130, 155, 0.22) !important; text-align: center !important; + transition: transform 160ms ease, box-shadow 160ms ease !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-ai-submit:hover { + background: linear-gradient(135deg, #21c8e3 0%, #1194ad 100%) !important; + box-shadow: 0 14px 32px rgba(15, 130, 155, 0.28) !important; + transform: translateY(-1px) !important; } @media (max-width: 640px) { @@ -20125,3 +20097,378 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d color: #0f7895 !important; font-weight: 600 !important; } + + +/* Platform picker: single-column scrollable list without logos, matching language/ratio style. */ +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover--platforms.ecom-command-popover--platforms { + display: flex !important; + flex-direction: column !important; + flex-wrap: nowrap !important; + gap: 2px !important; + width: fit-content !important; + min-width: 0 !important; + max-width: calc(100vw - 48px) !important; + max-height: min(320px, calc(100dvh - 180px)) !important; + padding: 5px !important; + border: 1px solid rgba(16, 115, 204, 0.1) !important; + border-radius: 12px !important; + background: #ffffff !important; + box-shadow: 0 12px 32px rgba(16, 115, 204, 0.1) !important; + overflow-x: hidden !important; + overflow-y: auto !important; + scrollbar-width: thin !important; + scrollbar-color: rgba(16, 115, 204, 0.28) transparent !important; + animation: none !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--platforms.ecom-command-popover--platforms button { + display: flex !important; + flex: 0 0 auto !important; + align-items: center !important; + justify-content: center !important; + width: 100% !important; + height: 36px !important; + min-height: 36px !important; + padding: 0 16px !important; + border: 0 !important; + border-radius: 8px !important; + color: rgba(16, 32, 44, 0.78) !important; + background: transparent !important; + font-size: 14px !important; + font-weight: 500 !important; + white-space: nowrap !important; + animation: none !important; + transition: background 120ms ease, color 120ms ease !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--platforms.ecom-command-popover--platforms button:hover { + background: rgba(30, 189, 219, 0.08) !important; + color: #0f7895 !important; + transform: none !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--platforms.ecom-command-popover--platforms button.is-active { + background: rgba(30, 189, 219, 0.12) !important; + color: #0f7895 !important; + font-weight: 600 !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover--platforms.ecom-command-popover--platforms::-webkit-scrollbar { + width: 5px !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover--platforms.ecom-command-popover--platforms::-webkit-scrollbar-thumb { + background: rgba(16, 115, 204, 0.28) !important; + border-radius: 999px !important; +} + + +/* Ratio & Language pickers: stronger active state so the selected option is clearly visible. */ +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--ratio-picker.ecom-command-popover--ratio-picker button.is-active, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--languages.ecom-command-popover--languages button.is-active { + background: rgba(30, 189, 219, 0.2) !important; + color: #0f7895 !important; + font-weight: 700 !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--ratio-picker.ecom-command-popover--ratio-picker button.is-active::before, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--languages.ecom-command-popover--languages button.is-active::before { + position: absolute !important; + left: 8px !important; + top: 50% !important; + width: 5px !important; + height: 5px !important; + border-radius: 999px !important; + background: #1ebddb !important; + transform: translateY(-50%) scale(1) !important; + opacity: 1 !important; + content: "" !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--ratio-picker.ecom-command-popover--ratio-picker button.is-active, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--languages.ecom-command-popover--languages button.is-active { + position: relative !important; + padding-left: 22px !important; +} + + +/* Platform picker: stronger active state with a visible dot indicator. */ +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--platforms.ecom-command-popover--platforms button.is-active { + position: relative !important; + padding-left: 22px !important; + background: rgba(30, 189, 219, 0.2) !important; + color: #0f7895 !important; + font-weight: 700 !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--platforms.ecom-command-popover--platforms button.is-active::before { + position: absolute !important; + left: 8px !important; + top: 50% !important; + width: 5px !important; + height: 5px !important; + border-radius: 999px !important; + background: #1ebddb !important; + transform: translateY(-50%) scale(1) !important; + opacity: 1 !important; + content: "" !important; +} + + +/* Ratio/Language/Platform pickers: clean active state without dot indicator, keep color highlight only. */ +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--ratio-picker.ecom-command-popover--ratio-picker button.is-active, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--languages.ecom-command-popover--languages button.is-active, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--platforms.ecom-command-popover--platforms button.is-active { + position: static !important; + padding-left: 16px !important; + padding-right: 16px !important; + background: rgba(30, 189, 219, 0.2) !important; + color: #0f7895 !important; + font-weight: 700 !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--ratio-picker.ecom-command-popover--ratio-picker button.is-active::before, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--languages.ecom-command-popover--languages button.is-active::before, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--platforms.ecom-command-popover--platforms button.is-active::before { + content: none !important; +} + + +/* Duration picker: single-column scrollable list for video duration, matching ratio/language style. */ +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover--duration.ecom-command-popover--duration { + display: flex !important; + flex-direction: column !important; + flex-wrap: nowrap !important; + gap: 2px !important; + width: fit-content !important; + min-width: 0 !important; + max-width: calc(100vw - 48px) !important; + max-height: min(320px, calc(100dvh - 180px)) !important; + padding: 5px !important; + border: 1px solid rgba(16, 115, 204, 0.1) !important; + border-radius: 12px !important; + background: #ffffff !important; + box-shadow: 0 12px 32px rgba(16, 115, 204, 0.1) !important; + overflow-x: hidden !important; + overflow-y: auto !important; + scrollbar-width: thin !important; + scrollbar-color: rgba(16, 115, 204, 0.28) transparent !important; + animation: none !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--duration.ecom-command-popover--duration button { + display: flex !important; + flex: 0 0 auto !important; + align-items: center !important; + justify-content: center !important; + width: 100% !important; + height: 36px !important; + min-height: 36px !important; + padding: 0 16px !important; + border: 0 !important; + border-radius: 8px !important; + color: rgba(16, 32, 44, 0.78) !important; + background: transparent !important; + font-size: 14px !important; + font-weight: 500 !important; + white-space: nowrap !important; + animation: none !important; + transition: background 120ms ease, color 120ms ease !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--duration.ecom-command-popover--duration button:hover { + background: rgba(30, 189, 219, 0.08) !important; + color: #0f7895 !important; + transform: none !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-popover--duration.ecom-command-popover--duration button.is-active { + background: rgba(30, 189, 219, 0.2) !important; + color: #0f7895 !important; + font-weight: 700 !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover--duration.ecom-command-popover--duration::-webkit-scrollbar { + width: 5px !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover--duration.ecom-command-popover--duration::-webkit-scrollbar-thumb { + background: rgba(16, 115, 204, 0.28) !important; + border-radius: 999px !important; +} + +/* Final composer rhythm: keep attachments compact and pin the toolbelt to the card bottom. */ +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer { + display: flex !important; + flex-direction: column !important; + justify-content: flex-start !important; + align-items: stretch !important; + gap: clamp(12px, 1.6vw, 18px) !important; + min-height: clamp(250px, 31vh, 316px) !important; + padding: clamp(18px, 2.05vw, 24px) clamp(18px, 2.3vw, 26px) clamp(16px, 1.9vw, 22px) !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) { + min-height: clamp(286px, 35vh, 340px) !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-asset-popover, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-popover { + display: flex !important; + flex-wrap: nowrap !important; + align-items: center !important; + align-self: start !important; + gap: 12px !important; + width: 100% !important; + max-width: 100% !important; + min-height: 64px !important; + margin: 0 !important; + padding: 0 2px 2px !important; + border: 0 !important; + background: transparent !important; + box-shadow: none !important; + overflow-x: auto !important; + overflow-y: hidden !important; + scrollbar-width: none !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-popover::-webkit-scrollbar { + display: none !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-add { + flex: 0 0 64px !important; + width: 64px !important; + height: 64px !important; + min-height: 64px !important; + padding: 0 !important; + border: 0 !important; + border-radius: 15px !important; + background: rgba(30, 189, 219, 0.1) !important; + color: #0b8fb2 !important; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.78) !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add span { + font-size: 24px !important; + line-height: 1 !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add small { + margin-top: 4px !important; + color: #0f7f9e !important; + font-size: 14px !important; + font-weight: 600 !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-thumb, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-thumb { + flex: 0 0 64px !important; + width: 64px !important; + height: 64px !important; + min-height: 64px !important; + border-radius: 15px !important; + box-shadow: 0 12px 28px rgba(16, 115, 204, 0.12) !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-option-row--settings { + align-self: start !important; + margin: 0 !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > textarea { + flex: 1 1 auto !important; + align-self: stretch !important; + width: 100% !important; + min-height: 86px !important; + height: auto !important; + margin: 0 !important; + padding: 0 !important; + line-height: 1.58 !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) > textarea { + min-height: 78px !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-toolbar { + flex: 0 0 auto !important; + align-self: end !important; + width: 100% !important; + margin: auto 0 0 !important; + padding-top: clamp(12px, 1.45vw, 16px) !important; + border-top: 1px solid rgba(30, 189, 219, 0.12) !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-actions { + min-width: 0 !important; +} + +@media (max-width: 640px) { + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer { + gap: 12px !important; + min-height: 318px !important; + padding: 18px 16px 16px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) { + min-height: 336px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-asset-popover, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-popover { + gap: 10px !important; + min-height: 58px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-thumb, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-add, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-thumb { + flex-basis: 58px !important; + width: 58px !important; + height: 58px !important; + min-height: 58px !important; + border-radius: 14px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add span { + font-size: 22px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add small { + font-size: 13px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > textarea, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) > textarea { + min-height: 96px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-toolbar { + min-height: 58px !important; + padding-top: 12px !important; + } +} + +@media (max-width: 390px) { + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer { + min-height: 306px !important; + padding-inline: 14px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) { + min-height: 326px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-thumb, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-add, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-thumb { + flex-basis: 54px !important; + width: 54px !important; + height: 54px !important; + min-height: 54px !important; + } +} diff --git a/src/styles/pages/ecommerce.css b/src/styles/pages/ecommerce.css index 08875be..2302139 100644 --- a/src/styles/pages/ecommerce.css +++ b/src/styles/pages/ecommerce.css @@ -12522,9 +12522,16 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d border: 0 !important; border-radius: 14px !important; color: #ffffff !important; - background: #181b1f !important; - box-shadow: 0 12px 28px rgba(16, 32, 44, 0.14) !important; + background: linear-gradient(135deg, #1ebddb 0%, #0f829b 100%) !important; + box-shadow: 0 12px 28px rgba(15, 130, 155, 0.22) !important; text-align: center !important; + transition: transform 160ms ease, box-shadow 160ms ease !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-ai-submit:hover { + background: linear-gradient(135deg, #21c8e3 0%, #1194ad 100%) !important; + box-shadow: 0 14px 32px rgba(15, 130, 155, 0.28) !important; + transform: translateY(-1px) !important; } @media (max-width: 640px) { @@ -12639,3 +12646,179 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d overflow-y: auto !important; } } + +/* Final composer rhythm: keep attachments compact and pin the toolbelt to the card bottom. */ +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer { + display: flex !important; + flex-direction: column !important; + justify-content: flex-start !important; + align-items: stretch !important; + gap: clamp(12px, 1.6vw, 18px) !important; + min-height: clamp(250px, 31vh, 316px) !important; + padding: clamp(18px, 2.05vw, 24px) clamp(18px, 2.3vw, 26px) clamp(16px, 1.9vw, 22px) !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) { + min-height: clamp(286px, 35vh, 340px) !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-asset-popover, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-popover { + display: flex !important; + flex-wrap: nowrap !important; + align-items: center !important; + align-self: start !important; + gap: 12px !important; + width: 100% !important; + max-width: 100% !important; + min-height: 64px !important; + margin: 0 !important; + padding: 0 2px 2px !important; + border: 0 !important; + background: transparent !important; + box-shadow: none !important; + overflow-x: auto !important; + overflow-y: hidden !important; + scrollbar-width: none !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-popover::-webkit-scrollbar { + display: none !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-add { + flex: 0 0 64px !important; + width: 64px !important; + height: 64px !important; + min-height: 64px !important; + padding: 0 !important; + border: 0 !important; + border-radius: 15px !important; + background: rgba(30, 189, 219, 0.1) !important; + color: #0b8fb2 !important; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.78) !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add span { + font-size: 24px !important; + line-height: 1 !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add small { + margin-top: 4px !important; + color: #0f7f9e !important; + font-size: 14px !important; + font-weight: 600 !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-thumb, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-thumb { + flex: 0 0 64px !important; + width: 64px !important; + height: 64px !important; + min-height: 64px !important; + border-radius: 15px !important; + box-shadow: 0 12px 28px rgba(16, 115, 204, 0.12) !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-option-row--settings { + align-self: start !important; + margin: 0 !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > textarea { + flex: 1 1 auto !important; + align-self: stretch !important; + width: 100% !important; + min-height: 86px !important; + height: auto !important; + margin: 0 !important; + padding: 0 !important; + line-height: 1.58 !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) > textarea { + min-height: 78px !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-toolbar { + flex: 0 0 auto !important; + align-self: end !important; + width: 100% !important; + margin: auto 0 0 !important; + padding-top: clamp(12px, 1.45vw, 16px) !important; + border-top: 1px solid rgba(30, 189, 219, 0.12) !important; +} + +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-actions { + min-width: 0 !important; +} + +@media (max-width: 640px) { + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer { + gap: 12px !important; + min-height: 318px !important; + padding: 18px 16px 16px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) { + min-height: 336px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-asset-popover, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-popover { + gap: 10px !important; + min-height: 58px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-thumb, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-add, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-thumb { + flex-basis: 58px !important; + width: 58px !important; + height: 58px !important; + min-height: 58px !important; + border-radius: 14px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add span { + font-size: 22px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add small { + font-size: 13px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > textarea, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) > textarea { + min-height: 96px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-toolbar { + min-height: 58px !important; + padding-top: 12px !important; + } +} + +@media (max-width: 390px) { + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer { + min-height: 306px !important; + padding-inline: 14px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) { + min-height: 326px !important; + } + + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-add, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-asset-thumb, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-add, + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-composer-wrap:not(.has-generated.is-compact) .clone-ai-input-wrapper.ecom-command-composer:has(.ecom-command-asset-popover) .ecom-command-asset-thumb { + flex-basis: 54px !important; + width: 54px !important; + height: 54px !important; + min-height: 54px !important; + } +} diff --git a/src/styles/standalone/overrides.css b/src/styles/standalone/overrides.css index 272f22b..d4817c3 100644 --- a/src/styles/standalone/overrides.css +++ b/src/styles/standalone/overrides.css @@ -3461,9 +3461,8 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d row-gap: 10px !important; } - html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings button { - flex: 1 1 calc(50% - 5px) !important; - justify-content: flex-start !important; + html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings { + display: none !important; } html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-toolbar { @@ -3472,40 +3471,6 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d } @media (max-width: 420px) { - html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings { - display: flex !important; - flex-wrap: nowrap !important; - gap: 7px !important; - justify-content: stretch !important; - overflow: visible !important; - } - - html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings button { - display: inline-flex !important; - flex: 1 1 0 !important; - width: auto !important; - min-width: 0 !important; - max-width: none !important; - height: 42px !important; - min-height: 42px !important; - padding: 0 !important; - justify-content: center !important; - font-size: 0 !important; - } - - html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings button > span:not(.ecom-command-option-icon) { - display: none !important; - } - - html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-option-row.ecom-command-option-row--settings .ecom-command-option-icon { - display: inline-grid !important; - width: 22px !important; - height: 22px !important; - min-width: 22px !important; - margin: 0 !important; - font-size: 14px !important; - } - html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-toolbar { flex-direction: row !important; align-items: center !important; @@ -3560,15 +3525,18 @@ html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[d z-index: 160 !important; } -html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover--platform { - width: min(360px, calc(100% - var(--composer-popover-left, 0px))) !important; - max-width: min(360px, calc(100% - var(--composer-popover-left, 0px))) !important; +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover--platforms { + width: fit-content !important; + min-width: 0 !important; + max-width: min(320px, calc(100% - var(--composer-popover-left, 0px))) !important; } html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover--languages, -html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover--ratio-picker { - width: min(420px, calc(100% - var(--composer-popover-left, 0px))) !important; - max-width: min(420px, calc(100% - var(--composer-popover-left, 0px))) !important; +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover--ratio-picker, +html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover--duration { + width: fit-content !important; + min-width: 0 !important; + max-width: min(320px, calc(100% - var(--composer-popover-left, 0px))) !important; } html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .clone-ai-input-wrapper.ecom-command-composer > .ecom-command-popover.ecom-command-popover--settings { From 3d72e166edf0f045635ea305e8c9c2ff77630a4e Mon Sep 17 00:00:00 2001 From: ludan <251918489@qq.com> Date: Wed, 17 Jun 2026 18:47:10 +0800 Subject: [PATCH 2/2] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E7=94=B5?= =?UTF-8?q?=E5=95=86=E5=85=A8=E5=9C=BA=E6=99=AF=E7=B4=A0=E6=9D=90=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E4=BD=93=E9=AA=8C=EF=BC=8C=E5=85=88=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E9=A2=84=E8=A7=88=E5=86=8D=E5=90=8E=E5=8F=B0=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=20OSS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 createLocalImageItems 同步创建本地 blob 预览项 - 新增 uploadImageItem 后台异步上传 OSS 并读取图片尺寸 - 改造商品主图、套图、参考图、服饰图、详情图 5 个上传入口 - 选择文件后立即渲染缩略图,OSS 上传在后台并行进行 - 上传完成后按 id 替换为 OSS URL,释放本地 blob URL --- src/features/ecommerce/EcommercePage.tsx | 209 ++++++++++++++++------- 1 file changed, 150 insertions(+), 59 deletions(-) diff --git a/src/features/ecommerce/EcommercePage.tsx b/src/features/ecommerce/EcommercePage.tsx index e703ea0..e7dafd2 100644 --- a/src/features/ecommerce/EcommercePage.tsx +++ b/src/features/ecommerce/EcommercePage.tsx @@ -1559,6 +1559,42 @@ const blobToDataUrl = (blob: Blob): Promise => reader.readAsDataURL(blob); }); +function createLocalImageItems(files: File[], limit: number, prefix: string): CloneImageItem[] { + const selectedFiles = Array.from(files).slice(0, limit); + const stamp = Date.now(); + return selectedFiles.map((file, index) => { + const localPreviewUrl = URL.createObjectURL(file); + const mimeType = normalizeEcommerceImageMime(file.type); + return { + id: `${prefix}-${stamp}-${index}`, + src: localPreviewUrl, + name: file.name, + file, + format: getImageFileFormat(file), + mimeType, + }; + }); +} + +async function uploadImageItem(item: CloneImageItem): Promise<{ src?: string; ossKey?: string; width?: number; height?: number }> { + if (!item.file) return {}; + const mimeType = normalizeEcommerceImageMime(item.file.type); + try { + const uploadBlob = item.file.type === mimeType ? item.file : new Blob([item.file], { type: mimeType }); + const [uploaded, dimensions] = await Promise.all([ + aiGenerationClient.uploadAssetBinary(uploadBlob, { + name: item.file.name, + mimeType, + scope: ecommerceOssScopes.productSource, + }), + readImageDimensions(item.src).catch(() => ({})), + ]); + return { src: uploaded.url, ossKey: uploaded.ossKey, ...dimensions }; + } catch { + return {}; + } +} + async function createUploadedImageItems(files: File[], limit: number, prefix: string): Promise { const selectedFiles = Array.from(files).slice(0, limit); const stamp = Date.now(); @@ -2553,20 +2589,30 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { }); }; - const addSetImages = async (files: File[]) => { + const addSetImages = (files: File[]) => { if (setImages.length >= 3) return; const imageFiles = notifyRejectedImages(files); if (!imageFiles.length) return; - try { - const nextImages = await createUploadedImageItems(imageFiles, 3 - setImages.length, "set"); - setSetImages((current) => { - if (current.length >= 3) return current; - return nextImages.length ? [...current, ...nextImages].slice(0, 3) : current; - }); - setProductSetStatus("ready"); - } catch (err) { - toast.error(err instanceof Error ? err.message : "商品图上传失败"); - } + const remainingSlots = 3 - setImages.length; + if (remainingSlots <= 0) return; + + const localItems = createLocalImageItems(imageFiles, remainingSlots, "set"); + setSetImages((current) => [...current, ...localItems].slice(0, 3)); + setProductSetStatus("ready"); + + Promise.all(localItems.map(async (item) => { + const uploaded = await uploadImageItem(item); + if (uploaded.src) URL.revokeObjectURL(item.src); + return { id: item.id, uploaded }; + })).then((results) => { + const updateMap = new Map(results.map((result) => [result.id, result.uploaded])); + setSetImages((current) => current.map((item) => { + const update = updateMap.get(item.id); + return update ? { ...item, ...update } : item; + })); + }).catch(() => { + toast.error("套图后台上传失败,请检查网络后重试"); + }); }; const handleSetUpload = (event: ChangeEvent) => { @@ -3651,20 +3697,33 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { }); }; - const addProductImages = async (files: File[]) => { + const addProductImages = (files: File[]) => { const imageFiles = notifyRejectedImages(files); if (!imageFiles.length) return; - try { - const nextImages = await createUploadedImageItems(imageFiles, maxCloneProductImages - productImages.length, "product"); - setProductImages((current) => { - if (current.length >= maxCloneProductImages) return current; - return nextImages.length ? [...current, ...nextImages].slice(0, maxCloneProductImages) : current; - }); - setStatus("ready"); - setResults([]); - } catch (err) { - toast.error(err instanceof Error ? err.message : "商品图上传失败"); - } + const remainingSlots = maxCloneProductImages - productImages.length; + if (remainingSlots <= 0) return; + + const localItems = createLocalImageItems(imageFiles, remainingSlots, "product"); + setProductImages((current) => [...current, ...localItems].slice(0, maxCloneProductImages)); + setStatus("ready"); + setResults([]); + + Promise.all(localItems.map(async (item) => { + const uploaded = await uploadImageItem(item); + if (uploaded.src) { + URL.revokeObjectURL(item.src); + } + return { id: item.id, uploaded }; + })).then((results) => { + const updateMap = new Map(results.map((result) => [result.id, result.uploaded])); + setProductImages((current) => current.map((item) => { + const update = updateMap.get(item.id); + if (!update) return item; + return { ...item, ...update }; + })); + }).catch(() => { + toast.error("商品图后台上传失败,请检查网络后重试"); + }); }; const handleProductUpload = (event: ChangeEvent) => { @@ -3718,22 +3777,29 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { }); }; - const addCloneReferenceImages = async (files: File[]) => { + const addCloneReferenceImages = (files: File[]) => { const imageFiles = notifyRejectedImages(files); if (!imageFiles.length) return; const remainingSlots = maxCloneReferenceImages - cloneReferenceImages.length; if (remainingSlots <= 0) return; - try { - const nextImages = await createUploadedImageItems(imageFiles, remainingSlots, "reference"); - if (!nextImages.length) return; - setCloneReferenceImages((current) => { - if (current.length >= maxCloneReferenceImages) return current; - return nextImages.length ? [...current, ...nextImages].slice(0, maxCloneReferenceImages) : current; - }); - hydrateCloneReferenceImageMeta(nextImages); - } catch (err) { - toast.error(err instanceof Error ? err.message : "参考图上传失败"); - } + + const localItems = createLocalImageItems(imageFiles, remainingSlots, "reference"); + setCloneReferenceImages((current) => [...current, ...localItems].slice(0, maxCloneReferenceImages)); + hydrateCloneReferenceImageMeta(localItems); + + Promise.all(localItems.map(async (item) => { + const uploaded = await uploadImageItem(item); + if (uploaded.src) URL.revokeObjectURL(item.src); + return { id: item.id, uploaded }; + })).then((results) => { + const updateMap = new Map(results.map((result) => [result.id, result.uploaded])); + setCloneReferenceImages((current) => current.map((item) => { + const update = updateMap.get(item.id); + return update ? { ...item, ...update } : item; + })); + }).catch(() => { + toast.error("参考图后台上传失败,请检查网络后重试"); + }); }; const removeCloneReferenceImage = (imageId: string) => { @@ -4189,23 +4255,38 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { }; }, [openCloneModelSelect]); + const addGarmentImages = (files: File[]) => { + const uploadedFiles = notifyRejectedImages(files); + if (!uploadedFiles.length) return; + const remainingSlots = 5 - garmentImages.length; + if (remainingSlots <= 0) return; + + const localItems = createLocalImageItems(uploadedFiles, remainingSlots, "garment"); + setGarmentImages((current) => [...current, ...localItems].slice(0, 5)); + setTryOnStatus("ready"); + + Promise.all(localItems.map(async (item) => { + const uploaded = await uploadImageItem(item); + if (uploaded.src) URL.revokeObjectURL(item.src); + return { id: item.id, uploaded }; + })).then((results) => { + const updateMap = new Map(results.map((result) => [result.id, result.uploaded])); + setGarmentImages((current) => current.map((item) => { + const update = updateMap.get(item.id); + return update ? { ...item, ...update } : item; + })); + }).catch(() => { + toast.error("服饰图后台上传失败,请检查网络后重试"); + }); + }; + const handleGarmentUpload = (event: ChangeEvent) => { const files = event.target.files; - if (!files?.length) return; - const uploadedFiles = notifyRejectedImages(Array.from(files)); - if (!uploadedFiles.length) { + if (!files?.length) { event.target.value = ""; return; } - void (async () => { - try { - const nextImages = await createUploadedImageItems(uploadedFiles, 5 - garmentImages.length, "garment"); - setGarmentImages((current) => [...current, ...nextImages].slice(0, 5)); - setTryOnStatus("ready"); - } catch (err) { - toast.error(err instanceof Error ? err.message : "服饰图上传失败"); - } - })(); + addGarmentImages(Array.from(files)); event.target.value = ""; }; @@ -4241,20 +4322,30 @@ function ProductClonePage(_props: ProductClonePageProps = {}) { reader.readAsDataURL(blob); }); - const addDetailImages = async (files: File[]) => { + const addDetailImages = (files: File[]) => { const uploadedFiles = notifyRejectedImages(files); if (!uploadedFiles.length) return; - try { - const nextImages = await createUploadedImageItems(uploadedFiles, 3 - detailProductImages.length, "detail"); - setDetailProductImages((current) => { - if (current.length >= 3) return current; - return nextImages.length ? [...current, ...nextImages].slice(0, 3) : current; - }); - setDetailStatus("ready"); - setDetailResultUrl(null); - } catch (err) { - toast.error(err instanceof Error ? err.message : "详情图上传失败"); - } + const remainingSlots = 3 - detailProductImages.length; + if (remainingSlots <= 0) return; + + const localItems = createLocalImageItems(uploadedFiles, remainingSlots, "detail"); + setDetailProductImages((current) => [...current, ...localItems].slice(0, 3)); + setDetailStatus("ready"); + setDetailResultUrl(null); + + Promise.all(localItems.map(async (item) => { + const uploaded = await uploadImageItem(item); + if (uploaded.src) URL.revokeObjectURL(item.src); + return { id: item.id, uploaded }; + })).then((results) => { + const updateMap = new Map(results.map((result) => [result.id, result.uploaded])); + setDetailProductImages((current) => current.map((item) => { + const update = updateMap.get(item.id); + return update ? { ...item, ...update } : item; + })); + }).catch(() => { + toast.error("详情图后台上传失败,请检查网络后重试"); + }); }; const uploadCloneImages = async (images: CloneImageItem[]): Promise => {