feat: 重构电商指令栏布局,模式标签外置、精简结果标签、优化生成记录交互

本次修改对电商图片工作台的指令栏(composer)进行了全面重构,主要包含以下变更:

一、指令栏布局重构(EcommercePage.tsx):
- 新增生成模式标签页(ecom-command-mode-tabs),5种模式(套图/详情图/模特图/视频/爆款图)以标签形式外置于输入区上方,每种模式配有独立图标和配色
- 设置行(平台/语种/比例/设置)移入输入区内部,采用圆角胶囊按钮排列
- 上传按钮从输入区移到底部工具栏,改为"上传素材"紧凑样式
- 精简生成结果画布:移除所有文字标签(套图/详情图/模特图/爆款图 标签、原图素材标签、结果卡片标签),让图片成为绝对视觉焦点
- 灵感行"AI团队"更名为"作品记录",更新描述文案为"沉淀最近生成的高转化素材,随时回看与复用"

二、样式系统升级(ecommerce-standalone.css):
- 新增模式标签页完整样式:5列等宽网格、磨砂玻璃背板、各模式独立主题色
  · 套图 set:翠绿 #0f8f72
  · 详情图 detail:紫色 #7a5af8
  · 模特图 model:蓝色 #1073cc
  · 视频 video:暖橙 #cc6b14
  · 爆款图 hot:玫红 #c04468
- hover/active 状态带径向光晕和上浮微动效(translateY(-1px))
- 隐藏生成结果中的所有文字标签(display:none),减少视觉噪音
- 修复历史记录删除按钮定位:改为绝对居中定位,不受网格布局影响
- 输入区改为单列布局,增大最小高度(214-286px),增加内边距

