feat: bootstrap auto virtual tryon admin frontend

This commit is contained in:
afei A
2026-03-27 23:38:50 +08:00
commit 98c6b741d6
119 changed files with 19046 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
"use client";
import { useEffect, useState } from "react";
import { Card, CardContent, CardDescription, CardEyebrow, CardHeader, CardTitle } from "@/components/ui/card";
import { EmptyState } from "@/components/ui/empty-state";
import { PageHeader } from "@/components/ui/page-header";
import type { LibraryItemVM, LibraryType } from "@/lib/types/view-models";
type LibraryPageProps = {
isLoading?: boolean;
items: LibraryItemVM[];
libraryType: LibraryType;
message?: string;
};
type LibraryEnvelope = {
data?: {
items?: LibraryItemVM[];
};
message?: string;
};
const LIBRARY_META: Record<
LibraryType,
{ title: string; description: string; eyebrow: string }
> = {
models: {
title: "模特库",
description: "继续复用提单页所需的 mock 模特数据,同时保留正式资源库的结构和标签体系。",
eyebrow: "Model library",
},
scenes: {
title: "场景库",
description: "场景库先用 mock 占位数据承接,等真实后台资源列表可用后再切换数据源。",
eyebrow: "Scene library",
},
garments: {
title: "服装库",
description: "服装资源暂时继续走 mock 资产,但页面本身已经按正式资源库组织。",
eyebrow: "Garment library",
},
};
const TITLE_MESSAGE = "当前资源库仍使用 mock 数据";
const DEFAULT_MESSAGE = "当前只保留页面结构和 mock 资源项,真实资源查询能力将在后端支持后接入。";
export function LibraryPage({
isLoading = false,
items,
libraryType,
message = DEFAULT_MESSAGE,
}: LibraryPageProps) {
const meta = LIBRARY_META[libraryType];
return (
<section className="space-y-8">
<PageHeader
eyebrow={meta.eyebrow}
title={meta.title}
description={meta.description}
meta="正式占位模块"
/>
<div className="rounded-[28px] border border-[rgba(145,104,46,0.2)] bg-[rgba(202,164,97,0.12)] px-6 py-5">
<p className="text-lg font-semibold tracking-[-0.02em] text-[#7a5323]">
{TITLE_MESSAGE}
</p>
<p className="mt-2 text-sm leading-7 text-[#7a5323]">{message}</p>
</div>
<Card>
<CardHeader>
<div className="space-y-2">
<CardEyebrow>Library inventory</CardEyebrow>
<div className="space-y-1">
<CardTitle>{meta.title}</CardTitle>
<CardDescription>
BFF
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
{isLoading ? (
<div className="rounded-[24px] border border-dashed border-[var(--border-strong)] bg-[var(--surface-muted)] px-5 py-6 text-sm text-[var(--ink-muted)]">
</div>
) : null}
{!isLoading && items.length ? (
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
{items.map((item) => (
<div
key={item.id}
className="rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-4 py-4"
>
<div className="space-y-3">
<div>
<p className="text-sm font-semibold text-[var(--ink-strong)]">
{item.name}
</p>
<p className="mt-1 text-sm leading-6 text-[var(--ink-muted)]">
{item.description}
</p>
</div>
<code className="block rounded-[18px] bg-[rgba(74,64,53,0.08)] px-3 py-3 text-xs leading-6 text-[var(--ink-muted)]">
{item.previewUri}
</code>
<div className="flex flex-wrap gap-2">
{item.tags.map((tag) => (
<span
key={tag}
className="rounded-full bg-[rgba(110,98,84,0.08)] px-2.5 py-1 font-[var(--font-mono)] text-[11px] uppercase tracking-[0.18em] text-[var(--ink-muted)]"
>
{tag}
</span>
))}
</div>
</div>
</div>
))}
</div>
) : null}
{!isLoading && !items.length ? (
<EmptyState
eyebrow="Library empty"
title="暂无资源条目"
description="当前库还没有占位数据,等后端或运营资源列表准备好后再补齐。"
/>
) : null}
</CardContent>
</Card>
</section>
);
}
export function LibraryPageScreen({ libraryType }: { libraryType: LibraryType }) {
const [items, setItems] = useState<LibraryItemVM[]>([]);
const [message, setMessage] = useState(
"当前资源库仍使用 mock 数据,真实资源查询能力将在后端支持后接入。",
);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
let active = true;
async function loadLibrary() {
setIsLoading(true);
try {
const response = await fetch(`/api/libraries/${libraryType}`);
const payload = (await response.json()) as LibraryEnvelope;
if (!active) {
return;
}
setItems(payload.data?.items ?? []);
setMessage(
payload.message ??
"当前资源库仍使用 mock 数据,真实资源查询能力将在后端支持后接入。",
);
} catch {
if (!active) {
return;
}
setItems([]);
setMessage("资源库数据加载失败,请稍后重试。");
} finally {
if (active) {
setIsLoading(false);
}
}
}
void loadLibrary();
return () => {
active = false;
};
}, [libraryType]);
return (
<LibraryPage
isLoading={isLoading}
items={items}
libraryType={libraryType}
message={message}
/>
);
}