import { type DiscoverItem, type DiscoverMatchResult, tmdbImageUrl } from "@umt/contracts"; import { ExternalLink, LoaderCircle, Search, Star } from "lucide-react"; import { useState } from "react"; import { Link, useNavigate, useSearchParams } from "react-router"; import { toast } from "sonner"; import { useBangumiCalendar, useDiscoverMatch, useDoubanHot } from "~/api/discover.ts"; import { EmptyState, ErrorState, LoadingState } from "~/components/console/empty-state.tsx"; import { consolePath } from "~/components/console/nav.ts"; import { PageHeader } from "~/components/console/page-header.tsx"; import { Button } from "~/components/ui/button.tsx"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "~/components/ui/dialog.tsx"; import { Tabs, TabsList, TabsTrigger } from "~/components/ui/tabs.tsx"; import { errorMessage } from "~/lib/api.ts"; // 发现(对照 MP 推荐页):bgm.tv 每日放送、豆瓣热门电影 / 剧集。点条目按名称 + 年份匹配 TMDB 作品, // 确定的直接进作品页(可订阅、搜资源),不确定的列候选让人选。 export const meta = () => [{ title: "发现 · UHD 媒体自动化控制台" }]; type View = "anime" | "movie" | "tv"; const VIEWS: { value: View; label: string }[] = [ { value: "anime", label: "番剧放送" }, { value: "movie", label: "热门电影" }, { value: "tv", label: "热门剧集" }, ]; /** bgm 周一 = 1 … 周日 = 7 */ const todayWeekday = () => ((new Date().getDay() + 6) % 7) + 1; export default function DiscoverPage() { const [sp, setSp] = useSearchParams(); const view = (VIEWS.some((v) => v.value === sp.get("view")) ? sp.get("view") : "anime") as View; const [day, setDay] = useState(todayWeekday); const cal = useBangumiCalendar(view === "anime"); const hot = useDoubanHot(view === "tv" ? "tv" : "movie", view !== "anime"); const match = useDiscoverMatch(); const navigate = useNavigate(); const [picking, setPicking] = useState<{ item: DiscoverItem; result: DiscoverMatchResult } | null>(null); const [busyId, setBusyId] = useState(null); const open = async (it: DiscoverItem) => { setBusyId(`${it.source}:${it.id}`); try { const r = await match.mutateAsync(it); if (r.match) navigate(consolePath("media", "tmdb", r.match.type, r.match.id)); else setPicking({ item: it, result: r }); } catch (err) { toast.error(errorMessage(err, "匹配 TMDB 失败")); } finally { setBusyId(null); } }; const q = view === "anime" ? cal : hot; const items = view === "anime" ? (cal.data?.days.find((d) => d.weekday === day)?.items ?? []) : (hot.data?.items ?? []); return ( <> setSp(new URLSearchParams({ view: v }), { replace: true })}> {VIEWS.map((v) => ( {v.label} ))} {view === "anime" && cal.data ? (
{cal.data.days.map((d) => ( ))}
) : null} {q.isLoading ? ( ) : q.error ? ( void q.refetch()} /> ) : items.length === 0 ? ( ) : (
    {items.map((it) => (
  • void open(it)} />
  • ))}
)} {picking ? setPicking(null)} /> : null} ); } function ItemCard({ item, busy, onOpen }: { item: DiscoverItem; busy: boolean; onOpen: () => void }) { const [failed, setFailed] = useState(false); return ( ); } function PickDialog({ item, result, onClose, }: { item: DiscoverItem; result: DiscoverMatchResult; onClose: () => void; }) { return ( !o && onClose()}> 选择 TMDB 作品 「{item.title}」{item.year ? `(${item.year})` : ""}没能自动确定对应的 TMDB 作品。 {result.candidates.length ? (
    {result.candidates.map((c) => (
  • {c.posterPath ? ( ) : null} {c.title} {c.type === "movie" ? "电影" : "剧集"} · {c.year ?? "—"}
  • ))}
) : (

TMDB 上没有找到相近的作品。

)}
); }