import { ChevronRight } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { Link } from "react-router"; import { useDiscoverSection } from "~/api/discover.ts"; import { Button } from "~/components/ui/button.tsx"; import { errorMessage } from "~/lib/api.ts"; import { ChipRow } from "./chips.tsx"; import { DiscoverCard, DiscoverCardSkeleton } from "./discover-card.tsx"; import { apiQuery, CATEGORIES, normalize, type RecommendSection, sectionsOf, toSearchParams, } from "./discover-state.ts"; import { useDiscoverActions } from "./use-discover-actions.tsx"; // 推荐(对照 MP recommend.vue):按分区横排,分区进入视口附近才加载(各分区各自请求、并发), // 每区「查看全部」带同样的参数切到探索。顶部按 全部 / 电影 / 剧集 / 动画 / 榜单 筛分区。 const CARD_W = "w-32 shrink-0 sm:w-36"; /** 进入视口(提前 600px)后为 true;没有 IntersectionObserver 时直接 true */ function useNearViewport() { const ref = useRef(null); const [near, setNear] = useState(() => typeof IntersectionObserver === "undefined"); useEffect(() => { if (near || !ref.current) return; const io = new IntersectionObserver((es) => es.some((e) => e.isIntersecting) && setNear(true), { rootMargin: "600px 0px", }); io.observe(ref.current); return () => io.disconnect(); }, [near]); return [ref, near] as const; } export function RecommendView({ category, onCategory, }: { category: string; onCategory: (c: string | undefined) => void; }) { const actions = useDiscoverActions(); return (
{sectionsOf(category).map((s) => (
))} {actions.dialogs}
); } function Section({ section, actions }: { section: RecommendSection; actions: ReturnType }) { const [ref, near] = useNearViewport(); const state = normalize(section.explore); const q = useDiscoverSection(apiQuery(state), near); const items = q.data?.items ?? []; const id = `discover-${section.key}`; return (

{section.title}

{q.isError ? (
{errorMessage(q.error, "加载失败")}
) : q.isSuccess && items.length === 0 ? (

这一栏暂时没有作品。

) : (
    {q.isSuccess ? items.slice(0, 20).map((it) => (
  • void actions.act(it, a)} />
  • )) : Array.from({ length: 8 }, (_, i) => (
  • ))}
)}
); }