diff --git a/core/frontend/src/App.tsx b/core/frontend/src/App.tsx index f978a04..8b7f4f3 100644 --- a/core/frontend/src/App.tsx +++ b/core/frontend/src/App.tsx @@ -660,7 +660,7 @@ export function App() { ); case "library": - return action(() => api.uploadAsset(formData), "资产已上传")} onDelete={(id) => action(() => api.deleteAsset(id), "资产已删除")} />; + return action(() => api.uploadAsset(formData), "资产已上传")} onDelete={(id) => action(() => api.deleteAsset(id), "资产已删除")} />; case "account": return ( >("/api/assets/"); }, + // 服务端分页+过滤的资产列表(各页按需懒加载,不再前端取全量再切片)。 + assetsPage(params: { + tab?: string; category?: string; source?: string; asset_type?: string; + product?: string; q?: string; ordering?: string; page?: number; pageSize?: number; + meta?: Record; + } = {}) { + const qs = new URLSearchParams(); + const { meta, pageSize, ...rest } = params; + for (const [k, v] of Object.entries(rest)) if (v !== undefined && v !== "" && v !== null) qs.set(k, String(v)); + if (pageSize) qs.set("page_size", String(pageSize)); + if (meta) for (const [k, v] of Object.entries(meta)) if (v) qs.set(`m_${k}`, v); + return request>(`/api/assets/?${qs.toString()}`); + }, + // 资产库各 tab 计数(tab 徽标用,不必取全量) + assetSummary() { + return request>("/api/assets/summary/"); + }, + // 某 tab 下真实存在的筛选项(来源/类型/指定 metadata 键取值),供下拉「只列真有的」 + assetFacets(tab?: string, metaKeys: string[] = []) { + const qs = new URLSearchParams(); + if (tab) qs.set("tab", tab); + if (metaKeys.length) qs.set("meta_keys", metaKeys.join(",")); + return request<{ sources: string[]; kinds: string[]; metadata: Record }>(`/api/assets/facets/?${qs.toString()}`); + }, // 经后端同源代理取资产原始文件(TOS 未配 CORS,浏览器抽帧/解码必须同源) async fetchAssetBlob(id: string): Promise { const token = getToken(); diff --git a/core/frontend/src/routes/library.tsx b/core/frontend/src/routes/library.tsx index 4a697ed..a7da2f0 100644 --- a/core/frontend/src/routes/library.tsx +++ b/core/frontend/src/routes/library.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; import type { FormEvent } from "react"; +import { api } from "../api"; import type { Asset } from "../types"; import { ConfirmModal, Drawer, MediaLightbox } from "../components/overlays"; import { Pager } from "../components/pager"; @@ -53,7 +54,7 @@ function assetTab(asset: Asset): LibTab { } } -export function LibraryPage({ assets, onUpload, onDelete }: { assets: Asset[]; onUpload: (formData: FormData) => Promise | void; onDelete?: (id: string) => Promise | void }) { +export function LibraryPage({ onUpload, onDelete }: { onUpload: (formData: FormData) => Promise | void; onDelete?: (id: string) => Promise | void }) { const [tab, setTab] = useState("people"); const [query, setQuery] = useState(""); const [drawer, setDrawer] = useState(false); @@ -75,12 +76,62 @@ export function LibraryPage({ assets, onUpload, onDelete }: { assets: Asset[]; o return () => document.body.classList.remove("edit-mode"); }, [editMode]); - // 分页:每页 10 个,切 tab / 改筛选 / 改排序回到第 1 页(数据多时全量渲染卡顿,视频卡每张挂一个