2026-06-03 23:20:57 +08:00
|
|
|
|
import { CloudUploadOutlined, CloseOutlined, FileImageOutlined, SettingOutlined } from "@ant-design/icons";
|
|
|
|
|
|
import type { ChangeEvent, DragEvent, RefObject } from "react";
|
|
|
|
|
|
|
2026-06-04 16:03:49 +08:00
|
|
|
|
type ProductSetOutputKey = "set" | "detail" | "model" | "video";
|
|
|
|
|
|
|
2026-06-03 23:20:57 +08:00
|
|
|
|
interface EcommerceSetPanelProps {
|
|
|
|
|
|
setInputRef: RefObject<HTMLInputElement>;
|
|
|
|
|
|
setImages: Array<{ id: string; src: string; name: string }>;
|
|
|
|
|
|
isSetUploadDragging: boolean;
|
2026-06-04 16:03:49 +08:00
|
|
|
|
productSetOutputOptions: Array<{ key: ProductSetOutputKey; label: string }>;
|
|
|
|
|
|
productSetOutput: ProductSetOutputKey;
|
2026-06-03 23:20:57 +08:00
|
|
|
|
platformOptions: string[];
|
|
|
|
|
|
marketOptions: string[];
|
|
|
|
|
|
productSetLanguageOptions: string[];
|
|
|
|
|
|
productSetRatioOptions: string[];
|
|
|
|
|
|
productSetPlatform: string;
|
|
|
|
|
|
productSetMarket: string;
|
|
|
|
|
|
productSetLanguage: string;
|
|
|
|
|
|
productSetRatio: string;
|
|
|
|
|
|
setIsSetUploadDragging: (value: boolean) => void;
|
2026-06-04 16:03:49 +08:00
|
|
|
|
handleSetDrop: (event: DragEvent<HTMLButtonElement>) => void;
|
2026-06-03 23:20:57 +08:00
|
|
|
|
handleSetUpload: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
|
|
|
|
removeSetImage: (id: string) => void;
|
2026-06-04 16:03:49 +08:00
|
|
|
|
handleProductSetOutputChange: (value: ProductSetOutputKey) => void;
|
2026-06-03 23:20:57 +08:00
|
|
|
|
handleProductSetPlatformChange: (value: string) => void;
|
|
|
|
|
|
handleProductSetMarketChange: (value: string) => void;
|
|
|
|
|
|
setProductSetLanguage: (value: string) => void;
|
|
|
|
|
|
setProductSetRatio: (value: string) => void;
|
|
|
|
|
|
formatRatioDisplayValue: (value: string) => string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function EcommerceSetPanel({
|
|
|
|
|
|
setInputRef,
|
|
|
|
|
|
setImages,
|
|
|
|
|
|
isSetUploadDragging,
|
|
|
|
|
|
productSetOutputOptions,
|
|
|
|
|
|
productSetOutput,
|
|
|
|
|
|
platformOptions,
|
|
|
|
|
|
marketOptions,
|
|
|
|
|
|
productSetLanguageOptions,
|
|
|
|
|
|
productSetRatioOptions,
|
|
|
|
|
|
productSetPlatform,
|
|
|
|
|
|
productSetMarket,
|
|
|
|
|
|
productSetLanguage,
|
|
|
|
|
|
productSetRatio,
|
|
|
|
|
|
setIsSetUploadDragging,
|
|
|
|
|
|
handleSetDrop,
|
|
|
|
|
|
handleSetUpload,
|
|
|
|
|
|
removeSetImage,
|
|
|
|
|
|
handleProductSetOutputChange,
|
|
|
|
|
|
handleProductSetPlatformChange,
|
|
|
|
|
|
handleProductSetMarketChange,
|
|
|
|
|
|
setProductSetLanguage,
|
|
|
|
|
|
setProductSetRatio,
|
|
|
|
|
|
formatRatioDisplayValue,
|
|
|
|
|
|
}: EcommerceSetPanelProps) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<div className="product-clone-panel__scroll">
|
|
|
|
|
|
<section className="product-clone-field product-set-upload-section">
|
|
|
|
|
|
<h2>
|
|
|
|
|
|
上传商品原图
|
|
|
|
|
|
<CloudUploadOutlined />
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={`product-clone-upload-zone product-set-upload${isSetUploadDragging ? " is-dragging" : ""}`}
|
|
|
|
|
|
onClick={() => setInputRef.current?.click()}
|
|
|
|
|
|
onDragEnter={(event) => {
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
|
setIsSetUploadDragging(true);
|
|
|
|
|
|
}}
|
|
|
|
|
|
onDragOver={(event) => event.preventDefault()}
|
|
|
|
|
|
onDragLeave={() => setIsSetUploadDragging(false)}
|
|
|
|
|
|
onDrop={handleSetDrop}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span className="product-set-upload-icon">
|
|
|
|
|
|
<FileImageOutlined />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="product-set-upload-title">拖拽或点击上传</span>
|
|
|
|
|
|
<strong>
|
|
|
|
|
|
<span aria-hidden="true">+</span>
|
|
|
|
|
|
上传图片
|
|
|
|
|
|
</strong>
|
|
|
|
|
|
<span className="product-set-upload-note">同一产品,最多 3 张</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<input ref={setInputRef} type="file" accept="image/jpeg,image/png,image/webp" multiple onChange={handleSetUpload} />
|
|
|
|
|
|
{setImages.length ? (
|
|
|
|
|
|
<div className="product-clone-thumb-row product-set-thumb-row" aria-label="已上传商品原图">
|
|
|
|
|
|
{setImages.map((item) => (
|
|
|
|
|
|
<figure key={item.id} className="product-set-thumb">
|
|
|
|
|
|
<img src={item.src} alt={item.name} />
|
|
|
|
|
|
<span className="uploaded-image-zoom" aria-hidden="true">
|
|
|
|
|
|
<img src={item.src} alt="" />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<button type="button" onClick={() => removeSetImage(item.id)} aria-label={`删除${item.name}`}>
|
|
|
|
|
|
<CloseOutlined />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</figure>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="product-clone-field product-set-settings-section">
|
|
|
|
|
|
<h2>
|
|
|
|
|
|
生成设置
|
|
|
|
|
|
<SettingOutlined />
|
|
|
|
|
|
</h2>
|
|
|
|
|
|
<div className="product-set-setting-block">
|
|
|
|
|
|
<span className="product-set-setting-title">生成内容</span>
|
|
|
|
|
|
<div className="product-set-output-grid" role="radiogroup" aria-label="生成内容">
|
|
|
|
|
|
{productSetOutputOptions.map((option) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={option.key}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={productSetOutput === option.key ? "is-active" : ""}
|
|
|
|
|
|
aria-pressed={productSetOutput === option.key}
|
|
|
|
|
|
onClick={() => handleProductSetOutputChange(option.key)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{option.label}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="product-set-setting-block">
|
|
|
|
|
|
<span className="product-set-setting-title">基础设置</span>
|
|
|
|
|
|
<div className="product-set-field-grid">
|
|
|
|
|
|
<label>
|
|
|
|
|
|
<span>平台</span>
|
|
|
|
|
|
<select value={productSetPlatform} onChange={(event) => handleProductSetPlatformChange(event.target.value)}>
|
|
|
|
|
|
{platformOptions.map((item) => (
|
|
|
|
|
|
<option key={item}>{item}</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label>
|
|
|
|
|
|
<span>国家</span>
|
|
|
|
|
|
<select value={productSetMarket} onChange={(event) => handleProductSetMarketChange(event.target.value)}>
|
|
|
|
|
|
{marketOptions.map((item) => (
|
|
|
|
|
|
<option key={item}>{item}</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label>
|
|
|
|
|
|
<span>语言</span>
|
|
|
|
|
|
<select value={productSetLanguage} onChange={(event) => setProductSetLanguage(event.target.value)}>
|
|
|
|
|
|
{productSetLanguageOptions.map((item) => (
|
|
|
|
|
|
<option key={item}>{item}</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<label>
|
|
|
|
|
|
<span>尺寸/比例</span>
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={productSetRatio}
|
|
|
|
|
|
onChange={(event) => setProductSetRatio(event.target.value)}
|
|
|
|
|
|
disabled={productSetRatioOptions.length <= 1}
|
|
|
|
|
|
>
|
|
|
|
|
|
{productSetRatioOptions.map((item) => (
|
|
|
|
|
|
<option key={item} value={item}>{formatRatioDisplayValue(item)}</option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|