feat: 电商页面 KeepAlive 保活机制,切换页面不再丢失生成状态
通过 display:none 模式实现轻量 KeepAlive,电商页面首次访问后保持挂载, 切换到其他页面再切回时所有右侧面板状态(上传图片、生成进度、结果)完整保留。 同时清理项目中的临时文件和本地冗余图片。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -2064,47 +2064,168 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
</span>
|
||||
</header>
|
||||
|
||||
{status === "done" ? (
|
||||
<section className="clone-ai-preview-showcase" aria-label="生成结果">
|
||||
<button type="button" className="clone-ai-main-result" onClick={() => openProductSetPreview(cloneOutput === "set" ? clonePreviewCards[0] : results[0])}>
|
||||
<img src={productImages[0]?.src ?? (cloneOutput === "set" ? clonePreviewCards[0].src : results[0]?.src ?? "")} alt="上传商品原图" />
|
||||
<span>原图素材</span>
|
||||
</button>
|
||||
<div className="clone-ai-flow-arrow" aria-hidden="true" />
|
||||
<div className="clone-ai-result-grid result-reveal">
|
||||
{cloneOutput === "set" ? (
|
||||
clonePreviewCards.map((card) => (
|
||||
<button key={card.id} type="button" onClick={() => openProductSetPreview(card)}>
|
||||
<img src={card.src} alt={card.label} />
|
||||
<span>{card.label}</span>
|
||||
</button>
|
||||
))
|
||||
) : results[0]?.src ? (
|
||||
<button type="button" onClick={() => openProductSetPreview(results[0])}>
|
||||
<img src={results[0].src} alt={selectedCloneOutput.label} />
|
||||
<span>{selectedCloneOutput.label}</span>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<section className="clone-ai-empty-state" aria-live="polite">
|
||||
{status === "generating" ? <LoadingOutlined /> : status === "failed" ? <FrownOutlined /> : <FileImageOutlined />}
|
||||
<strong>{status === "generating" ? "正在生成" : status === "failed" ? "生成失败" : "等待生成"}</strong>
|
||||
{status === "generating" ? <EcommerceProgressBar status="generating" label={`${selectedCloneOutput.label}生成`} /> : null}
|
||||
<span>
|
||||
{status === "generating"
|
||||
? `AI 正在为 ${platform} / ${market} 整理${selectedCloneOutput.label}。`
|
||||
: status === "failed"
|
||||
? "请检查网络后点击下方重试"
|
||||
: "上传商品原图并填写信息后,AI 将在这里展示生成结果。"}
|
||||
</span>
|
||||
{status === "failed" && lastFailedActionRef.current ? (
|
||||
<button type="button" className="clone-ai-retry-btn" onClick={lastFailedActionRef.current}>
|
||||
<ReloadOutlined /> 重试
|
||||
</button>
|
||||
{cloneOutput === "video" ? (
|
||||
<>
|
||||
<section className="clone-ai-flow-pipeline" aria-label="生成流程">
|
||||
{/* Source Node — 原图素材 */}
|
||||
<div className="clone-ai-flow-source">
|
||||
<div className="clone-ai-flow-node clone-ai-flow-node--source">
|
||||
{productImages[0]?.src ? (
|
||||
<img src={productImages[0].src} alt="商品原图" />
|
||||
) : (
|
||||
<div className="clone-ai-flow-node__placeholder">
|
||||
<FileImageOutlined />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="clone-ai-flow-node__label">附件原图</span>
|
||||
</div>
|
||||
|
||||
{/* Connector — 分支连接线 */}
|
||||
<div className="clone-ai-flow-connector" aria-hidden="true">
|
||||
<div className="clone-ai-flow-connector__trunk" />
|
||||
<div className="clone-ai-flow-connector__branches">
|
||||
<div className="clone-ai-flow-connector__branch" />
|
||||
<div className="clone-ai-flow-connector__branch" />
|
||||
<div className="clone-ai-flow-connector__branch" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Branches — 生成路径分支 */}
|
||||
{status === "done" ? (
|
||||
<div className="clone-ai-flow-branches">
|
||||
{results[0]?.src ? (
|
||||
<div className="clone-ai-flow-branch">
|
||||
<div className="clone-ai-flow-node clone-ai-flow-node--text">
|
||||
<div className="clone-ai-flow-node__text-content">
|
||||
<span className="clone-ai-flow-node__text-title">{selectedCloneOutput.label}</span>
|
||||
<span className="clone-ai-flow-node__text-desc">{requirement || "AI智能生成"}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="clone-ai-flow-arrow" aria-hidden="true" />
|
||||
<button
|
||||
type="button"
|
||||
className="clone-ai-flow-node clone-ai-flow-node--result"
|
||||
onClick={() => openProductSetPreview(results[0])}
|
||||
>
|
||||
<img src={results[0].src} alt={selectedCloneOutput.label} />
|
||||
<span className="clone-ai-flow-node__tag">{selectedCloneOutput.label}</span>
|
||||
</button>
|
||||
<div className="clone-ai-flow-arrow" aria-hidden="true" />
|
||||
<div className="clone-ai-flow-node clone-ai-flow-node--video">
|
||||
<img src={results[0].src} alt="分镜视频" />
|
||||
<span className="clone-ai-flow-node__tag clone-ai-flow-node__tag--accent">分镜视频</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<div className="clone-ai-flow-branches clone-ai-flow-branches--empty">
|
||||
{[1, 2, 3].map((branchIndex) => (
|
||||
<div
|
||||
key={branchIndex}
|
||||
className={`clone-ai-flow-branch${status === "generating" ? " is-generating" : ""}${status === "failed" ? " is-failed" : ""}`}
|
||||
>
|
||||
<div className="clone-ai-flow-node clone-ai-flow-node--text">
|
||||
<div className="clone-ai-flow-node__text-content">
|
||||
<span className="clone-ai-flow-node__text-title">分镜文本{branchIndex}</span>
|
||||
<span className="clone-ai-flow-node__text-desc">
|
||||
{status === "generating" ? "AI 解析中..." : "等待生成"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="clone-ai-flow-arrow" aria-hidden="true" />
|
||||
<div className="clone-ai-flow-node clone-ai-flow-node--result">
|
||||
<div className="clone-ai-flow-node__placeholder">
|
||||
{status === "generating" ? <LoadingOutlined /> : <FileImageOutlined />}
|
||||
</div>
|
||||
<span className="clone-ai-flow-node__tag">分镜图{branchIndex}</span>
|
||||
</div>
|
||||
<div className="clone-ai-flow-arrow" aria-hidden="true" />
|
||||
<div className="clone-ai-flow-node clone-ai-flow-node--video">
|
||||
<div className="clone-ai-flow-node__placeholder">
|
||||
{status === "generating" ? <LoadingOutlined /> : <FileImageOutlined />}
|
||||
</div>
|
||||
<span className="clone-ai-flow-node__tag">分镜视频{branchIndex}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Status Overlay — 生成状态覆盖层 */}
|
||||
{status !== "done" ? (
|
||||
<section className="clone-ai-flow-status" aria-live="polite">
|
||||
{status === "generating" ? (
|
||||
<>
|
||||
<LoadingOutlined style={{ fontSize: 28 }} />
|
||||
<strong>正在生成</strong>
|
||||
<EcommerceProgressBar status="generating" label={`${selectedCloneOutput.label}生成`} />
|
||||
<span>AI 正在为 {platform} / {market} 整理{selectedCloneOutput.label}。</span>
|
||||
</>
|
||||
) : status === "failed" ? (
|
||||
<>
|
||||
<FrownOutlined style={{ fontSize: 28 }} />
|
||||
<strong>生成失败</strong>
|
||||
<span>请检查网络后点击下方重试</span>
|
||||
{lastFailedActionRef.current ? (
|
||||
<button type="button" className="clone-ai-retry-btn" onClick={lastFailedActionRef.current}>
|
||||
<ReloadOutlined /> 重试
|
||||
</button>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<span>上传商品原图并填写信息后,AI 将在这里展示生成结果。</span>
|
||||
)}
|
||||
</section>
|
||||
) : null}
|
||||
</section>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{status === "done" ? (
|
||||
<section className="clone-ai-preview-showcase" aria-label="生成结果">
|
||||
<button type="button" className="clone-ai-main-result" onClick={() => openProductSetPreview(cloneOutput === "set" ? clonePreviewCards[0] : results[0])}>
|
||||
<img src={productImages[0]?.src ?? (cloneOutput === "set" ? clonePreviewCards[0].src : results[0]?.src ?? "")} alt="上传商品原图" />
|
||||
<span>原图素材</span>
|
||||
</button>
|
||||
<div className="clone-ai-flow-arrow" aria-hidden="true" />
|
||||
<div className="clone-ai-result-grid result-reveal">
|
||||
{cloneOutput === "set" ? (
|
||||
clonePreviewCards.map((card) => (
|
||||
<button key={card.id} type="button" onClick={() => openProductSetPreview(card)}>
|
||||
<img src={card.src} alt={card.label} />
|
||||
<span>{card.label}</span>
|
||||
</button>
|
||||
))
|
||||
) : results[0]?.src ? (
|
||||
<button type="button" onClick={() => openProductSetPreview(results[0])}>
|
||||
<img src={results[0].src} alt={selectedCloneOutput.label} />
|
||||
<span>{selectedCloneOutput.label}</span>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<section className="clone-ai-empty-state" aria-live="polite">
|
||||
{status === "generating" ? <LoadingOutlined /> : status === "failed" ? <FrownOutlined /> : <FileImageOutlined />}
|
||||
<strong>{status === "generating" ? "正在生成" : status === "failed" ? "生成失败" : "等待生成"}</strong>
|
||||
{status === "generating" ? <EcommerceProgressBar status="generating" label={`${selectedCloneOutput.label}生成`} /> : null}
|
||||
<span>
|
||||
{status === "generating"
|
||||
? `AI 正在为 ${platform} / ${market} 整理${selectedCloneOutput.label}。`
|
||||
: status === "failed"
|
||||
? "请检查网络后点击下方重试"
|
||||
: "上传商品原图并填写信息后,AI 将在这里展示生成结果。"}
|
||||
</span>
|
||||
{status === "failed" && lastFailedActionRef.current ? (
|
||||
<button type="button" className="clone-ai-retry-btn" onClick={lastFailedActionRef.current}>
|
||||
<ReloadOutlined /> 重试
|
||||
</button>
|
||||
) : null}
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<section className="clone-ai-bottom-input" aria-label="信息详情">
|
||||
|
||||
Reference in New Issue
Block a user