优化住流程和添加复刻视频页面
This commit is contained in:
@@ -31,6 +31,7 @@ const iconPaths: Record<string, string> = {
|
||||
film: '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 3v18"/><path d="M17 3v18"/><path d="M3 7.5h4"/><path d="M3 16.5h4"/><path d="M17 7.5h4"/><path d="M17 16.5h4"/><path d="M3 12h4"/><path d="M17 12h4"/>',
|
||||
wand: '<path d="m21.64 3-1.28 1.28a5.5 5.5 0 0 0-7.78 7.78l-8.5 8.5a2.12 2.12 0 0 0 3 3l8.5-8.5a5.5 5.5 0 0 0 7.78-7.78Z"/><path d="m14 7 3 3"/>',
|
||||
scan: '<path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><path d="M7 12h10"/>',
|
||||
replace: '<path d="M14 4a2 2 0 0 1 2-2"/><path d="M16 10a2 2 0 0 1-2-2"/><path d="M20 2a2 2 0 0 1 2 2"/><path d="M22 8a2 2 0 0 1-2 2"/><path d="m3 7 3 3 3-3"/><path d="M6 10V5a3 3 0 0 1 3-3h1"/><rect x="2" y="14" width="8" height="8" rx="2"/>',
|
||||
swap: '<path d="M8 3 4 7l4 4"/><path d="M4 7h16"/><path d="m16 21 4-4-4-4"/><path d="M20 17H4"/>',
|
||||
arrowRight: '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>'
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ const SHELL_COMMANDS: Command[] = [
|
||||
{ id: "free-create", group: "导航", label: "自由创作", sub: "AI 视频生成 · 全能参考 / 首尾帧", page: "freeCreate", icon: "film", key: "F" },
|
||||
{ id: "quick-create", group: "导航", label: "极速成片", sub: "上传商品图片,自动完成整条视频", page: "quickCreate", icon: "wand", key: "Q" },
|
||||
{ id: "video-remix", group: "导航", label: "提炼提示词", sub: "上传参考视频,提炼可编辑提示词", page: "videoRemix", icon: "scan", key: "R" },
|
||||
{ id: "video-replace", group: "导航", label: "视频复刻", sub: "保留参考视频的人物、场景与节奏,复刻出自己的带货内容", page: "videoReplace", icon: "replace", key: "E" },
|
||||
{ id: "library", group: "导航", label: "成品库", sub: "素材、人物、场景、成片统一管理", page: "library", icon: "folder", key: "A" },
|
||||
{ id: "team", group: "导航", label: "团队", sub: "成员、权限、额度、协作记录", page: "team", icon: "users" },
|
||||
{ id: "account", group: "导航", label: "账单库", sub: "余额、充值、账单流水", page: "account", icon: "creditCard" },
|
||||
@@ -272,7 +273,7 @@ export function topModuleForPage(page: Page): TopModule | null {
|
||||
|| page === "modelPhotoDemoB"
|
||||
|| page === "platformCover"
|
||||
) return "image";
|
||||
if (page === "projects" || page === "projectWizard" || page === "quickCreate" || page === "pipeline" || page === "freeCreate" || page === "videoRemix") return "video";
|
||||
if (page === "projects" || page === "projectWizard" || page === "quickCreate" || page === "pipeline" || page === "freeCreate" || page === "videoRemix" || page === "videoReplace") return "video";
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -404,6 +405,7 @@ const PAGE_TO_NAV: Partial<Record<Page, Page>> = {
|
||||
platformCover: "assetFactory",
|
||||
freeCreate: "freeCreate",
|
||||
videoRemix: "projects",
|
||||
videoReplace: "projects",
|
||||
library: "library",
|
||||
team: "team",
|
||||
account: "account",
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { ChangeEvent, DragEvent } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { ArrowLeft, Check, ChevronDown, Image as ImageIcon, Plus, ShieldCheck, Trash2, Upload, X } from "lucide-react";
|
||||
import { ArrowLeft, Check, ChevronDown, ChevronRight, Image as ImageIcon, Plus, ShieldCheck, Trash2, Upload, X } from "lucide-react";
|
||||
import { api } from "../api";
|
||||
import { useBodyScrollLock, ConfirmModal } from "./overlays";
|
||||
import type { Product } from "../types";
|
||||
import {
|
||||
BUSINESS_TYPES,
|
||||
BUSINESS_TYPE_KEYS,
|
||||
PC_CAT_MORE,
|
||||
PC_CAT_MORE_LABEL,
|
||||
PC_CAT_OPTIONS,
|
||||
PC_CAT_PRIMARY,
|
||||
catOptionsFor,
|
||||
type BusinessType,
|
||||
} from "../product-business";
|
||||
@@ -20,7 +23,6 @@ export type ProductCreatePayload = {
|
||||
title: string;
|
||||
business_type?: BusinessType;
|
||||
category: string;
|
||||
target_audience?: string;
|
||||
selling_points?: Array<{ title: string; detail: string; sort_order: number }>;
|
||||
cover_asset?: string;
|
||||
images?: Array<{ asset: string; sort_order: number; is_primary?: boolean }>;
|
||||
@@ -51,7 +53,6 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
const [title, setTitle] = useState("");
|
||||
const [businessType, setBusinessType] = useState<BusinessType>("ecommerce");
|
||||
const [category, setCategory] = useState("");
|
||||
const [target, setTarget] = useState("");
|
||||
const [bullets, setBullets] = useState<string[]>([""]);
|
||||
const [images, setImages] = useState<PfImage[]>([]);
|
||||
const [dragOver, setDragOver] = useState(false);
|
||||
@@ -60,6 +61,7 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [galleryOpen, setGalleryOpen] = useState(false);
|
||||
const [openSelect, setOpenSelect] = useState<"" | "type" | "cat">("");
|
||||
const [catMoreOpen, setCatMoreOpen] = useState(false);
|
||||
const [toast, setToast] = useState<{ title: string; sub: string } | null>(null);
|
||||
const [confirmExit, setConfirmExit] = useState(false);
|
||||
const imgInputRef = useRef<HTMLInputElement | null>(null);
|
||||
@@ -84,7 +86,6 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
setTitle("");
|
||||
setBusinessType("ecommerce");
|
||||
setCategory("");
|
||||
setTarget("");
|
||||
setBullets([""]);
|
||||
setImages((list) => { list.forEach((item) => URL.revokeObjectURL(item.url)); return []; });
|
||||
setDragOver(false);
|
||||
@@ -92,6 +93,7 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
setCategoryError(false);
|
||||
setGalleryOpen(false);
|
||||
setOpenSelect("");
|
||||
setCatMoreOpen(false);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -114,7 +116,7 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
}, [openSelect]);
|
||||
|
||||
function isDirty() {
|
||||
return Boolean(title.trim() || businessType !== "ecommerce" || category || target.trim() || bullets.some((b) => b.trim()) || images.length);
|
||||
return Boolean(title.trim() || businessType !== "ecommerce" || category || bullets.some((b) => b.trim()) || images.length);
|
||||
}
|
||||
|
||||
function changeBusinessType(next: BusinessType) {
|
||||
@@ -227,7 +229,6 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
title: name,
|
||||
business_type: businessType,
|
||||
category: category || activeCatOptions[0],
|
||||
target_audience: target.trim() || undefined,
|
||||
selling_points: finalBullets.map((item, index) => ({ title: item, detail: item, sort_order: index })),
|
||||
...(images[0]?.assetId
|
||||
? {
|
||||
@@ -299,31 +300,57 @@ export function ProductCreateDrawer({ open, close, onCreate, onCreated, catOptio
|
||||
|
||||
<div className="pcd-field">
|
||||
<span className="pcd-label"><span>选择品类 <span className="pcd-req">*</span></span><small>必选</small></span>
|
||||
<div className={`pcd-select${openSelect === "cat" ? " open" : ""}`}>
|
||||
<button type="button" className={`pcd-select-trigger${categoryError ? " is-error" : ""}`} aria-haspopup="listbox" aria-expanded={openSelect === "cat"} onClick={() => setOpenSelect((c) => (c === "cat" ? "" : "cat"))}>
|
||||
<div className={`pcd-select${openSelect === "cat" ? " open" : ""}${catMoreOpen ? " is-more-open" : ""}`}>
|
||||
<button
|
||||
type="button"
|
||||
className={`pcd-select-trigger${categoryError ? " is-error" : ""}`}
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={openSelect === "cat"}
|
||||
onClick={() => {
|
||||
if (openSelect === "cat") {
|
||||
setOpenSelect("");
|
||||
return;
|
||||
}
|
||||
setCatMoreOpen(PC_CAT_MORE.includes(category));
|
||||
setOpenSelect("cat");
|
||||
}}
|
||||
>
|
||||
<span className={category ? "" : "is-placeholder"}>{category || "请选择商品品类"}</span>
|
||||
<ChevronDown />
|
||||
</button>
|
||||
<div className="pcd-select-menu" role="listbox">
|
||||
{activeCatOptions.map((option) => (
|
||||
{isLocal ? activeCatOptions.map((option) => (
|
||||
<button type="button" className={`pcd-select-option${category === option ? " selected" : ""}`} key={option} onClick={() => { setCategory(option); setCategoryError(false); setOpenSelect(""); }}>
|
||||
{option}
|
||||
</button>
|
||||
))}
|
||||
)) : (
|
||||
<>
|
||||
{PC_CAT_PRIMARY.map((option) => (
|
||||
<button type="button" className={`pcd-select-option${category === option ? " selected" : ""}`} key={option} onClick={() => { setCategory(option); setCategoryError(false); setOpenSelect(""); }}>
|
||||
{option}
|
||||
</button>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className={`pcd-select-option pcd-select-more-toggle${catMoreOpen ? " is-open" : ""}`}
|
||||
aria-expanded={catMoreOpen}
|
||||
onClick={() => setCatMoreOpen((open) => !open)}
|
||||
>
|
||||
<span>{PC_CAT_MORE_LABEL}</span>
|
||||
<ChevronRight />
|
||||
</button>
|
||||
{catMoreOpen ? PC_CAT_MORE.map((option) => (
|
||||
<button type="button" className={`pcd-select-option pcd-select-more-item${category === option ? " selected" : ""}`} key={option} onClick={() => { setCategory(option); setCategoryError(false); setOpenSelect(""); }}>
|
||||
{option}
|
||||
</button>
|
||||
)) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label className="pcd-field">
|
||||
<span className="pcd-label"><span>目标人群</span><small>选填</small></span>
|
||||
<input
|
||||
value={target}
|
||||
onChange={(event) => setTarget(event.target.value)}
|
||||
placeholder={isLocal ? "例如:附近上班族、带娃家庭、周末聚餐" : "例如:22–32 岁女性、敏感肌、通勤人群"}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<section className="pcd-block" aria-labelledby="productImageTitle">
|
||||
<div className="pcd-block-head">
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user