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 = () => { const openHotVideoPage = () => {
clearSmartCutoutTransition(); clearSmartCutoutTransition();
setActiveQuickTool("hot-video"); clearQuickPageTransition();
setComposerMenu(null); runQuickPageTransition(
setIsCloneSettingsCollapsed(true); { title: "正在进入广告视频", subtitle: "AI智能策划 · 一键生成电商短视频" },
setIsCommandHistoryCollapsed(true); () => {
setActiveQuickTool("hot-video");
setComposerMenu(null);
setIsCloneSettingsCollapsed(true);
setIsCommandHistoryCollapsed(true);
setPreviewZoom(1);
setRatio("9:16");
resetQuickSetSelectState();
},
);
}; };
const closeHotVideoPage = () => { const closeHotVideoPage = () => {
@@ -4949,16 +4958,13 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
className="ecom-command-hidden-file" className="ecom-command-hidden-file"
onChange={handleImageWorkbenchUpload} 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"> <aside className="ecom-image-workbench-side">
<div className="ecom-image-workbench-heading"> <header className="ecom-quick-set-panel-head ecom-image-workbench-panel-head">
<span></span> <strong className="ecom-quick-set-page-title"></strong>
<strong></strong> <button type="button" className="ecom-quick-set-back" onClick={closeImageWorkbenchPage}></button>
<p> AI </p> <button type="button" className="ecom-quick-set-back" onClick={closeImageWorkbenchPage}></button>
</div> </header>
<p className="ecom-image-workbench-intro"> AI </p>
<section className="ecom-image-workbench-panel"> <section className="ecom-image-workbench-panel">
<header> <header>
@@ -5157,16 +5163,13 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
className="ecom-command-hidden-file" className="ecom-command-hidden-file"
onChange={handleWatermarkUpload} 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"> <aside className="ecom-watermark-side">
<div className="ecom-watermark-heading"> <header className="ecom-quick-set-panel-head ecom-watermark-panel-head">
<span>AI </span> <strong className="ecom-quick-set-page-title">/</strong>
<strong>/</strong> <button type="button" className="ecom-quick-set-back" onClick={closeWatermarkRemovalPage}></button>
<p></p> <button type="button" className="ecom-quick-set-back" onClick={closeWatermarkRemovalPage}></button>
</div> </header>
<p className="ecom-watermark-intro"></p>
<section className="ecom-watermark-panel"> <section className="ecom-watermark-panel">
<header> <header>
<strong></strong> <strong></strong>
@@ -5347,46 +5350,35 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
); );
const hotVideoPreview = ( const hotVideoPreview = (
<main className="ecom-hot-video-page" aria-label="广告视频"> <main className="ecom-quick-set-page ecom-hot-video-page" aria-label="广告视频">
<nav className="ecom-hot-video-nav"> <div className="ecom-quick-set-body">
<button type="button" className="ecom-hot-video-back" onClick={closeHotVideoPage}> <aside className="ecom-quick-set-panel" aria-label="广告视频设置">
<header className="ecom-quick-set-panel-head">
</button> <strong className="ecom-quick-set-page-title">广</strong>
<div className="ecom-hot-video-nav-title"> <button type="button" className="ecom-quick-set-back" onClick={closeHotVideoPage}></button>
<h1>广</h1> <button type="button" className="ecom-quick-set-back" onClick={closeHotVideoPage}></button>
<span>AI智能策划 · </span> </header>
</div> <section>
<div className="ecom-hot-video-nav-meta"> <strong><CloudUploadOutlined /> </strong>
<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>
<div <div
role="button" role="button"
tabIndex={0} 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()} onClick={() => quickProductInputRef.current?.click()}
onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") quickProductInputRef.current?.click(); }} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") quickProductInputRef.current?.click(); }}
onDragOver={(event) => { event.preventDefault(); setIsProductUploadDragging(true); }} onDragOver={(event) => { event.preventDefault(); setIsProductUploadDragging(true); }}
onDragLeave={(event) => { event.preventDefault(); setIsProductUploadDragging(false); }} onDragLeave={(event) => { event.preventDefault(); setIsProductUploadDragging(false); }}
onDrop={handleProductDrop} onDrop={handleProductDrop}
> >
<CloudUploadOutlined /> <FileImageOutlined />
<span></span> <span></span>
<em> JPG / PNG / WebP</em> <em> JPG / PNG / WebP</em>
<b>+ </b>
{productImages.length > 0 ? ( {productImages.length > 0 ? (
<div className="ecom-hot-video-upload-thumbs"> <div className="ecom-quick-upload-thumbs">
{productImages.map((img) => ( {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} /> <img src={img.src} alt={img.name} />
<button
type="button"
aria-label="删除"
onClick={(event) => { event.stopPropagation(); removeProductImage(img.id); }}
>×</button>
</figure> </figure>
))} ))}
</div> </div>
@@ -5401,8 +5393,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
onChange={handleProductUpload} onChange={handleProductUpload}
/> />
</section> </section>
<section>
<section className="ecom-hot-video-section">
<strong></strong> <strong></strong>
<textarea <textarea
className="ecom-hot-video-textarea" className="ecom-hot-video-textarea"
@@ -5413,41 +5404,16 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
maxLength={500} maxLength={500}
/> />
</section> </section>
<section className="ecom-quick-set-basic-section">
<section className="ecom-hot-video-section"> <span className="ecom-quick-set-label"></span>
<strong></strong> <div className="ecom-quick-set-select-anchor">
<div className="ecom-hot-video-options"> <div className="ecom-quick-set-selects">
{platformOptions.map((option) => ( <button type="button"><span></span><strong>{platform}</strong></button>
<button <button type="button"><span></span><strong>{ratio}</strong></button>
key={option} </div>
type="button"
className={platform === option ? "is-active" : ""}
onClick={() => setPlatform(option)}
>
{renderPlatformLogo(option)}
<span>{option}</span>
</button>
))}
</div> </div>
</section> </section>
<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">
<strong></strong> <strong></strong>
<div className="ecom-hot-video-options"> <div className="ecom-hot-video-options">
{cloneVideoQualityOptions.map((option) => ( {cloneVideoQualityOptions.map((option) => (
@@ -5463,8 +5429,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
))} ))}
</div> </div>
</section> </section>
<section>
<section className="ecom-hot-video-section">
<strong> · {cloneVideoDuration}</strong> <strong> · {cloneVideoDuration}</strong>
<input <input
type="range" type="range"
@@ -5481,24 +5446,23 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
<span>{cloneVideoDurationMax}</span> <span>{cloneVideoDurationMax}</span>
</div> </div>
</section> </section>
<button <button
type="button" type="button"
className="ecom-hot-video-start" className="ecom-quick-set-primary"
disabled={!productImages.length && !requirement.trim()} disabled={!productImages.length && !requirement.trim()}
onClick={() => setVideoPlanTrigger((prev) => prev + 1)} onClick={() => setVideoPlanTrigger((prev) => prev + 1)}
> >
</button> </button>
</aside> </aside>
<section className="ecom-hot-video-workspace"> <section className="ecom-quick-set-stage">
<EcommerceVideoWorkspace <EcommerceVideoWorkspace
isAuthenticated={isAuthenticated} isAuthenticated={isAuthenticated}
productImageDataUrls={ecommerceVideoImageDataUrls} productImageDataUrls={ecommerceVideoImageDataUrls}
productImageFiles={ecommerceVideoImageFiles} productImageFiles={ecommerceVideoImageFiles}
requirement={requirement} requirement={requirement}
platform={platform} 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} durationSeconds={cloneVideoDuration}
resolution={cloneVideoQuality === "standard" ? "720P" : "1080P"} resolution={cloneVideoQuality === "standard" ? "720P" : "1080P"}
onRequestLogin={() => (isAuthenticated ? undefined : requestLogin())} onRequestLogin={() => (isAuthenticated ? undefined : requestLogin())}
@@ -5606,8 +5570,11 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
</div> </div>
</section> </section>
<button type="button" className="ecom-quick-set-primary" onClick={handleGenerate} disabled={!canGenerate}> <button type="button" className="ecom-quick-set-primary" onClick={handleGenerate} disabled={!canGenerate}>
{status === "generating" ? <LoadingOutlined /> : "✦"} {status === "generating" ? "正在生成..." : "开始生成"}
</button> </button>
{status === "generating" ? (
<button type="button" className="ecom-quick-set-primary ecom-quick-set-primary--cancel" onClick={handleCancelGenerate}></button>
) : null}
</aside> </aside>
<section className="ecom-quick-set-stage"> <section className="ecom-quick-set-stage">
<header className="ecom-quick-set-preview-head"> <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}> <button type="button" className="ecom-quick-set-primary" onClick={handleDetailGenerate} disabled={!canGenerateDetail}>
{detailStatus === "generating" ? <LoadingOutlined /> : "✦"} {detailStatus === "generating" ? <LoadingOutlined /> : "✦"}
</button> </button>
{detailStatus === "generating" ? (
<button type="button" className="ecom-quick-set-primary ecom-quick-set-primary--cancel" onClick={handleCancelGenerate}></button>
) : null}
</aside> </aside>
<section className="ecom-quick-set-stage"> <section className="ecom-quick-set-stage">
<header className="ecom-quick-set-preview-head"> <header className="ecom-quick-set-preview-head">
@@ -5958,12 +5928,13 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
? imageWorkbenchPreview ? imageWorkbenchPreview
: isSmartCutoutTool : isSmartCutoutTool
? smartCutoutPreview ? smartCutoutPreview
: isQuickSetTool : isQuickSetTool || isQuickDetailTool || isHotVideoTool
? quickProductSetPreview ? (
: isQuickDetailTool <div className="ecom-quick-page-wrap">
? quickDetailPreview {quickPageSidebar}
: isHotVideoTool {isQuickSetTool ? quickProductSetPreview : isQuickDetailTool ? quickDetailPreview : hotVideoPreview}
? hotVideoPreview </div>
)
: cloneOutput === "video-outfit" && results.length > 0 && results[0].type === "video" : 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" }}> <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> </main>
) : activePreview} ) : 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> </div>
<aside className="ecom-command-history" aria-label="生成历史"> <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; 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 { .image-workbench-canvas {
display: flex; display: flex;
align-items: center; align-items: center;