feat: UI animation enhancements across all major pages
P1 - Critical UX feedback: - Add scale-in + slide-up-in entrance animations to profile popover and notification panel - Port SmoothedProgressBar to EcommercePage (4 generation tools: clone, detail, tryOn, productSet) - Add result-reveal stagger animation to ecommerce result grids - Add heart-pop spring animation to CommunityPage favorite toggle P2 - Visual polish: - Add scroll-entrance IntersectionObserver animations for HomePage feature sections and experience section - Add chat-message-in entrance animation to WorkbenchPage message rows - Fix prefers-reduced-motion accessibility in WelcomeSplash canvas (skip animation, instant entry) P3 - CSS consolidation: - Remove conflicting .page-motion definition from legacy-pages.css (keep translateY version from legacy-components.css) - Consolidate skeleton-shimmer: remove opacity-pulse keyframe from primitives.css, unify with gradient sweep - Wire up --ease-spring token for heart-pop animation - Add :active press states (scale 0.97) to topbar buttons, brand lockup Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
SkinOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useEffect, useRef, useState, type CSSProperties, type ChangeEvent, type DragEvent, type ReactNode } from "react";
|
||||
import { EcommerceProgressBar } from "./EcommerceProgressBar";
|
||||
|
||||
const OSS_MUBAN = "https://stringtest.oss-cn-hangzhou.aliyuncs.com/muban";
|
||||
const ecommerceGenerated = `${OSS_MUBAN}/ecommerce-carousel-generated.png`;
|
||||
@@ -2402,6 +2403,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
</div>
|
||||
|
||||
<footer className="product-clone-panel__footer">
|
||||
{detailStatus === "generating" ? <EcommerceProgressBar status="generating" label="A+详情页" /> : null}
|
||||
<button type="button" className="product-clone-primary" disabled={!canGenerateDetail} onClick={handleDetailGenerate}>
|
||||
{detailStatus === "generating" ? <LoadingOutlined /> : null}
|
||||
{detailPrimaryLabel}
|
||||
@@ -2547,6 +2549,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
</div>
|
||||
|
||||
<footer className="product-clone-panel__footer">
|
||||
{tryOnStatus === "generating" ? <EcommerceProgressBar status="generating" label="服饰穿戴图" /> : null}
|
||||
<button type="button" className="product-clone-primary" disabled={!canGenerateTryOn} onClick={handleTryOnGenerate}>
|
||||
{tryOnStatus === "generating" ? <LoadingOutlined /> : null}
|
||||
{tryOnPrimaryLabel}
|
||||
@@ -2592,7 +2595,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
<span>{productSetPreviewCards[0].label}</span>
|
||||
</button>
|
||||
<div className="product-set-flow-arrow" aria-hidden="true" />
|
||||
<div className="product-set-card-grid">
|
||||
<div className="product-set-card-grid result-reveal">
|
||||
{productSetPreviewCards.slice(1).map((card) => (
|
||||
<button key={card.id} type="button" onClick={() => openProductSetPreview(card)}>
|
||||
<img src={card.src} alt={card.label} />
|
||||
@@ -2605,6 +2608,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
<section className="product-set-empty-preview" aria-live="polite">
|
||||
{productSetStatus === "generating" ? <LoadingOutlined /> : <FileImageOutlined />}
|
||||
<strong>{productSetStatus === "generating" ? "正在生成" : "等待生成"}</strong>
|
||||
{productSetStatus === "generating" ? <EcommerceProgressBar status="generating" label="商品套图" /> : null}
|
||||
<span>{productSetStatus === "generating" ? "AI 正在整理主图、场景、细节与卖点图。" : "上传商品原图并填写信息后,AI 将为您生成专业的电商商品图"}</span>
|
||||
</section>
|
||||
)}
|
||||
@@ -2650,7 +2654,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
<span>原图素材</span>
|
||||
</button>
|
||||
<div className="clone-ai-flow-arrow" aria-hidden="true" />
|
||||
<div className="clone-ai-result-grid">
|
||||
<div className="clone-ai-result-grid result-reveal">
|
||||
{clonePreviewCards.map((card) => (
|
||||
<button key={card.id} type="button" onClick={() => openProductSetPreview(card)}>
|
||||
<img src={card.src} alt={card.label} />
|
||||
@@ -2663,6 +2667,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
<section className="clone-ai-empty-state" aria-live="polite">
|
||||
{status === "generating" ? <LoadingOutlined /> : <FileImageOutlined />}
|
||||
<strong>{status === "generating" ? "正在生成" : "等待生成"}</strong>
|
||||
{status === "generating" ? <EcommerceProgressBar status="generating" label={`${selectedCloneOutput.label}生成`} /> : null}
|
||||
<span>
|
||||
{status === "generating"
|
||||
? `AI 正在为 ${platform} / ${market} 整理${selectedCloneOutput.label}。`
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { useSmoothedProgress } from "../../hooks/useSmoothedProgress";
|
||||
|
||||
interface EcommerceProgressBarProps {
|
||||
status: "idle" | "generating" | "done" | "failed" | string;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
function mapStatus(status: string): "running" | "completed" | "failed" {
|
||||
if (status === "done") return "completed";
|
||||
if (status === "failed") return "failed";
|
||||
if (status === "generating" || status === "modeling") return "running";
|
||||
return "running";
|
||||
}
|
||||
|
||||
export function EcommerceProgressBar({ status, label }: EcommerceProgressBarProps) {
|
||||
const progress = mapStatus(status) === "running" ? 50 : 100;
|
||||
const smoothed = useSmoothedProgress(progress, mapStatus(status));
|
||||
|
||||
if (status === "idle") return null;
|
||||
|
||||
return (
|
||||
<div className="ecommerce-progress-bar">
|
||||
<span className="ecommerce-progress-bar__label">{label || "AI 正在生成"}</span>
|
||||
<div className="ecommerce-progress-bar__track">
|
||||
<div className="ecommerce-progress-bar__fill" style={{ width: `${smoothed}%` }} />
|
||||
</div>
|
||||
<span className="ecommerce-progress-bar__value">{smoothed}%</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user