import { useEffect, useMemo, useState, type ReactNode } from 'react' import { Link } from 'react-router-dom' import { motion } from 'framer-motion' import { ArrowRight, Film, FolderOpen, Library as LibraryIcon, Music, PlayCircle, RefreshCw, Tv } from 'lucide-react' import { imageURL } from '../api/client' import { libraryAPI } from '../api/library' import { toolsAPI } from '../api/tools' import { MediaCard } from '../components/MediaCard' import type { Library, Media } from '../types' import { artworkScore, groupSeries, seriesCardLink, type SeriesCard } from '../utils/groupSeries' type LibraryPreview = { library: Library items: Media[] total: number cards: SeriesCard[] } const TYPE_ICONS: Record = { movie: , tv: , anime: , variety: , music: , } const TYPE_LABELS: Record = { movie: '电影', tv: '剧集', anime: '动漫', variety: '综艺', music: '音乐', } export function LibrariesPage() { const [previews, setPreviews] = useState([]) const [loading, setLoading] = useState(true) const [repairing, setRepairing] = useState(false) const [repairMsg, setRepairMsg] = useState('') async function handleRepairRescrape() { if (repairing) return setRepairing(true) setRepairMsg('') try { await toolsAPI.repairAndRescrapeAll() setRepairMsg('已开始全库修复+重刮,进度可在任务中查看。') } catch { setRepairMsg('启动失败,请稍后重试。') } finally { setRepairing(false) } } useEffect(() => { let cancelled = false async function load() { setLoading(true) try { const libs = await libraryAPI.list() const rows = await Promise.all(libs.map(async (library) => { try { 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, cards: [] } satisfies LibraryPreview } })) if (!cancelled) setPreviews(rows) } finally { if (!cancelled) setLoading(false) } } load() return () => { cancelled = true } }, []) const total = useMemo(() => previews.reduce((sum, preview) => sum + preview.total, 0), [previews]) if (loading) { return

媒体库加载中…

} return (

媒体库

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

{repairMsg && {repairMsg}} 管理媒体库
{previews.length === 0 ? (

暂无媒体库,请到管理后台添加目录。

) : ( <>

媒体库入口

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

{previews.map((preview, index) => ( ))}
{previews.map((preview, index) => ( ))}
)}
) } function LibraryEntryCard({ preview }: { preview: LibraryPreview }) { const library = preview.library 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) .slice(0, 4) as string[] return (
{artwork.length > 0 ? ( artwork.map((src, index) => ( { event.currentTarget.style.visibility = 'hidden' }} /> )) ) : (
{TYPE_ICONS[library.type] ?? }
)}
{TYPE_LABELS[library.type] ?? library.type}

{library.name}

{library.path}

{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 mediaTime(media: Media): number { return Date.parse(media.updated_at || media.created_at || '') || 0 }