变更文件:
- src/features/ecommerce/EcommercePage.tsx (+87/-51)
- src/styles/ecommerce-standalone.css (+456)
This commit is contained in:
2026-06-12 18:41:31 +08:00
parent 9ae5e1f493
commit 4f6e32fb10
2 changed files with 492 additions and 51 deletions
+36 -51
View File
@@ -106,8 +106,8 @@ const ecommerceInspirationAssets = ossAssets.ecommerce.inspiration;
const ecommerceInspirationRows = [
{
title: "AI团队",
desc: "不止作图,更懂转化。",
title: "作品记录",
desc: "沉淀最近生成的高转化素材,随时回看与复用。",
variant: "team",
cards: [
{ title: "指定ASIN,优化listing", meta: "竞品拆解 · 卖点重排 · 图文建议", mediaUrl: ecommerceInspirationAssets.asinListing, mediaType: "image" },
@@ -4487,14 +4487,12 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
onClick={() => openProductSetPreview(setPreviewCards[0] ?? productSetPreviewCards[0])}
>
<img src={setImages[0]?.src ?? (setPreviewCards[0]?.src ?? productSetPreviewCards[0].src)} alt="商品原图" />
<span></span>
</button>
<div className="product-set-flow-arrow" aria-hidden="true" />
<div className="product-set-card-grid result-reveal">
{setPreviewCards.map((card) => (
<button key={card.id} type="button" onClick={() => openProductSetPreview(card)}>
<img src={card.src} alt={card.label} />
<span>{card.label}</span>
</button>
))}
</div>
@@ -4985,9 +4983,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
e.currentTarget.releasePointerCapture(e.pointerId);
}
}}
>
<span className="clone-ai-node-label">{node.mode === "set" ? "套图" : node.mode === "detail" ? "详情图" : node.mode === "model" ? "模特图" : node.mode === "hot" ? "爆款图" : node.mode}</span>
</div>
/>
{node.sourceImage ? (
<button
type="button"
@@ -4995,7 +4991,6 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
onClick={() => openProductSetPreview({ src: node.sourceImage!, label: "原图素材" })}
>
<img src={node.sourceImage} alt="原图素材" />
<span></span>
</button>
) : null}
<div className="clone-ai-flow-arrow" aria-hidden="true" />
@@ -5003,7 +4998,6 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
{node.results.map((card) => (
<button key={card.id} type="button" style={{ aspectRatio: parseRatioToAspectCss(ratio) }} onClick={() => openProductSetPreview(card, { nodeId: node.id, removable: true })}>
<img src={card.src} alt={card.label} />
<span>{card.label}</span>
</button>
))}
</div>
@@ -5078,27 +5072,20 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
onChange={handleSmartCutoutUpload}
aria-label="上传智能抠图素材"
/>
<div className="ecom-command-mode-tabs" aria-label="生成模式">
{cloneOutputOptions.map((option) => (
<button
key={option.key}
type="button"
className={cloneOutput === option.key ? "is-active" : ""}
onClick={() => handleCloneOutputChange(option.key)}
>
<span className={`ecom-command-mode-icon ecom-command-mode-icon--${option.key}`} aria-hidden="true">{option.icon}</span>
<strong>{option.label}</strong>
</button>
))}
</div>
<div className="clone-ai-input-wrapper ecom-command-composer">
<button
type="button"
className={`ecom-command-reference${productImages.length ? " has-images" : ""}${isProductUploadDragging ? " is-dragging" : ""}`}
onClick={() => productInputRef.current?.click()}
onDragEnter={(event) => {
event.preventDefault();
setIsProductUploadDragging(true);
}}
onDragOver={(event) => event.preventDefault()}
onDragLeave={() => setIsProductUploadDragging(false)}
onDrop={(event) => {
event.preventDefault();
setIsProductUploadDragging(false);
const files = Array.from(event.dataTransfer.files);
if (files.length) addComposerAssets(files);
}}
>
<span aria-hidden="true"><CloudUploadOutlined /></span>
<strong></strong>
</button>
{productImages.length ? (
<div className="ecom-command-asset-popover" aria-label="已上传素材">
{productImages.map((image) => (
@@ -5115,6 +5102,24 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<button type="button" className="ecom-command-asset-add" onClick={() => productInputRef.current?.click()} aria-label="继续上传">+</button>
</div>
) : null}
<div className="ecom-command-option-row ecom-command-option-row--settings">
<button type="button" className={composerMenu === "platform" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("platform", event)}>
<span className="ecom-command-option-icon" aria-hidden="true"><GlobalOutlined /></span>
<span></span>{platform}
</button>
<button type="button" className={composerMenu === "language" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("language", event)}>
<span className="ecom-command-option-icon" aria-hidden="true"><FileImageOutlined /></span>
<span></span>{language}
</button>
<button type="button" className={composerMenu === "ratio" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("ratio", event)}>
<span className="ecom-command-option-icon" aria-hidden="true"><TableOutlined /></span>
<span></span>{formatRatioDisplayValue(ratio)}
</button>
<button type="button" className={composerMenu === "settings" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("settings", event)}>
<span className="ecom-command-option-icon" aria-hidden="true"><SettingOutlined /></span>
<span></span>{composerSettingLabel}
</button>
</div>
<textarea
ref={requirementTextareaRef}
value={requirement}
@@ -5135,10 +5140,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<ImageMentionMenu images={ecommerceMentionImages} query={requirementImageMentionQuery} onSelect={insertRequirementImageMention} />
) : null}
<div className="ecom-command-toolbar" aria-label="生成设置">
<div className="ecom-command-option-row">
<div className="ecom-command-composer-actions">
<button
type="button"
className={`ecom-command-reference ecom-command-reference--inline${productImages.length ? " has-images" : ""}${isProductUploadDragging ? " is-dragging" : ""}`}
className={`ecom-command-reference ecom-command-reference--bottom${productImages.length ? " has-images" : ""}${isProductUploadDragging ? " is-dragging" : ""}`}
onClick={() => productInputRef.current?.click()}
onDragEnter={(event) => {
event.preventDefault();
@@ -5156,26 +5161,6 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<span aria-hidden="true"><PaperClipOutlined /></span>
<strong></strong>
</button>
<button type="button" className={composerMenu === "mode" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("mode", event)}>
<span className="ecom-command-option-icon" aria-hidden="true"><AppstoreOutlined /></span>
{selectedCloneOutput.label}<span></span>
</button>
<button type="button" className={composerMenu === "platform" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("platform", event)}>
<span className="ecom-command-option-icon" aria-hidden="true"><GlobalOutlined /></span>
<span></span>{platform}
</button>
<button type="button" className={composerMenu === "language" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("language", event)}>
<span className="ecom-command-option-icon" aria-hidden="true"><FileImageOutlined /></span>
<span></span>{language}
</button>
<button type="button" className={composerMenu === "ratio" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("ratio", event)}>
<span className="ecom-command-option-icon" aria-hidden="true"><TableOutlined /></span>
<span></span>{formatRatioDisplayValue(ratio)}
</button>
<button type="button" className={composerMenu === "settings" ? "is-active" : ""} onClick={(event) => toggleComposerMenu("settings", event)}>
<span className="ecom-command-option-icon" aria-hidden="true"><SettingOutlined /></span>
<span></span>{composerSettingLabel}
</button>
</div>
<div className="ecom-command-submit-row">
<button type="button" className="clone-ai-send-button ecom-command-send" disabled={commandGenerateDisabled} onClick={handleCommandGenerate} aria-label={clonePrimaryLabel}>