import { useEffect, useMemo, useState } from 'react' import { Link } from 'react-router-dom' import { motion } from 'framer-motion' import { ArrowRight, Clock, Film, Play, Sparkles } from 'lucide-react' import { libraryAPI, mediaAPI } from '../api/library' import { playbackAPI, type HistoryItem } from '../api/playback' import { imageURL } from '../api/client' import { MediaCard } from '../components/MediaCard' import type { Library, Media } from '../types' import { groupSeries } from '../utils/groupSeries' const hasArtwork = (media?: Media | null) => !!(media?.poster_url || media?.backdrop_url) export function HomePage() { const [libraries, setLibraries] = useState([]) const [recent, setRecent] = useState([]) const [history, setHistory] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { let cancelled = false async function load() { setLoading(true) try { const [libs, recentItems, hist] = await Promise.all([ libraryAPI.list().catch(() => [] as Library[]), mediaAPI.search('', 120).then((d) => d.items).catch(() => [] as Media[]), playbackAPI.recentHistory().catch(() => [] as HistoryItem[]), ]) if (cancelled) return setLibraries(libs) setRecent(recentItems) setHistory(hist.filter((h) => !h.completed && !!h.media)) } finally { if (!cancelled) setLoading(false) } } load() return () => { cancelled = true } }, []) const recentCards = useMemo(() => groupSeries(recent).slice(0, 24), [recent]) const featuredItem = useMemo(() => { const candidates = [ ...(history.map((h) => h.media).filter(Boolean) as Media[]), ...recentCards.map((card) => card.rep), ...recent, ] return candidates.find(hasArtwork) ?? candidates[0] ?? null }, [history, recentCards, recent]) const featuredVisual = featuredItem?.backdrop_url || featuredItem?.poster_url || '' const featuredPoster = featuredItem?.poster_url || featuredItem?.backdrop_url || '' const featuredMark = (featuredItem?.title || 'MS').trim().slice(0, 4).toUpperCase() const empty = !loading && libraries.length === 0 && recentCards.length === 0 && history.length === 0 if (loading) { return (
首页内容准备中…
) } if (empty) { return (

您的家庭影视站暂无内容

前往管理后台添加媒体目录,扫描后首页将展示本周力荐、继续观看和最近入库。

前往管理后台
) } return (
{featuredItem && (
{featuredVisual && ( { event.currentTarget.style.display = 'none' }} /> )}
本周力荐 / Featured
{featuredMark}

{featuredItem.title}

{featuredItem.overview || '家庭私人媒体中心收藏。支持多端播放、外部播放器、智能刮削与订阅下载。'}

{featuredItem.year > 0 && ( {featuredItem.year} 年 )} {featuredItem.video_codec && ( {featuredItem.video_codec} )} {featuredItem.container && ( {featuredItem.container} )}
立即播放 发现更多精彩
{featuredItem.title}
{featuredPoster && ( {featuredItem.title} { event.currentTarget.style.display = 'none' }} /> )}
)} {history.length > 0 && (

继续观看

{history.length} 个记录
{history.slice(0, 8).map((h) => { const media = h.media! const progress = h.duration_ms > 0 ? h.position_ms / h.duration_ms : 0 return })}
)} {recentCards.length > 0 && (

最近入库

按整部电影、剧集、番剧和综艺合集展示新增内容。

海报墙
{recentCards.map((card) => ( 1 ? `/library/${card.rep.library_id}?series=${encodeURIComponent(card.key)}` : undefined} /> ))}
)}
) } function ContinueCard({ media, progress }: { media: Media; progress: number }) { return (
{media.poster_url ? ( ) : (
)}

{media.title}

已观看到 {Math.round(progress * 100)}%

) }