feat(ecommerce): add one-click video quick tool page
- Add '一键视频' button left of '更多功能' in quick action board - Create EcommerceOneClickVideoPanel with hot-clone-like UI - Reuse EcommerceVideoWorkspace on the right for video flow - Add light-theme CSS matching quick-set/hot-clone pages
This commit is contained in:
@@ -51,6 +51,7 @@ import EcommerceSetPanel from "./panels/EcommerceSetPanel";
|
|||||||
import EcommerceTryOnPanel from "./panels/EcommerceTryOnPanel";
|
import EcommerceTryOnPanel from "./panels/EcommerceTryOnPanel";
|
||||||
import EcommerceClonePanel from "./panels/EcommerceClonePanel";
|
import EcommerceClonePanel from "./panels/EcommerceClonePanel";
|
||||||
import EcommerceCopywritingPanel from "./panels/EcommerceCopywritingPanel";
|
import EcommerceCopywritingPanel from "./panels/EcommerceCopywritingPanel";
|
||||||
|
import EcommerceOneClickVideoPanel from "./panels/EcommerceOneClickVideoPanel";
|
||||||
import { ecommerceOssScopes, saveUnifiedEcommerceGenerationRecord, deleteEcommerceGenerationRecord } from "./ecommerceGenerationPersistence";
|
import { ecommerceOssScopes, saveUnifiedEcommerceGenerationRecord, deleteEcommerceGenerationRecord } from "./ecommerceGenerationPersistence";
|
||||||
import { downloadResultAsset } from "../workbench/workbenchDownload";
|
import { downloadResultAsset } from "../workbench/workbenchDownload";
|
||||||
import type { CloneOutputKey, ProductSetOutputKey } from "./utils/platformRules";
|
import type { CloneOutputKey, ProductSetOutputKey } from "./utils/platformRules";
|
||||||
@@ -1885,7 +1886,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
const [selectedProductSetPreview, setSelectedProductSetPreview] = useState<ProductSetPreviewSelection | null>(null);
|
const [selectedProductSetPreview, setSelectedProductSetPreview] = useState<ProductSetPreviewSelection | null>(null);
|
||||||
const [showHostingModal, setShowHostingModal] = useState(false);
|
const [showHostingModal, setShowHostingModal] = useState(false);
|
||||||
const [productImages, setProductImages] = useState<CloneImageItem[]>([]);
|
const [productImages, setProductImages] = useState<CloneImageItem[]>([]);
|
||||||
const [activeQuickTool, setActiveQuickTool] = useState<"cutout" | "detail" | "watermark" | "image-edit" | "translate" | "hot" | "quick-set" | "copywriting" | null>(null);
|
const [activeQuickTool, setActiveQuickTool] = useState<"cutout" | "detail" | "watermark" | "image-edit" | "translate" | "hot" | "quick-set" | "copywriting" | "oneClickVideo" | null>(null);
|
||||||
const [smartCutoutImage, setSmartCutoutImage] = useState<SmartCutoutImageItem | null>(null);
|
const [smartCutoutImage, setSmartCutoutImage] = useState<SmartCutoutImageItem | null>(null);
|
||||||
const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState<SmartCutoutImageItem[]>([]);
|
const [smartCutoutBatchImages, setSmartCutoutBatchImages] = useState<SmartCutoutImageItem[]>([]);
|
||||||
const [smartCutoutBackgroundColor, setSmartCutoutBackgroundColor] = useState("#ffffff");
|
const [smartCutoutBackgroundColor, setSmartCutoutBackgroundColor] = useState("#ffffff");
|
||||||
@@ -5019,6 +5020,26 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
setComposerMenu(null);
|
setComposerMenu(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openOneClickVideoPage = () => {
|
||||||
|
clearSmartCutoutTransition();
|
||||||
|
setActiveQuickTool("oneClickVideo");
|
||||||
|
setComposerMenu(null);
|
||||||
|
setIsCloneSettingsCollapsed(false);
|
||||||
|
setIsQuickPanelCollapsed(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeOneClickVideoPage = () => {
|
||||||
|
setActiveQuickTool(null);
|
||||||
|
setComposerMenu(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOneClickVideoPlatformChange = (nextPlatform: string) => {
|
||||||
|
const normalizedPlatform = normalizePlatform(nextPlatform);
|
||||||
|
setPlatform(normalizedPlatform);
|
||||||
|
setRatio((current) => normalizeRatioForPlatform(normalizedPlatform, current, "video"));
|
||||||
|
setLanguage(getPlatformDefaultLanguage(normalizedPlatform, market));
|
||||||
|
};
|
||||||
|
|
||||||
const resetTask = () => {
|
const resetTask = () => {
|
||||||
setSetImages([]);
|
setSetImages([]);
|
||||||
setProductSetRequirement("");
|
setProductSetRequirement("");
|
||||||
@@ -5084,6 +5105,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
const isHotCloneTool = isCloneTool && activeQuickTool === "hot";
|
const isHotCloneTool = isCloneTool && activeQuickTool === "hot";
|
||||||
const isQuickSetTool = isCloneTool && activeQuickTool === "quick-set";
|
const isQuickSetTool = isCloneTool && activeQuickTool === "quick-set";
|
||||||
const isCopywritingTool = isCloneTool && activeQuickTool === "copywriting";
|
const isCopywritingTool = isCloneTool && activeQuickTool === "copywriting";
|
||||||
|
const isOneClickVideoTool = isCloneTool && activeQuickTool === "oneClickVideo";
|
||||||
const pageLabel = isSetTool ? "商品套图" : isDetail ? "A+/详情页" : isTryOn ? "AI服饰穿戴" : activeToolMeta?.label || "商品工具";
|
const pageLabel = isSetTool ? "商品套图" : isDetail ? "A+/详情页" : isTryOn ? "AI服饰穿戴" : activeToolMeta?.label || "商品工具";
|
||||||
const setPrimaryLabel =
|
const setPrimaryLabel =
|
||||||
setImages.length === 0
|
setImages.length === 0
|
||||||
@@ -6587,6 +6609,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
{ label: "图片翻译", tone: "translate", icon: <GlobalOutlined />, onClick: openImageTranslatePage },
|
{ label: "图片翻译", tone: "translate", icon: <GlobalOutlined />, onClick: openImageTranslatePage },
|
||||||
{ label: "商品套图", tone: "product", icon: <AppstoreOutlined />, onClick: openQuickSetPage },
|
{ label: "商品套图", tone: "product", icon: <AppstoreOutlined />, onClick: openQuickSetPage },
|
||||||
{ label: "一键文案", tone: "copywriting", icon: <EditOutlined />, onClick: openCopywritingPage },
|
{ label: "一键文案", tone: "copywriting", icon: <EditOutlined />, onClick: openCopywritingPage },
|
||||||
|
{ label: "一键视频", tone: "video", icon: <VideoCameraOutlined />, onClick: openOneClickVideoPage },
|
||||||
{ label: "更多功能", tone: "more", icon: <SettingOutlined />, disabled: true },
|
{ label: "更多功能", tone: "more", icon: <SettingOutlined />, disabled: true },
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<button
|
<button
|
||||||
@@ -8430,6 +8453,42 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const oneClickVideoPreview = (
|
||||||
|
<div key="oneClickVideo" className="ecom-quick-page-wrap ecom-tool-page-enter">
|
||||||
|
<EcommerceOneClickVideoPanel
|
||||||
|
onClose={closeOneClickVideoPage}
|
||||||
|
isAuthenticated={isAuthenticated}
|
||||||
|
onRequestLogin={requestLogin}
|
||||||
|
productImages={productImages}
|
||||||
|
productInputRef={productInputRef}
|
||||||
|
isProductUploadDragging={isProductUploadDragging}
|
||||||
|
setIsProductUploadDragging={setIsProductUploadDragging}
|
||||||
|
handleProductDrop={handleProductDrop}
|
||||||
|
handleProductUpload={handleProductUpload}
|
||||||
|
removeProductImage={removeProductImage}
|
||||||
|
maxProductImages={maxCloneProductImages}
|
||||||
|
requirement={requirement}
|
||||||
|
onRequirementChange={setRequirement}
|
||||||
|
platform={platform}
|
||||||
|
platformOptions={platformOptions}
|
||||||
|
onPlatformChange={handleOneClickVideoPlatformChange}
|
||||||
|
ratio={ratio}
|
||||||
|
ratioOptions={getPlatformRatioOptions(platform, "video")}
|
||||||
|
onRatioChange={setRatio}
|
||||||
|
videoQuality={cloneVideoQuality}
|
||||||
|
videoQualityOptions={cloneVideoQualityOptions}
|
||||||
|
onVideoQualityChange={setCloneVideoQuality}
|
||||||
|
videoDuration={cloneVideoDuration}
|
||||||
|
videoDurationMin={cloneVideoDurationMin}
|
||||||
|
videoDurationMax={cloneVideoDurationMax}
|
||||||
|
onVideoDurationChange={setCloneVideoDuration}
|
||||||
|
videoSmart={cloneVideoSmart}
|
||||||
|
onVideoSmartChange={setCloneVideoSmart}
|
||||||
|
onOpenHistory={() => setVideoHistoryVisible(true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const activePreview = isSetTool
|
const activePreview = isSetTool
|
||||||
? setPreview
|
? setPreview
|
||||||
: isDetail
|
: isDetail
|
||||||
@@ -8465,9 +8524,11 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
)
|
)
|
||||||
: isCopywritingTool
|
: isCopywritingTool
|
||||||
? copywritingPreview
|
? copywritingPreview
|
||||||
|
: isOneClickVideoTool
|
||||||
|
? oneClickVideoPreview
|
||||||
: clonePreview
|
: clonePreview
|
||||||
: placeholderPreview;
|
: placeholderPreview;
|
||||||
const isMainCloneWorkspace = isCloneTool && !isSmartCutoutTool && !isQuickDetailTool && !isWatermarkTool && !isTranslateTool && !isImageEditTool && !isQuickSetTool && !isCopywritingTool;
|
const isMainCloneWorkspace = isCloneTool && !isSmartCutoutTool && !isQuickDetailTool && !isWatermarkTool && !isTranslateTool && !isImageEditTool && !isQuickSetTool && !isCopywritingTool && !isOneClickVideoTool;
|
||||||
const isRecordDetailWorkspace = isMainCloneWorkspace && Boolean(activeHistoryRecordId);
|
const isRecordDetailWorkspace = isMainCloneWorkspace && Boolean(activeHistoryRecordId);
|
||||||
const currentResultCount = canvasNodes.reduce((count, node) => count + node.results.length, 0);
|
const currentResultCount = canvasNodes.reduce((count, node) => count + node.results.length, 0);
|
||||||
const activeHistoryRecord = activeHistoryRecordId ? ecommerceHistoryRecords.find((record) => record.id === activeHistoryRecordId) : null;
|
const activeHistoryRecord = activeHistoryRecordId ? ecommerceHistoryRecords.find((record) => record.id === activeHistoryRecordId) : null;
|
||||||
@@ -8503,7 +8564,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className={`product-clone-page page-motion${isCloneTool && isCloneSettingsCollapsed ? " is-settings-collapsed" : ""}${isCloneTool && isCommandHistoryCollapsed ? " is-history-collapsed" : ""}${isRecordDetailWorkspace && isCloneConversationCollapsed ? " is-conversation-collapsed" : ""}${isRecordDetailWorkspace ? " is-history-detail" : ""}${isSmartCutoutTool ? " is-smart-cutout-page" : ""}${isQuickDetailTool ? " is-quick-set-page" : ""}${isWatermarkTool ? " is-watermark-page" : ""}${isTranslateTool ? " is-translate-page" : ""}${isImageEditTool ? " is-image-workbench-page" : ""}${isHotCloneTool ? " is-hot-clone-page" : ""}${isQuickSetTool ? " is-quick-set-page" : ""}${isCopywritingTool ? " is-copywriting-page" : ""}`}
|
className={`product-clone-page page-motion${isCloneTool && isCloneSettingsCollapsed ? " is-settings-collapsed" : ""}${isCloneTool && isCommandHistoryCollapsed ? " is-history-collapsed" : ""}${isRecordDetailWorkspace && isCloneConversationCollapsed ? " is-conversation-collapsed" : ""}${isRecordDetailWorkspace ? " is-history-detail" : ""}${isSmartCutoutTool ? " is-smart-cutout-page" : ""}${isQuickDetailTool ? " is-quick-set-page" : ""}${isWatermarkTool ? " is-watermark-page" : ""}${isTranslateTool ? " is-translate-page" : ""}${isImageEditTool ? " is-image-workbench-page" : ""}${isHotCloneTool ? " is-hot-clone-page" : ""}${isQuickSetTool ? " is-quick-set-page" : ""}${isCopywritingTool ? " is-copywriting-page" : ""}${isOneClickVideoTool ? " is-one-click-video-page" : ""}`}
|
||||||
data-tool={activeTool}
|
data-tool={activeTool}
|
||||||
aria-label={pageLabel}
|
aria-label={pageLabel}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,407 @@
|
|||||||
|
import {
|
||||||
|
FileImageOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
ThunderboltOutlined,
|
||||||
|
VideoCameraOutlined,
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
import { useMemo, useState, type ChangeEvent, type DragEvent, type KeyboardEvent, type RefObject } from "react";
|
||||||
|
import EcommerceVideoWorkspace from "../EcommerceVideoWorkspace";
|
||||||
|
|
||||||
|
interface CloneImageItem {
|
||||||
|
id: string;
|
||||||
|
src: string;
|
||||||
|
name: string;
|
||||||
|
file?: File;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloneVideoQualityKey = "standard" | "high" | "ultra";
|
||||||
|
|
||||||
|
interface EcommerceOneClickVideoPanelProps {
|
||||||
|
onClose: () => void;
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
onRequestLogin: () => void;
|
||||||
|
productImages: CloneImageItem[];
|
||||||
|
productInputRef: RefObject<HTMLInputElement>;
|
||||||
|
isProductUploadDragging: boolean;
|
||||||
|
setIsProductUploadDragging: (value: boolean) => void;
|
||||||
|
handleProductDrop: (event: DragEvent<HTMLDivElement>) => void;
|
||||||
|
handleProductUpload: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
removeProductImage: (imageId: string) => void;
|
||||||
|
maxProductImages: number;
|
||||||
|
requirement: string;
|
||||||
|
onRequirementChange: (value: string) => void;
|
||||||
|
platform: string;
|
||||||
|
platformOptions: string[];
|
||||||
|
onPlatformChange: (value: string) => void;
|
||||||
|
ratio: string;
|
||||||
|
ratioOptions: string[];
|
||||||
|
onRatioChange: (value: string) => void;
|
||||||
|
videoQuality: CloneVideoQualityKey;
|
||||||
|
videoQualityOptions: Array<{ key: CloneVideoQualityKey; label: string; desc: string }>;
|
||||||
|
onVideoQualityChange: (value: CloneVideoQualityKey) => void;
|
||||||
|
videoDuration: number;
|
||||||
|
videoDurationMin: number;
|
||||||
|
videoDurationMax: number;
|
||||||
|
onVideoDurationChange: (value: number) => void;
|
||||||
|
videoSmart: boolean;
|
||||||
|
onVideoSmartChange: (value: boolean) => void;
|
||||||
|
onOpenHistory: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVideoAspectRatio(ratio: string): string {
|
||||||
|
if (ratio.includes("9:16")) return "9:16";
|
||||||
|
if (ratio.includes("16:9")) return "16:9";
|
||||||
|
if (ratio.includes("3:4")) return "3:4";
|
||||||
|
return "9:16";
|
||||||
|
}
|
||||||
|
|
||||||
|
function openQuickUploadWithKeyboard(
|
||||||
|
event: KeyboardEvent<HTMLDivElement>,
|
||||||
|
inputRef: { current: HTMLInputElement | null },
|
||||||
|
) {
|
||||||
|
if (event.key !== "Enter" && event.key !== " ") return;
|
||||||
|
event.preventDefault();
|
||||||
|
inputRef.current?.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EcommerceOneClickVideoPanel({
|
||||||
|
onClose,
|
||||||
|
isAuthenticated,
|
||||||
|
onRequestLogin,
|
||||||
|
productImages,
|
||||||
|
productInputRef,
|
||||||
|
isProductUploadDragging,
|
||||||
|
setIsProductUploadDragging,
|
||||||
|
handleProductDrop,
|
||||||
|
handleProductUpload,
|
||||||
|
removeProductImage,
|
||||||
|
maxProductImages,
|
||||||
|
requirement,
|
||||||
|
onRequirementChange,
|
||||||
|
platform,
|
||||||
|
platformOptions,
|
||||||
|
onPlatformChange,
|
||||||
|
ratio,
|
||||||
|
ratioOptions,
|
||||||
|
onRatioChange,
|
||||||
|
videoQuality,
|
||||||
|
videoQualityOptions,
|
||||||
|
onVideoQualityChange,
|
||||||
|
videoDuration,
|
||||||
|
videoDurationMin,
|
||||||
|
videoDurationMax,
|
||||||
|
onVideoDurationChange,
|
||||||
|
videoSmart,
|
||||||
|
onVideoSmartChange,
|
||||||
|
onOpenHistory,
|
||||||
|
}: EcommerceOneClickVideoPanelProps) {
|
||||||
|
const [openSelect, setOpenSelect] = useState<"platform" | "ratio" | null>(null);
|
||||||
|
const [planTrigger, setPlanTrigger] = useState(0);
|
||||||
|
|
||||||
|
const productImageDataUrls = useMemo(() => productImages.map((img) => img.src), [productImages]);
|
||||||
|
const productImageFiles = useMemo(() => productImages.map((img) => img.file), [productImages]);
|
||||||
|
|
||||||
|
const canGenerate = productImages.length > 0 || requirement.trim().length > 0;
|
||||||
|
|
||||||
|
const handleGenerate = () => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
onRequestLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setPlanTrigger((value) => value + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePlatformSelect = (value: string) => {
|
||||||
|
onPlatformChange(value);
|
||||||
|
setOpenSelect(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRatioSelect = (value: string) => {
|
||||||
|
onRatioChange(value);
|
||||||
|
setOpenSelect(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleSelect = (key: "platform" | "ratio") => {
|
||||||
|
setOpenSelect((current) => (current === key ? null : key));
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderThumbs = () => (
|
||||||
|
<div className="ecom-quick-upload-thumbs" aria-label="已上传商品原图">
|
||||||
|
{productImages.map((item) => (
|
||||||
|
<figure key={item.id} className="ecom-command-asset-thumb ecom-quick-upload-thumb">
|
||||||
|
<img src={item.src} alt={item.name} />
|
||||||
|
<span className="ecom-command-asset-zoom" aria-hidden="true">
|
||||||
|
<img src={item.src} alt="" />
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="删除图片"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
removeProductImage(item.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</figure>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="ecom-one-click-video-page ecom-quick-hot-page ecom-quick-set-page ecom-tool-page-enter" aria-label="一键视频">
|
||||||
|
<div className="ecom-quick-set-body">
|
||||||
|
<aside className="ecom-quick-set-panel" aria-label="一键视频设置">
|
||||||
|
<header className="ecom-quick-set-panel-head">
|
||||||
|
<strong className="ecom-quick-set-page-title">
|
||||||
|
<VideoCameraOutlined /> 一键视频
|
||||||
|
</strong>
|
||||||
|
<button type="button" className="ecom-quick-set-back" onClick={onClose}>
|
||||||
|
首页
|
||||||
|
</button>
|
||||||
|
<button type="button" className="ecom-quick-set-back" onClick={onClose}>
|
||||||
|
上一页
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<strong><FileImageOutlined /> 上传商品原图</strong>
|
||||||
|
{productImages.length ? (
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className={`ecom-quick-set-upload ecom-quick-hot-material has-images${isProductUploadDragging ? " is-dragging" : ""}`}
|
||||||
|
onClick={() => productInputRef.current?.click()}
|
||||||
|
onKeyDown={(event) => openQuickUploadWithKeyboard(event, productInputRef)}
|
||||||
|
onDragOver={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
if (event.dataTransfer.types.includes("Files")) setIsProductUploadDragging(true);
|
||||||
|
}}
|
||||||
|
onDragLeave={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) {
|
||||||
|
setIsProductUploadDragging(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDrop={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
setIsProductUploadDragging(false);
|
||||||
|
handleProductDrop(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderThumbs()}
|
||||||
|
{productImages.length < maxProductImages ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="ecom-quick-hot-add-btn"
|
||||||
|
aria-label="添加更多素材"
|
||||||
|
onClick={(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
productInputRef.current?.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusOutlined />
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
className={`ecom-quick-set-upload ecom-quick-hot-material${isProductUploadDragging ? " is-dragging" : ""}`}
|
||||||
|
onClick={() => productInputRef.current?.click()}
|
||||||
|
onKeyDown={(event) => openQuickUploadWithKeyboard(event, productInputRef)}
|
||||||
|
onDragOver={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
if (event.dataTransfer.types.includes("Files")) setIsProductUploadDragging(true);
|
||||||
|
}}
|
||||||
|
onDragLeave={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
if (event.currentTarget === event.target || !event.currentTarget.contains(event.relatedTarget as Node)) {
|
||||||
|
setIsProductUploadDragging(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onDrop={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
setIsProductUploadDragging(false);
|
||||||
|
handleProductDrop(event);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FileImageOutlined />
|
||||||
|
<span>拖拽或点击上传</span>
|
||||||
|
<em>上传商品素材图,最多 {maxProductImages} 张</em>
|
||||||
|
<b>+ 上传图片</b>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<input
|
||||||
|
ref={productInputRef}
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
multiple
|
||||||
|
className="ecom-command-hidden-file"
|
||||||
|
onChange={handleProductUpload}
|
||||||
|
aria-label="上传商品图片"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="ecom-quick-hot-requirement">
|
||||||
|
<div className="ecom-quick-hot-requirement__head">
|
||||||
|
<strong>视频需求</strong>
|
||||||
|
</div>
|
||||||
|
<div className="ecom-quick-hot-requirement__input">
|
||||||
|
<textarea
|
||||||
|
value={requirement}
|
||||||
|
onChange={(event) => onRequirementChange(event.target.value.slice(0, 500))}
|
||||||
|
placeholder="建议包含以下信息:产品名称、核心卖点、期望场景、口播风格、具体参数"
|
||||||
|
maxLength={500}
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
<span>{requirement.length}/500</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="ecom-quick-set-basic-section">
|
||||||
|
<span className="ecom-quick-set-label">基础设置</span>
|
||||||
|
<div className="ecom-quick-set-select-anchor" ref={selectAnchorRef}>
|
||||||
|
<div className="ecom-quick-set-selects">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={openSelect === "platform" ? "is-active" : ""}
|
||||||
|
onClick={() => toggleSelect("platform")}
|
||||||
|
>
|
||||||
|
<span>平台</span>
|
||||||
|
<strong>{platform}</strong>
|
||||||
|
<em>⌄</em>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={openSelect === "ratio" ? "is-active" : ""}
|
||||||
|
onClick={() => toggleSelect("ratio")}
|
||||||
|
>
|
||||||
|
<span>尺寸比例</span>
|
||||||
|
<strong>{ratio.replace(/\s+/g, " ").trim()}</strong>
|
||||||
|
<em>⌄</em>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{openSelect ? (
|
||||||
|
<div
|
||||||
|
className="ecom-quick-set-dropdown"
|
||||||
|
role="listbox"
|
||||||
|
aria-label={openSelect === "platform" ? "平台" : "尺寸比例"}
|
||||||
|
>
|
||||||
|
{(openSelect === "platform" ? platformOptions : ratioOptions).map((option) => (
|
||||||
|
<button
|
||||||
|
key={option}
|
||||||
|
type="button"
|
||||||
|
className={
|
||||||
|
(openSelect === "platform" ? platform === option : ratio === option) ? "is-active" : ""
|
||||||
|
}
|
||||||
|
role="option"
|
||||||
|
aria-selected={openSelect === "platform" ? platform === option : ratio === option}
|
||||||
|
onClick={() => {
|
||||||
|
if (openSelect === "platform") {
|
||||||
|
handlePlatformSelect(option);
|
||||||
|
} else {
|
||||||
|
handleRatioSelect(option);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option.replace(/\s+/g, " ").trim()}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<strong>视频画质</strong>
|
||||||
|
<div className="ecom-quick-detail-modules">
|
||||||
|
{videoQualityOptions.map((option) => (
|
||||||
|
<button
|
||||||
|
key={option.key}
|
||||||
|
type="button"
|
||||||
|
className={videoQuality === option.key ? "is-active" : ""}
|
||||||
|
aria-pressed={videoQuality === option.key}
|
||||||
|
onClick={() => onVideoQualityChange(option.key)}
|
||||||
|
>
|
||||||
|
<strong>{option.label}</strong>
|
||||||
|
<span>{option.desc}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<strong>视频时长</strong>
|
||||||
|
<div className="ecom-one-click-video-duration">
|
||||||
|
<span>{videoDuration} 秒</span>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
className="ecom-one-click-video-range"
|
||||||
|
min={videoDurationMin}
|
||||||
|
max={videoDurationMax}
|
||||||
|
step={5}
|
||||||
|
value={videoDuration}
|
||||||
|
onChange={(event) => onVideoDurationChange(Number(event.target.value))}
|
||||||
|
aria-label="视频时长"
|
||||||
|
/>
|
||||||
|
<div className="ecom-one-click-video-duration-scale" aria-hidden="true">
|
||||||
|
<span>{videoDurationMin}秒</span>
|
||||||
|
<span>{videoDurationMax}秒</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`ecom-one-click-video-smart${videoSmart ? " is-on" : ""}`}
|
||||||
|
aria-pressed={videoSmart}
|
||||||
|
onClick={() => onVideoSmartChange(!videoSmart)}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<strong>智能优化</strong>
|
||||||
|
<em>根据平台、商品图和尺寸自动匹配推荐参数</em>
|
||||||
|
</span>
|
||||||
|
<i aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="ecom-quick-hot-actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="ecom-quick-set-primary ecom-one-click-video-generate"
|
||||||
|
onClick={handleGenerate}
|
||||||
|
disabled={!canGenerate}
|
||||||
|
>
|
||||||
|
<ThunderboltOutlined /> {isAuthenticated ? "一键生成视频" : "登录后生成"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section className="ecom-quick-set-stage">
|
||||||
|
<EcommerceVideoWorkspace
|
||||||
|
isAuthenticated={isAuthenticated}
|
||||||
|
productImageDataUrls={productImageDataUrls}
|
||||||
|
productImageFiles={productImageFiles}
|
||||||
|
requirement={requirement}
|
||||||
|
platform={platform}
|
||||||
|
aspectRatio={getVideoAspectRatio(ratio)}
|
||||||
|
durationSeconds={videoDuration}
|
||||||
|
resolution={videoQuality === "standard" ? "720P" : "1080P"}
|
||||||
|
onRequestLogin={onRequestLogin}
|
||||||
|
onOpenHistory={onOpenHistory}
|
||||||
|
triggerPlan={planTrigger}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -19210,3 +19210,297 @@ html body #root .ecommerce-standalone.ecommerce-standalone .ecommerce-standalone
|
|||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
object-fit: contain !important;
|
object-fit: contain !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Quick action: 一键视频 card theme ── */
|
||||||
|
html body #root .ecommerce-standalone.ecommerce-standalone .product-clone-page[data-tool="clone"][data-tool="clone"] .ecom-command-quick-board .ecom-command-quick-card--video {
|
||||||
|
--quick-accent: #1073cc;
|
||||||
|
--quick-bg: #eaf5ff;
|
||||||
|
--quick-text: #123454;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── One-click video panel: hot-clone style layout + light video workspace ── */
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page.ecom-hot-video-page .ecom-quick-set-body {
|
||||||
|
grid-template-columns: minmax(386px, 420px) minmax(0, 1fr) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-quick-set-panel-head .ecom-quick-set-page-title {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-one-click-video-duration {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-one-click-video-duration > span {
|
||||||
|
color: #1073cc;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-one-click-video-duration-scale {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: #6b7c88;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-one-click-video-range {
|
||||||
|
width: 100%;
|
||||||
|
accent-color: #1073cc;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-one-click-video-smart {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 52px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid rgba(16, 115, 204, 0.14);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8fbfc;
|
||||||
|
color: #162535;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-one-click-video-smart.is-on {
|
||||||
|
border-color: rgba(16, 115, 204, 0.34);
|
||||||
|
background: linear-gradient(180deg, #edf8ff, #f8fdff);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-one-click-video-smart > span {
|
||||||
|
display: grid;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-one-click-video-smart strong {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-one-click-video-smart em {
|
||||||
|
color: #6b7c88;
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-one-click-video-smart i {
|
||||||
|
width: 40px;
|
||||||
|
height: 22px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #d0dbe3;
|
||||||
|
position: relative;
|
||||||
|
transition: background 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-one-click-video-smart.is-on i {
|
||||||
|
background: #1073cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-one-click-video-smart i::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 2px;
|
||||||
|
top: 2px;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #fff;
|
||||||
|
transition: transform 160ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-one-click-video-smart.is-on i::after {
|
||||||
|
transform: translateX(18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light-themed video workspace overrides */
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page.ecom-hot-video-page .ecom-video-workspace {
|
||||||
|
color: #162535;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-flowbar.ecom-video-preview-head {
|
||||||
|
background: #ffffff;
|
||||||
|
border-bottom-color: rgba(16, 115, 204, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-preview-copy h1 {
|
||||||
|
color: #162535;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-preview-copy p {
|
||||||
|
color: #6b7c88;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-preview-copy p span {
|
||||||
|
color: #1073cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-step-dot {
|
||||||
|
background: rgba(16, 115, 204, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-step-dot.is-done {
|
||||||
|
background: #1073cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-step-dot.is-active {
|
||||||
|
background: #1ebddb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-flowbar__stage-label {
|
||||||
|
color: #1073cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-flowbar__error {
|
||||||
|
color: #e03131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-flowbar__zoom button {
|
||||||
|
border: 1px solid rgba(16, 115, 204, 0.1) !important;
|
||||||
|
background: #edf8ff !important;
|
||||||
|
color: #1073cc !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-flow-action {
|
||||||
|
border-color: rgba(16, 115, 204, 0.2);
|
||||||
|
background: #edf8ff;
|
||||||
|
color: #1073cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-flow-action--ghost {
|
||||||
|
background: #f8fbfc;
|
||||||
|
color: #6b7c88;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-flow-action--danger {
|
||||||
|
border-color: rgba(224, 49, 49, 0.3);
|
||||||
|
background: #fff0f0;
|
||||||
|
color: #e03131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-flow-canvas {
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-empty {
|
||||||
|
color: #6b7c88;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-flow-dock button {
|
||||||
|
border-color: rgba(16, 115, 204, 0.2);
|
||||||
|
background: #edf8ff;
|
||||||
|
color: #1073cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-flow-notice {
|
||||||
|
border-color: rgba(16, 115, 204, 0.2);
|
||||||
|
background: #ffffff;
|
||||||
|
color: #1073cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Light tree nodes */
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node {
|
||||||
|
border-color: rgba(16, 115, 204, 0.16);
|
||||||
|
background: #f7f9fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node--source {
|
||||||
|
border-color: rgba(16, 115, 204, 0.2);
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node--text {
|
||||||
|
border-color: rgba(16, 115, 204, 0.14);
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node--text.is-completed {
|
||||||
|
border-color: rgba(16, 115, 204, 0.34);
|
||||||
|
background: #edf8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node--text.is-active {
|
||||||
|
border-color: #1ebddb;
|
||||||
|
background: #f0fdff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node__title {
|
||||||
|
color: #162535;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node__desc {
|
||||||
|
color: #6b7c88;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node__label {
|
||||||
|
color: #6b7c88;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node--image,
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node--video {
|
||||||
|
border-color: rgba(16, 115, 204, 0.16);
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node--image.is-completed,
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node--video.is-completed {
|
||||||
|
border-color: rgba(16, 115, 204, 0.34);
|
||||||
|
background: #edf8ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node--image.is-active,
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node--video.is-active {
|
||||||
|
border-color: #1ebddb;
|
||||||
|
background: #f0fdff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node--video.is-failed {
|
||||||
|
border-color: rgba(224, 49, 49, 0.4);
|
||||||
|
background: #fff0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node__placeholder {
|
||||||
|
background: #edf1f4;
|
||||||
|
color: #9badb9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree-node__tag {
|
||||||
|
border-color: rgba(16, 115, 204, 0.12);
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
color: #1073cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree__arrow {
|
||||||
|
color: #9badb9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree__trunk-line,
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree__branches-line::before,
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree__branch-tap::before {
|
||||||
|
background: rgba(16, 115, 204, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree__trunk-line::after,
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page .ecom-video-tree__branch-tap::after {
|
||||||
|
background: linear-gradient(90deg, transparent, #1ebddb, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1280px) {
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page.ecom-hot-video-page .ecom-quick-set-body {
|
||||||
|
grid-template-columns: minmax(330px, 370px) minmax(0, 1fr) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.ecommerce-standalone .ecom-one-click-video-page.ecom-hot-video-page .ecom-quick-set-body {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
grid-template-rows: auto minmax(0, 1fr) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user