"use client"; import Link from "next/link"; import { useEffect, useState } from "react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { CardEyebrow } from "@/components/ui/card"; import { EmptyState } from "@/components/ui/empty-state"; import { PageHeader } from "@/components/ui/page-header"; import { LibraryEditModal } from "@/features/libraries/components/library-edit-modal"; import { LibraryUploadModal } from "@/features/libraries/components/library-upload-modal"; import { archiveLibraryResource } from "@/features/libraries/manage-resource"; 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; }; type LibraryMeta = { description: string; singularLabel: string; tabLabel: string; }; const LIBRARY_META: Record = { models: { description: "按模特资源管理上传、封面和提单联动素材。", singularLabel: "模特", tabLabel: "模特", }, scenes: { description: "按场景资源管理上传、封面和提单联动素材。", singularLabel: "场景", tabLabel: "场景", }, garments: { description: "按服装资源管理上传、封面和提单联动素材。", singularLabel: "服装", tabLabel: "服装", }, }; const LIBRARY_SECTIONS: Array<{ href: string; label: string; libraryType: LibraryType; }> = [ { href: "/libraries/models", label: "模特", libraryType: "models" }, { href: "/libraries/scenes", label: "场景", libraryType: "scenes" }, { href: "/libraries/garments", label: "服装", libraryType: "garments" }, ]; const STATE_TITLE = "资源库状态"; const DEFAULT_MESSAGE = "资源库当前显示真实后端数据。"; function joinClasses(...values: Array) { return values.filter(Boolean).join(" "); } function getResourceRequestId(item: LibraryItemVM) { return typeof item.backendId === "number" ? String(item.backendId) : item.id; } export function LibraryPage({ isLoading = false, items, libraryType, message = DEFAULT_MESSAGE, }: LibraryPageProps) { const meta = LIBRARY_META[libraryType]; const [archivingItem, setArchivingItem] = useState(null); const [editingItem, setEditingItem] = useState(null); const [isArchiving, setIsArchiving] = useState(false); const [isUploadOpen, setIsUploadOpen] = useState(false); const [visibleItems, setVisibleItems] = useState(items); const [statusMessage, setStatusMessage] = useState(message); useEffect(() => { setVisibleItems(items); }, [items]); useEffect(() => { setStatusMessage(message); }, [message]); function handleUploaded(item: LibraryItemVM) { setVisibleItems((current) => [ item, ...current.filter((candidate) => candidate.id !== item.id), ]); setStatusMessage("资源上传成功,已写入正式资源库。"); setIsUploadOpen(false); } function handleSaved(item: LibraryItemVM) { setVisibleItems((current) => current.map((candidate) => (candidate.id === item.id ? item : candidate)), ); setStatusMessage("资源更新成功,封面与元数据已同步。"); setEditingItem(null); } async function handleArchive(item: LibraryItemVM) { setIsArchiving(true); try { const archivedId = await archiveLibraryResource({ libraryType, resourceId: getResourceRequestId(item), }); setVisibleItems((current) => current.filter((candidate) => candidate.id !== archivedId), ); setStatusMessage(`资源「${item.name}」已移入归档。`); setArchivingItem(null); } catch (archiveError) { setStatusMessage( archiveError instanceof Error ? archiveError.message : "资源删除失败,请稍后重试。", ); } finally { setIsArchiving(false); } } return (

{STATE_TITLE}

{statusMessage}

{meta.tabLabel} upload

上传{meta.singularLabel}资源

当前页签决定资源类型,只需要上传原图,缩略图会自动生成。

{isLoading ? (
正在加载资源库数据…
) : null} {!isLoading && !visibleItems.length ? (
) : null} {!isLoading ? visibleItems.map((item) => (
{item.previewUri ? ( // previewUri may come from mock:// fixtures during tests, so keep a plain img here. // eslint-disable-next-line @next/next/no-img-element {`${item.name} ) : null} {/*
{meta.singularLabel}预览
*/}

{item.name}

{item.description}

{item.tags.length ? (
{item.tags.map((tag) => ( {tag} ))}
) : null} {/* {item.previewUri} */}
)) : null}
setIsUploadOpen(false)} onUploaded={handleUploaded} /> setEditingItem(null)} onSaved={handleSaved} /> { if (!open && !isArchiving) { setArchivingItem(null); } }} > {`确认删除${meta.singularLabel}资源`} {archivingItem ? `删除后,这条资源会被移入归档,不再出现在当前列表中。当前资源:${archivingItem.name}` : "删除后,这条资源会被移入归档,不再出现在当前列表中。"} 取消 { event.preventDefault(); if (!archivingItem || isArchiving) { return; } void handleArchive(archivingItem); }} > {isArchiving ? "删除中..." : "确认删除"}
); } export function LibraryPageScreen({ libraryType }: { libraryType: LibraryType }) { const [items, setItems] = useState([]); const [message, setMessage] = useState(DEFAULT_MESSAGE); 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 ?? DEFAULT_MESSAGE); } catch { if (!active) { return; } setItems([]); setMessage("资源库数据加载失败,请稍后重试。"); } finally { if (active) { setIsLoading(false); } } } void loadLibrary(); return () => { active = false; }; }, [libraryType]); return ( ); }