perf(platform): 短剧创作工作台 - 拆分长篇项目接口并优化章节与素材分页

This commit is contained in:
ddCat
2026-08-30 01:32:33 +08:00
parent 40fd0c54a4
commit d89f170a08
22 changed files with 1497 additions and 222 deletions

View File

@@ -6,6 +6,7 @@ import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
import { AssetMediaPreview } from "@/components/asset-media-preview";
import { AssetLibraryCard } from "@/components/assets/asset-library-card";
import { CachedResourceImage } from "@/components/cached-resource-image";
import { PaginationBar } from "@/components/layout/workspace-page";
import { cn } from "@/lib/utils";
import type { ExternalAssetPickerReference } from "@/lib/plugins/plugin-types";
import type { Asset } from "@/stores/use-asset-store";
@@ -47,6 +48,8 @@ type Props = {
emptyTitle?: string;
emptyDescription?: string;
footerNote?: string;
loading?: boolean;
pagination?: { current: number; pageSize: number; total: number; onChange: (page: number, pageSize: number) => void };
folderActionLabel?: string;
folderActionSource?: "local" | "all";
upload?: {
@@ -79,6 +82,8 @@ export function AssetLibraryPickerModal({
emptyTitle = "这个分类还没有素材",
emptyDescription = "换个分类后再试。",
footerNote,
loading = false,
pagination,
folderActionLabel = "将文件夹放到画布",
folderActionSource = "all",
upload,
@@ -123,10 +128,10 @@ export function AssetLibraryPickerModal({
return !query || [item.title, item.searchText || "", item.description || ""].join(" ").toLowerCase().includes(query);
});
}, [category, folderId, keyword, sourceItems]);
const selectedIds = useMemo(
() => allItems.filter((item) => !item.disabledReason && selected.has(item.id)).map((item) => item.id),
[allItems, selected],
);
const selectedIds = useMemo(() => Array.from(selected).filter((id) => {
const item = allItems.find((entry) => entry.id === id);
return !item?.disabledReason;
}), [allItems, selected]);
useEffect(() => {
if (!open) return;
@@ -272,7 +277,7 @@ export function AssetLibraryPickerModal({
</div>
</div>
<label className="asset-picker-search"><Search aria-hidden /><input value={keyword} onChange={(event) => setKeyword(event.target.value)} placeholder="搜索素材名称或标签" aria-label="搜索素材" /></label>
<span className="asset-picker-count">已选 {selectedIds.length} · {visibleItems.length} 个素材</span>
<span className="asset-picker-count">已选 {selectedIds.length} · {pagination ? pagination.total : visibleItems.length} 个素材</span>
</header>
<div className="asset-picker-body">
<nav className="asset-picker-categories" aria-label="素材分类">
@@ -285,12 +290,15 @@ export function AssetLibraryPickerModal({
</nav>
<div className="asset-picker-grid-wrap">
<div className="asset-picker-grid">
{visibleItems.length ? visibleItems.map((item) => (
{loading ? (
<div className="asset-picker-empty"><LoaderCircle className="animate-spin" /><strong>正在读取素材</strong><span>素材会按页加载,不会一次下载整个项目库。</span></div>
) : visibleItems.length ? visibleItems.map((item) => (
<PickerCard key={item.id} item={item} selected={selected.has(item.id)} onToggle={() => toggle(item)} />
)) : (
<div className="asset-picker-empty"><FolderOpen /><strong>{emptyTitle}</strong><span>{activeUpload ? "换个分类,或从底部上传一份新素材。" : emptyDescription}</span></div>
)}
</div>
{pagination ? <PaginationBar alwaysShow current={pagination.current} pageSize={pagination.pageSize} total={pagination.total} itemLabel="项" pageSizeOptions={[20, 40, 80]} onChange={pagination.onChange} /> : null}
</div>
</div>
<footer className={cn("asset-picker-footer", !activeUpload && "is-compact")}>

View File

@@ -106,6 +106,7 @@ export function PaginationBar({
onChange,
pageSizeOptions = [20, 50, 100],
alwaysShow = false,
itemLabel = "条",
}: {
current: number;
pageSize: number;
@@ -113,6 +114,7 @@ export function PaginationBar({
onChange: (page: number, pageSize: number) => void;
pageSizeOptions?: number[];
alwaysShow?: boolean;
itemLabel?: string;
}) {
if (!alwaysShow && total <= pageSize && current === 1) return null;
const pages = Math.max(1, Math.ceil(total / pageSize));
@@ -121,8 +123,8 @@ export function PaginationBar({
const items = pageItems(current, pages);
return (
<div className="app-pagination-bar admin-pagination-bar mt-4 flex min-h-10 min-w-0 items-center justify-end gap-2 px-2 py-1.5">
<span className="admin-pagination-total">{total === 0 ? "共 0 条" : `${start}-${end} / 共 ${total} 条`}</span>
<Select size="small" value={pageSize} className="app-pagination-size" options={pageSizeOptions.map((size) => ({ value: size, label: `${size} 条/页` }))} onChange={(value) => onChange(1, Number(value))} />
<span className="admin-pagination-total">{total === 0 ? `共 0 ${itemLabel}` : `${start}-${end} / 共 ${total} ${itemLabel}`}</span>
<Select size="small" value={pageSize} className="app-pagination-size" options={pageSizeOptions.map((size) => ({ value: size, label: `${size} ${itemLabel}/页` }))} onChange={(value) => onChange(1, Number(value))} />
<div className="app-pagination-pages" role="navigation" aria-label="分页">
<button type="button" className="app-pagination-btn app-pagination-prev" disabled={current <= 1} aria-label="上一页" onClick={() => onChange(current - 1, pageSize)}>
<ChevronLeft className="size-4" />