Polish ecommerce tool page layouts

This commit is contained in:
Codex
2026-06-11 23:07:31 +08:00
parent 2fdb82d499
commit 5ddfd37f4d
3 changed files with 738 additions and 373 deletions
+78 -99
View File
@@ -2137,10 +2137,19 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const openHotVideoPage = () => {
clearSmartCutoutTransition();
setActiveQuickTool("hot-video");
setComposerMenu(null);
setIsCloneSettingsCollapsed(true);
setIsCommandHistoryCollapsed(true);
clearQuickPageTransition();
runQuickPageTransition(
{ title: "正在进入广告视频", subtitle: "AI智能策划 · 一键生成电商短视频" },
() => {
setActiveQuickTool("hot-video");
setComposerMenu(null);
setIsCloneSettingsCollapsed(true);
setIsCommandHistoryCollapsed(true);
setPreviewZoom(1);
setRatio("9:16");
resetQuickSetSelectState();
},
);
};
const closeHotVideoPage = () => {
@@ -4949,16 +4958,13 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
className="ecom-command-hidden-file"
onChange={handleImageWorkbenchUpload}
/>
<nav className="ecom-image-workbench-nav" aria-label="图片修改导航">
<button type="button" onClick={closeImageWorkbenchPage}></button>
<button type="button" onClick={closeImageWorkbenchPage}></button>
</nav>
<aside className="ecom-image-workbench-side">
<div className="ecom-image-workbench-heading">
<span></span>
<strong></strong>
<p> AI </p>
</div>
<header className="ecom-quick-set-panel-head ecom-image-workbench-panel-head">
<strong className="ecom-quick-set-page-title"></strong>
<button type="button" className="ecom-quick-set-back" onClick={closeImageWorkbenchPage}></button>
<button type="button" className="ecom-quick-set-back" onClick={closeImageWorkbenchPage}></button>
</header>
<p className="ecom-image-workbench-intro"> AI </p>
<section className="ecom-image-workbench-panel">
<header>
@@ -5157,16 +5163,13 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
className="ecom-command-hidden-file"
onChange={handleWatermarkUpload}
/>
<nav className="ecom-watermark-nav" aria-label="去水印导航">
<button type="button" onClick={closeWatermarkRemovalPage}></button>
<button type="button" onClick={closeWatermarkRemovalPage}></button>
</nav>
<aside className="ecom-watermark-side">
<div className="ecom-watermark-heading">
<span>AI </span>
<strong>/</strong>
<p></p>
</div>
<header className="ecom-quick-set-panel-head ecom-watermark-panel-head">
<strong className="ecom-quick-set-page-title">/</strong>
<button type="button" className="ecom-quick-set-back" onClick={closeWatermarkRemovalPage}></button>
<button type="button" className="ecom-quick-set-back" onClick={closeWatermarkRemovalPage}></button>
</header>
<p className="ecom-watermark-intro"></p>
<section className="ecom-watermark-panel">
<header>
<strong></strong>
@@ -5347,46 +5350,35 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
);
const hotVideoPreview = (
<main className="ecom-hot-video-page" aria-label="广告视频">
<nav className="ecom-hot-video-nav">
<button type="button" className="ecom-hot-video-back" onClick={closeHotVideoPage}>
</button>
<div className="ecom-hot-video-nav-title">
<h1>广</h1>
<span>AI智能策划 · </span>
</div>
<div className="ecom-hot-video-nav-meta">
<span>{platform} / {formatRatioDisplayValue(ratio)} / {cloneVideoDuration} / {cloneVideoQuality === "standard" ? "720P" : "1080P"}</span>
</div>
</nav>
<div className="ecom-hot-video-body">
<aside className="ecom-hot-video-settings" aria-label="视频设置">
<section className="ecom-hot-video-section">
<strong></strong>
<main className="ecom-quick-set-page ecom-hot-video-page" aria-label="广告视频">
<div className="ecom-quick-set-body">
<aside className="ecom-quick-set-panel" aria-label="广告视频设置">
<header className="ecom-quick-set-panel-head">
<strong className="ecom-quick-set-page-title">广</strong>
<button type="button" className="ecom-quick-set-back" onClick={closeHotVideoPage}></button>
<button type="button" className="ecom-quick-set-back" onClick={closeHotVideoPage}></button>
</header>
<section>
<strong><CloudUploadOutlined /> </strong>
<div
role="button"
tabIndex={0}
className={`ecom-hot-video-upload${productImages.length ? " has-images" : ""}${isProductUploadDragging ? " is-dragging" : ""}`}
className={`ecom-quick-set-upload${productImages.length ? " has-images" : ""}`}
onClick={() => quickProductInputRef.current?.click()}
onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") quickProductInputRef.current?.click(); }}
onDragOver={(event) => { event.preventDefault(); setIsProductUploadDragging(true); }}
onDragLeave={(event) => { event.preventDefault(); setIsProductUploadDragging(false); }}
onDrop={handleProductDrop}
>
<CloudUploadOutlined />
<span></span>
<em> JPG / PNG / WebP</em>
<FileImageOutlined />
<span></span>
<em> JPG / PNG / WebP</em>
<b>+ </b>
{productImages.length > 0 ? (
<div className="ecom-hot-video-upload-thumbs">
<div className="ecom-quick-upload-thumbs">
{productImages.map((img) => (
<figure key={img.id}>
<figure key={img.id} className="ecom-command-asset-thumb ecom-quick-upload-thumb">
<img src={img.src} alt={img.name} />
<button
type="button"
aria-label="删除"
onClick={(event) => { event.stopPropagation(); removeProductImage(img.id); }}
>×</button>
</figure>
))}
</div>
@@ -5401,8 +5393,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
onChange={handleProductUpload}
/>
</section>
<section className="ecom-hot-video-section">
<section>
<strong></strong>
<textarea
className="ecom-hot-video-textarea"
@@ -5413,41 +5404,16 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
maxLength={500}
/>
</section>
<section className="ecom-hot-video-section">
<strong></strong>
<div className="ecom-hot-video-options">
{platformOptions.map((option) => (
<button
key={option}
type="button"
className={platform === option ? "is-active" : ""}
onClick={() => setPlatform(option)}
>
{renderPlatformLogo(option)}
<span>{option}</span>
</button>
))}
<section className="ecom-quick-set-basic-section">
<span className="ecom-quick-set-label"></span>
<div className="ecom-quick-set-select-anchor">
<div className="ecom-quick-set-selects">
<button type="button"><span></span><strong>{platform}</strong></button>
<button type="button"><span></span><strong>{ratio}</strong></button>
</div>
</div>
</section>
<section className="ecom-hot-video-section">
<strong></strong>
<div className="ecom-hot-video-options ecom-hot-video-options--ratio">
{cloneRatioOptions.map((option) => (
<button
key={option}
type="button"
className={ratio === option ? "is-active" : ""}
onClick={() => setRatio(option)}
>
{formatRatioDisplayValue(option)}
</button>
))}
</div>
</section>
<section className="ecom-hot-video-section">
<section>
<strong></strong>
<div className="ecom-hot-video-options">
{cloneVideoQualityOptions.map((option) => (
@@ -5463,8 +5429,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
))}
</div>
</section>
<section className="ecom-hot-video-section">
<section>
<strong> · {cloneVideoDuration}</strong>
<input
type="range"
@@ -5481,24 +5446,23 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<span>{cloneVideoDurationMax}</span>
</div>
</section>
<button
type="button"
className="ecom-hot-video-start"
className="ecom-quick-set-primary"
disabled={!productImages.length && !requirement.trim()}
onClick={() => setVideoPlanTrigger((prev) => prev + 1)}
>
</button>
</aside>
<section className="ecom-hot-video-workspace">
<section className="ecom-quick-set-stage">
<EcommerceVideoWorkspace
isAuthenticated={isAuthenticated}
productImageDataUrls={ecommerceVideoImageDataUrls}
productImageFiles={ecommerceVideoImageFiles}
requirement={requirement}
platform={platform}
aspectRatio={ratio.includes("916") || ratio.includes("9:16") ? "9:16" : ratio.includes("169") || ratio.includes("16:9") ? "16:9" : ratio.includes("34") || ratio.includes("3:4") ? "3:4" : "9:16"}
aspectRatio={ratio.includes("9:16") ? "9:16" : ratio.includes("16:9") ? "16:9" : ratio.includes("3:4") ? "3:4" : "9:16"}
durationSeconds={cloneVideoDuration}
resolution={cloneVideoQuality === "standard" ? "720P" : "1080P"}
onRequestLogin={() => (isAuthenticated ? undefined : requestLogin())}
@@ -5606,8 +5570,11 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
</div>
</section>
<button type="button" className="ecom-quick-set-primary" onClick={handleGenerate} disabled={!canGenerate}>
{status === "generating" ? <LoadingOutlined /> : "✦"} {status === "generating" ? "正在生成..." : "开始生成"}
</button>
{status === "generating" ? (
<button type="button" className="ecom-quick-set-primary ecom-quick-set-primary--cancel" onClick={handleCancelGenerate}></button>
) : null}
</aside>
<section className="ecom-quick-set-stage">
<header className="ecom-quick-set-preview-head">
@@ -5777,6 +5744,9 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<button type="button" className="ecom-quick-set-primary" onClick={handleDetailGenerate} disabled={!canGenerateDetail}>
{detailStatus === "generating" ? <LoadingOutlined /> : "✦"}
</button>
{detailStatus === "generating" ? (
<button type="button" className="ecom-quick-set-primary ecom-quick-set-primary--cancel" onClick={handleCancelGenerate}></button>
) : null}
</aside>
<section className="ecom-quick-set-stage">
<header className="ecom-quick-set-preview-head">
@@ -5958,12 +5928,13 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
? imageWorkbenchPreview
: isSmartCutoutTool
? smartCutoutPreview
: isQuickSetTool
? quickProductSetPreview
: isQuickDetailTool
? quickDetailPreview
: isHotVideoTool
? hotVideoPreview
: isQuickSetTool || isQuickDetailTool || isHotVideoTool
? (
<div className="ecom-quick-page-wrap">
{quickPageSidebar}
{isQuickSetTool ? quickProductSetPreview : isQuickDetailTool ? quickDetailPreview : hotVideoPreview}
</div>
)
: cloneOutput === "video-outfit" && results.length > 0 && results[0].type === "video"
? (
<main className="product-clone-preview product-clone-preview--video-outfit" style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
@@ -6032,6 +6003,14 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
/>
</main>
) : activePreview}
{quickPageTransition ? (
<div className="ecom-quick-page-transition" role="status" aria-live="polite">
<span aria-hidden="true" />
<strong>{quickPageTransition.title}</strong>
<em>{quickPageTransition.subtitle}</em>
</div>
) : null}
</div>
<aside className="ecom-command-history" aria-label="生成历史">
File diff suppressed because it is too large Load Diff
+38
View File
@@ -579,6 +579,44 @@ textarea.image-workbench-prompt {
background: rgba(var(--accent-rgb), 0.18) !important;
}
.image-workbench-page--image-tool .image-workbench-panel--left,
.watermark-removal-page .image-workbench-panel--left {
margin: 12px 0 12px 12px;
border: 1px solid rgba(var(--accent-rgb), 0.14);
border-radius: 12px;
background:
linear-gradient(180deg, rgba(var(--accent-rgb), 0.06), transparent 180px),
var(--bg-elevated);
box-shadow: 0 14px 34px rgba(15, 23, 42, 0.08);
}
.image-workbench-page--image-tool .image-workbench-panel--left .image-workbench-control-card,
.watermark-removal-page .image-workbench-panel--left .image-workbench-control-card {
border-color: rgba(var(--accent-rgb), 0.14);
border-radius: 10px;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.035), transparent),
var(--bg-elevated);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04);
}
.image-workbench-page--image-tool .image-workbench-panel--left .image-workbench-upload,
.watermark-removal-page .image-workbench-panel--left .image-workbench-upload,
.image-workbench-page--image-tool .image-workbench-panel--left :where(input, textarea, select),
.watermark-removal-page .image-workbench-panel--left :where(input, textarea, select) {
border-color: rgba(var(--accent-rgb), 0.18);
background:
linear-gradient(180deg, rgba(var(--accent-rgb), 0.035), transparent),
var(--bg-inset);
}
.image-workbench-page--image-tool .image-workbench-panel--left .image-workbench-actions,
.watermark-removal-page .image-workbench-panel--left .image-workbench-actions {
margin-top: 2px;
padding-top: 12px;
border-top: 1px solid rgba(var(--accent-rgb), 0.12);
}
.image-workbench-canvas {
display: flex;
align-items: center;