fix: 商品库视图持久化漏修 + 商品三视图跨模块回填
- ZWQ#row22(补): 商品库网格/列表用 useViewMode 持久化(上轮只接了视频项目、 误判商品库无切换跳过,实际 products.tsx 有切换但用裸 useState 不持久化) - 商品三视图不同步(测试新增): 商品详情三视图面板 triVersions 从不回填已有资产, 只在本次会话点生成才有数据 → 视频项目基础资产阶段生成/采用的商品三视图(落库 tri_view), 回商品库打开面板显示「尚未生成」。新增 useEffect 从 productAssets 回填已有 tri_view (判定对齐后端 is_triview),并把采用为封面的那版标记已采用 构建: tsc --noEmit=0, npm run build=0 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { ReviewBadge, type ReviewStatus } from "../components/review-badge";
|
|||||||
import { ProductCreateDrawer, PC_CAT_OPTIONS } from "../components/product-create-drawer";
|
import { ProductCreateDrawer, PC_CAT_OPTIONS } from "../components/product-create-drawer";
|
||||||
import { Pager } from "../components/pager";
|
import { Pager } from "../components/pager";
|
||||||
import { SkeletonGrid } from "../components/loading";
|
import { SkeletonGrid } from "../components/loading";
|
||||||
|
import { useViewMode } from "../components/use-view-mode";
|
||||||
import { api } from "../api";
|
import { api } from "../api";
|
||||||
|
|
||||||
const PROD_PAGE_SIZE = 10;
|
const PROD_PAGE_SIZE = 10;
|
||||||
@@ -107,7 +108,8 @@ export function ProductsPage({ products, projects = [], loading = false, navigat
|
|||||||
const [catFilter, setCatFilter] = useState<Set<string>>(new Set());
|
const [catFilter, setCatFilter] = useState<Set<string>>(new Set());
|
||||||
const [dateFilter, setDateFilter] = useState<"all" | "7" | "30" | "90">("all");
|
const [dateFilter, setDateFilter] = useState<"all" | "7" | "30" | "90">("all");
|
||||||
// 视图切换:网格 / 列表(.view-tog 黑激活;列表态卡片横排)
|
// 视图切换:网格 / 列表(.view-tog 黑激活;列表态卡片横排)
|
||||||
const [view, setView] = useState<"grid" | "list">("grid");
|
// ZWQ#row22:持久化 → 切走再切回商品库仍保持用户上次选的网格/列表
|
||||||
|
const [view, setView] = useViewMode<"grid" | "list">("view:products", "grid");
|
||||||
const toggleCat = (cat: string) => setCatFilter((prev) => {
|
const toggleCat = (cat: string) => setCatFilter((prev) => {
|
||||||
const next = new Set(prev);
|
const next = new Set(prev);
|
||||||
next.has(cat) ? next.delete(cat) : next.add(cat);
|
next.has(cat) ? next.delete(cat) : next.add(cat);
|
||||||
@@ -673,6 +675,32 @@ export function ProductDetailPage({ product, projects, initialTab = "assets", na
|
|||||||
return () => { alive = false; };
|
return () => { alive = false; };
|
||||||
}, [product?.id, assetReload]);
|
}, [product?.id, assetReload]);
|
||||||
|
|
||||||
|
// ZWQ#row24/三视图同步:该商品已存在的三视图资产(在【视频项目】基础资产阶段生成、
|
||||||
|
// 或此前在商品库生成并落库的 tri_view)回填到三视图面板,避免「明明已生成却显示尚未生成」。
|
||||||
|
// 判定与后端 is_triview 对齐:category=tri_view / metadata.view=three_view / 名字含三视图。
|
||||||
|
useEffect(() => {
|
||||||
|
const triAssets = productAssets.filter(
|
||||||
|
(a) => a.category === "tri_view" || a.asset_type === "tri_view"
|
||||||
|
|| (a.metadata as { view?: string } | undefined)?.view === "three_view"
|
||||||
|
|| (a.name || "").includes("三视图")
|
||||||
|
);
|
||||||
|
if (!triAssets.length) return;
|
||||||
|
const serverVersions: TriVersion[] = triAssets
|
||||||
|
.map((a) => ({ id: a.id, url: a.files?.[0]?.preview_url || "", asset: a, createdAt: a.created_at || "" }))
|
||||||
|
.filter((v) => v.url);
|
||||||
|
if (!serverVersions.length) return;
|
||||||
|
setTriVersions((prev) => {
|
||||||
|
const seen = new Set(prev.map((v) => v.id));
|
||||||
|
const merged = [...prev, ...serverVersions.filter((v) => !seen.has(v.id))];
|
||||||
|
// 最新版本在前(与生成态 prepend 一致)
|
||||||
|
merged.sort((x, y) => (y.createdAt || "").localeCompare(x.createdAt || ""));
|
||||||
|
return merged.length === prev.length ? prev : merged;
|
||||||
|
});
|
||||||
|
// 已被采用为商品封面的那版三视图 → 标记「已采用」
|
||||||
|
const adopted = triAssets.find((a) => a.id === product.cover_asset);
|
||||||
|
if (adopted) setTriAdoptedId((cur) => cur || adopted.id);
|
||||||
|
}, [productAssets, product.cover_asset]);
|
||||||
|
|
||||||
async function onPickProductImage(event: ChangeEvent<HTMLInputElement>) {
|
async function onPickProductImage(event: ChangeEvent<HTMLInputElement>) {
|
||||||
const file = event.target.files?.[0];
|
const file = event.target.files?.[0];
|
||||||
event.target.value = "";
|
event.target.value = "";
|
||||||
|
|||||||
Reference in New Issue
Block a user