diff --git a/web/src/pages/LibrariesPage.tsx b/web/src/pages/LibrariesPage.tsx index b5461a8..7202a52 100644 --- a/web/src/pages/LibrariesPage.tsx +++ b/web/src/pages/LibrariesPage.tsx @@ -13,13 +13,7 @@ type LibraryPreview = { library: Library items: Media[] total: number -} - -type CategorySummary = { - type: string - libraries: number - total: number - items: Media[] + cards: SeriesCard[] } const TYPE_ICONS: Record = { @@ -38,18 +32,9 @@ const TYPE_LABELS: Record = { music: '音乐', } -const TYPE_DESCRIPTIONS: Record = { - movie: '电影 / 动画电影 / 纪录片', - tv: '国产剧 / 欧美剧 / 日韩剧', - anime: '国漫 / 日番 / OVA', - variety: '综艺 / 真人秀 / 晚会', - music: '音乐 / MV / 演唱会', -} - export function LibrariesPage() { const [previews, setPreviews] = useState([]) const [loading, setLoading] = useState(true) - const [activeType, setActiveType] = useState('all') useEffect(() => { let cancelled = false @@ -59,10 +44,11 @@ export function LibrariesPage() { const libs = await libraryAPI.list() const rows = await Promise.all(libs.map(async (library) => { try { - const page = await libraryAPI.listMedia(library.id, 1, 120) - return { library, items: page.items, total: page.total } satisfies LibraryPreview + const page = await libraryAPI.listMedia(library.id, 1, 160) + const cards = latestCards(page.items) + return { library, items: page.items, total: page.total, cards } satisfies LibraryPreview } catch { - return { library, items: [], total: 0 } satisfies LibraryPreview + return { library, items: [], total: 0, cards: [] } satisfies LibraryPreview } })) if (!cancelled) setPreviews(rows) @@ -74,33 +60,7 @@ export function LibrariesPage() { return () => { cancelled = true } }, []) - const categories = useMemo(() => { - const map = new Map() - for (const preview of previews) { - const type = preview.library.type || 'other' - const current = map.get(type) ?? { type, libraries: 0, total: 0, items: [] } - current.libraries += 1 - current.total += preview.total - current.items.push(...preview.items) - map.set(type, current) - } - return Array.from(map.values()).sort((a, b) => b.total - a.total) - }, [previews]) - - const filtered = activeType === 'all' - ? previews - : previews.filter((preview) => preview.library.type === activeType) - - const activeItems = useMemo(() => filtered.flatMap((preview) => preview.items), [filtered]) - const latestCards = useMemo(() => { - return groupSeries(activeItems) - .sort((a, b) => mediaTime(b.rep) - mediaTime(a.rep)) - .slice(0, 9) - }, [activeItems]) - - const activeLabel = activeType === 'all' - ? '全部媒体' - : TYPE_LABELS[activeType] ?? activeType + const total = useMemo(() => previews.reduce((sum, preview) => sum + preview.total, 0), [previews]) if (loading) { return

媒体库加载中…

@@ -111,7 +71,9 @@ export function LibrariesPage() {

媒体库

-

先看每类最新入库,再通过目录入口进入完整文件夹。

+

+ 共 {previews.length} 个目录 · {total.toLocaleString()} 个条目。每个目录直接展示最新入库内容。 +

管理媒体库 @@ -126,134 +88,46 @@ export function LibrariesPage() {
) : ( <> -
-
- } - label="全部媒体" - description="统一浏览" - total={previews.reduce((sum, preview) => sum + preview.total, 0)} - libraries={previews.length} - onClick={() => setActiveType('all')} - /> - {categories.map((category) => ( - } - label={TYPE_LABELS[category.type] ?? category.type} - description={TYPE_DESCRIPTIONS[category.type] ?? '自定义分类'} - total={category.total} - libraries={category.libraries} - onClick={() => setActiveType(category.type)} - /> - ))} -
-
- -
-
-
-

{activeLabel} · 最近更新

-

显示前 9 部最新入库或更新的影片 / 剧集合集。

-
- {filtered.length === 1 && ( - - 浏览全部 - - - )} -
- - {latestCards.length > 0 ? ( -
- {latestCards.map((card) => ( - - ))} -
- ) : ( -
- 当前分类还没有入库内容,扫描媒体库后会显示在这里。 -
- )} -
-
-

目录入口

-

按你添加的媒体库文件夹进入,适合继续按文件分类查找。

+

媒体库入口

+

按目录进入完整媒体库;下方每个目录也会直接展示最新内容。

- {filtered.map((preview, index) => ( + {previews.map((preview, index) => ( - + ))}
+ +
+ {previews.map((preview, index) => ( + + + + ))} +
)} ) } -function CategoryCard({ - active, - icon, - label, - description, - total, - libraries, - onClick, -}: { - active: boolean - icon: ReactNode - label: string - description: string - total: number - libraries: number - onClick: () => void -}) { - return ( - - ) -} - -function LibraryFolderCard({ preview }: { preview: LibraryPreview }) { +function LibraryEntryCard({ preview }: { preview: LibraryPreview }) { const library = preview.library - const artwork = groupSeries(preview.items) + const artwork = preview.cards .sort((a, b) => artworkScore(b.rep) - artworkScore(a.rep) || mediaTime(b.rep) - mediaTime(a.rep)) .map((card) => card.rep.poster_url || card.rep.backdrop_url) .filter(Boolean) @@ -295,13 +169,63 @@ function LibraryFolderCard({ preview }: { preview: LibraryPreview }) {
{preview.total.toLocaleString()} 个条目 - 进入文件夹 + 浏览全部
) } +function LibraryShelf({ preview }: { preview: LibraryPreview }) { + const library = preview.library + const cards = preview.cards.slice(0, 10) + + return ( +
+
+
+
+ {TYPE_ICONS[library.type] ?? } + {TYPE_LABELS[library.type] ?? library.type} +
+

{library.name}

+

+ {library.path} · {preview.total.toLocaleString()} 个条目 · 最新 {cards.length} 部 +

+
+ + 浏览全部 + + +
+ + {cards.length > 0 ? ( +
+ {cards.map((card) => ( +
+ +
+ ))} +
+ ) : ( +
+ 该目录暂无可展示内容,扫描媒体库后会出现在这里。 +
+ )} +
+ ) +} + +function latestCards(items: Media[]): SeriesCard[] { + return groupSeries(items) + .sort((a, b) => mediaTime(b.rep) - mediaTime(a.rep) || artworkScore(b.rep) - artworkScore(a.rep)) + .slice(0, 10) +} + function mediaCardLink(card: SeriesCard): string { if (card.count > 1) { return `/library/${card.rep.library_id}?series=${encodeURIComponent(card.key)}`