220 lines
8.4 KiB
TypeScript
220 lines
8.4 KiB
TypeScript
|
|
import { CloudUploadOutlined, LoadingOutlined, QuestionCircleOutlined } from "@ant-design/icons";
|
|||
|
|
import type { ChangeEvent, RefObject } from "react";
|
|||
|
|
import { EcommerceProgressBar } from "../EcommerceProgressBar";
|
|||
|
|
|
|||
|
|
interface EcommerceTryOnPanelProps {
|
|||
|
|
garmentInputRef: RefObject<HTMLInputElement>;
|
|||
|
|
garmentImages: Array<{ id: string; src: string; name: string }>;
|
|||
|
|
modelSource: string;
|
|||
|
|
modelGender: string;
|
|||
|
|
modelAge: string;
|
|||
|
|
modelEthnicity: string;
|
|||
|
|
modelBody: string;
|
|||
|
|
appearance: string;
|
|||
|
|
selectedScenes: string[];
|
|||
|
|
customScene: string;
|
|||
|
|
smartScene: boolean;
|
|||
|
|
tryOnRatio: string;
|
|||
|
|
tryOnStatus: string;
|
|||
|
|
canGenerateTryOn: boolean;
|
|||
|
|
tryOnPrimaryLabel: string;
|
|||
|
|
tryOnModelOptions: { gender: string[]; age: string[]; ethnicity: string[]; body: string[] };
|
|||
|
|
tryOnAssets: { modelWoman: string; modelMan: string; modelAsian: string };
|
|||
|
|
tryOnScenes: string[];
|
|||
|
|
tryOnRatioOptions: string[];
|
|||
|
|
handleGarmentUpload: (event: ChangeEvent<HTMLInputElement>) => void;
|
|||
|
|
setModelSource: (value: "ai" | "library") => void;
|
|||
|
|
setModelGender: (value: string) => void;
|
|||
|
|
setModelAge: (value: string) => void;
|
|||
|
|
setModelEthnicity: (value: string) => void;
|
|||
|
|
setModelBody: (value: string) => void;
|
|||
|
|
setAppearance: (value: string) => void;
|
|||
|
|
handleGenerateModel: () => void;
|
|||
|
|
toggleScene: (scene: string) => void;
|
|||
|
|
setCustomScene: (value: string) => void;
|
|||
|
|
setSmartScene: (updater: (current: boolean) => boolean) => void;
|
|||
|
|
setTryOnRatio: (value: string) => void;
|
|||
|
|
handleTryOnGenerate: () => void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export default function EcommerceTryOnPanel({
|
|||
|
|
garmentInputRef,
|
|||
|
|
garmentImages,
|
|||
|
|
modelSource,
|
|||
|
|
modelGender,
|
|||
|
|
modelAge,
|
|||
|
|
modelEthnicity,
|
|||
|
|
modelBody,
|
|||
|
|
appearance,
|
|||
|
|
selectedScenes,
|
|||
|
|
customScene,
|
|||
|
|
smartScene,
|
|||
|
|
tryOnRatio,
|
|||
|
|
tryOnStatus,
|
|||
|
|
canGenerateTryOn,
|
|||
|
|
tryOnPrimaryLabel,
|
|||
|
|
tryOnModelOptions,
|
|||
|
|
tryOnAssets,
|
|||
|
|
tryOnScenes,
|
|||
|
|
tryOnRatioOptions,
|
|||
|
|
handleGarmentUpload,
|
|||
|
|
setModelSource,
|
|||
|
|
setModelGender,
|
|||
|
|
setModelAge,
|
|||
|
|
setModelEthnicity,
|
|||
|
|
setModelBody,
|
|||
|
|
setAppearance,
|
|||
|
|
handleGenerateModel,
|
|||
|
|
toggleScene,
|
|||
|
|
setCustomScene,
|
|||
|
|
setSmartScene,
|
|||
|
|
setTryOnRatio,
|
|||
|
|
handleTryOnGenerate,
|
|||
|
|
}: EcommerceTryOnPanelProps) {
|
|||
|
|
return (
|
|||
|
|
<>
|
|||
|
|
<div className="product-clone-panel__scroll">
|
|||
|
|
<section className="product-clone-field">
|
|||
|
|
<h2>服装图片</h2>
|
|||
|
|
<button type="button" className="product-clone-upload-zone product-try-on-upload" onClick={() => garmentInputRef.current?.click()}>
|
|||
|
|
<strong>
|
|||
|
|
<CloudUploadOutlined />
|
|||
|
|
服装图片
|
|||
|
|
</strong>
|
|||
|
|
<span>整套搭配或同一件服装不同角度图,最多5张。</span>
|
|||
|
|
</button>
|
|||
|
|
<input ref={garmentInputRef} type="file" accept="image/*" multiple onChange={handleGarmentUpload} />
|
|||
|
|
{garmentImages.length ? (
|
|||
|
|
<div className="product-clone-thumb-row product-try-on-thumb-row" aria-label="已上传服装图片">
|
|||
|
|
{garmentImages.map((item) => (
|
|||
|
|
<figure key={item.id} className="product-clone-uploaded-thumb">
|
|||
|
|
<img src={item.src} alt={item.name} />
|
|||
|
|
<span className="uploaded-image-zoom" aria-hidden="true">
|
|||
|
|
<img src={item.src} alt="" />
|
|||
|
|
</span>
|
|||
|
|
</figure>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
) : null}
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<section className="product-clone-field">
|
|||
|
|
<h2>模特形象</h2>
|
|||
|
|
<div className="product-clone-segment" role="tablist" aria-label="模特来源">
|
|||
|
|
<button type="button" className={modelSource === "ai" ? "is-active" : ""} onClick={() => setModelSource("ai")}>
|
|||
|
|
AI 生成
|
|||
|
|
</button>
|
|||
|
|
<button type="button" className={modelSource === "library" ? "is-active" : ""} onClick={() => setModelSource("library")}>
|
|||
|
|
模特库
|
|||
|
|
<QuestionCircleOutlined />
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
{modelSource === "ai" ? (
|
|||
|
|
<>
|
|||
|
|
<div className="product-clone-model-grid">
|
|||
|
|
<select value={modelGender} onChange={(event) => setModelGender(event.target.value)}>
|
|||
|
|
{tryOnModelOptions.gender.map((item) => (
|
|||
|
|
<option key={item}>{item}</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
<select value={modelAge} onChange={(event) => setModelAge(event.target.value)}>
|
|||
|
|
{tryOnModelOptions.age.map((item) => (
|
|||
|
|
<option key={item}>{item}</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
<select value={modelEthnicity} onChange={(event) => setModelEthnicity(event.target.value)}>
|
|||
|
|
{tryOnModelOptions.ethnicity.map((item) => (
|
|||
|
|
<option key={item}>{item}</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
<select value={modelBody} onChange={(event) => setModelBody(event.target.value)}>
|
|||
|
|
{tryOnModelOptions.body.map((item) => (
|
|||
|
|
<option key={item}>{item}</option>
|
|||
|
|
))}
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
<label className="product-try-on-textarea-label">
|
|||
|
|
<span>外貌细节(可选)</span>
|
|||
|
|
<textarea
|
|||
|
|
value={appearance}
|
|||
|
|
onChange={(event) => setAppearance(event.target.value)}
|
|||
|
|
placeholder="例如:小麦色皮肤、齐刘海、眼角有泪痣..."
|
|||
|
|
/>
|
|||
|
|
</label>
|
|||
|
|
<button type="button" className="product-clone-model-button" onClick={handleGenerateModel} disabled={tryOnStatus === "modeling"}>
|
|||
|
|
{tryOnStatus === "modeling" ? <LoadingOutlined /> : null}
|
|||
|
|
{tryOnStatus === "modeling" ? "生成中..." : "生成基准模特"}
|
|||
|
|
</button>
|
|||
|
|
</>
|
|||
|
|
) : (
|
|||
|
|
<div className="product-try-on-library" aria-label="模特库">
|
|||
|
|
{[tryOnAssets.modelWoman, tryOnAssets.modelMan, tryOnAssets.modelAsian].map((src, index) => (
|
|||
|
|
<button key={src} type="button" className={index === 0 ? "is-active" : ""}>
|
|||
|
|
<img src={src} alt={`模特 ${index + 1}`} />
|
|||
|
|
</button>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<section className="product-clone-field">
|
|||
|
|
<h2>拍摄场景</h2>
|
|||
|
|
<div className="product-clone-scene-grid">
|
|||
|
|
{tryOnScenes.map((scene) => (
|
|||
|
|
<button
|
|||
|
|
key={scene}
|
|||
|
|
type="button"
|
|||
|
|
className={selectedScenes.includes(scene) ? "is-active" : ""}
|
|||
|
|
onClick={() => toggleScene(scene)}
|
|||
|
|
>
|
|||
|
|
<span aria-hidden="true" />
|
|||
|
|
{scene}
|
|||
|
|
</button>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<label className="product-clone-field product-try-on-scene-field">
|
|||
|
|
<h2>或自定义描述场景(可选)</h2>
|
|||
|
|
<textarea
|
|||
|
|
value={customScene}
|
|||
|
|
onChange={(event) => setCustomScene(event.target.value)}
|
|||
|
|
placeholder="描述你想要的场景:如秋季枫叶小径、暖色调午后阳光、模特倚靠树干..."
|
|||
|
|
/>
|
|||
|
|
</label>
|
|||
|
|
|
|||
|
|
<section className="product-clone-field">
|
|||
|
|
<button type="button" className="product-clone-switch-row" onClick={() => setSmartScene((current) => !current)}>
|
|||
|
|
<span>
|
|||
|
|
<strong>智能推荐场景</strong>
|
|||
|
|
<em>根据服装自动匹配最佳场景</em>
|
|||
|
|
</span>
|
|||
|
|
<span className={`product-clone-switch${smartScene ? " is-on" : ""}`} role="switch" aria-checked={smartScene}>
|
|||
|
|
<span />
|
|||
|
|
</span>
|
|||
|
|
</button>
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<section className="product-clone-field">
|
|||
|
|
<h2>图片比例</h2>
|
|||
|
|
<div className="product-clone-ratio-row">
|
|||
|
|
{tryOnRatioOptions.map((item) => (
|
|||
|
|
<button key={item} type="button" className={tryOnRatio === item ? "is-active" : ""} onClick={() => setTryOnRatio(item)}>
|
|||
|
|
{item}
|
|||
|
|
</button>
|
|||
|
|
))}
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
</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}
|
|||
|
|
</button>
|
|||
|
|
</footer>
|
|||
|
|
</>
|
|||
|
|
);
|
|||
|
|
}
|