/* eslint-disable no-console,react-hooks/exhaustive-deps */ 'use client'; import { useSearchParams } from 'next/navigation'; import { Suspense } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react'; import { getDoubanCategories } from '@/lib/douban.client'; import { DoubanItem } from '@/lib/types'; import DoubanCardSkeleton from '@/components/DoubanCardSkeleton'; import DoubanSelector from '@/components/DoubanSelector'; import PageLayout from '@/components/PageLayout'; import VideoCard from '@/components/VideoCard'; function DoubanPageClient() { const searchParams = useSearchParams(); const [doubanData, setDoubanData] = useState([]); const [loading, setLoading] = useState(false); const [currentPage, setCurrentPage] = useState(0); const [hasMore, setHasMore] = useState(true); const [isLoadingMore, setIsLoadingMore] = useState(false); const [selectorsReady, setSelectorsReady] = useState(false); const observerRef = useRef(null); const loadingRef = useRef(null); const debounceTimeoutRef = useRef(null); const type = searchParams.get('type') || 'movie'; // 选择器状态 - 完全独立,不依赖URL参数 const [primarySelection, setPrimarySelection] = useState(() => { return type === 'movie' ? '热门' : ''; }); const [secondarySelection, setSecondarySelection] = useState(() => { if (type === 'movie') return '全部'; if (type === 'tv') return 'tv'; if (type === 'show') return 'show'; return '全部'; }); // 初始化时标记选择器为准备好状态 useEffect(() => { // 短暂延迟确保初始状态设置完成 const timer = setTimeout(() => { setSelectorsReady(true); }, 50); return () => clearTimeout(timer); }, []); // 只在组件挂载时执行一次 // type变化时立即重置selectorsReady(最高优先级) useEffect(() => { setSelectorsReady(false); setLoading(true); // 立即显示loading状态 }, [type]); // 当type变化时重置选择器状态 useEffect(() => { // 批量更新选择器状态 if (type === 'movie') { setPrimarySelection('热门'); setSecondarySelection('全部'); } else if (type === 'tv') { setPrimarySelection(''); setSecondarySelection('tv'); } else if (type === 'show') { setPrimarySelection(''); setSecondarySelection('show'); } else { setPrimarySelection(''); setSecondarySelection('全部'); } // 使用短暂延迟确保状态更新完成后标记选择器准备好 const timer = setTimeout(() => { setSelectorsReady(true); }, 50); return () => clearTimeout(timer); }, [type]); // 生成骨架屏数据 const skeletonData = Array.from({ length: 25 }, (_, index) => index); // 生成API请求参数的辅助函数 const getRequestParams = useCallback( (pageStart: number) => { // 当type为tv或show时,kind统一为'tv',category使用type本身 if (type === 'tv' || type === 'show') { return { kind: 'tv' as const, category: type, type: secondarySelection, pageLimit: 25, pageStart, }; } // 电影类型保持原逻辑 return { kind: type as 'tv' | 'movie', category: primarySelection, type: secondarySelection, pageLimit: 25, pageStart, }; }, [type, primarySelection, secondarySelection] ); // 防抖的数据加载函数 const loadInitialData = useCallback(async () => { try { setLoading(true); const data = await getDoubanCategories(getRequestParams(0)); if (data.code === 200) { setDoubanData(data.list); setHasMore(data.list.length === 25); setLoading(false); } else { throw new Error(data.message || '获取数据失败'); } } catch (err) { console.error(err); } }, [type, primarySelection, secondarySelection, getRequestParams]); // 只在选择器准备好后才加载数据 useEffect(() => { // 只有在选择器准备好时才开始加载 if (!selectorsReady) { return; } // 重置页面状态 setDoubanData([]); setCurrentPage(0); setHasMore(true); setIsLoadingMore(false); // 清除之前的防抖定时器 if (debounceTimeoutRef.current) { clearTimeout(debounceTimeoutRef.current); } // 使用防抖机制加载数据,避免连续状态更新触发多次请求 debounceTimeoutRef.current = setTimeout(() => { loadInitialData(); }, 100); // 100ms 防抖延迟 // 清理函数 return () => { if (debounceTimeoutRef.current) { clearTimeout(debounceTimeoutRef.current); } }; }, [ selectorsReady, type, primarySelection, secondarySelection, loadInitialData, ]); // 单独处理 currentPage 变化(加载更多) useEffect(() => { if (currentPage > 0) { const fetchMoreData = async () => { try { setIsLoadingMore(true); const data = await getDoubanCategories( getRequestParams(currentPage * 25) ); if (data.code === 200) { setDoubanData((prev) => [...prev, ...data.list]); setHasMore(data.list.length === 25); } else { throw new Error(data.message || '获取数据失败'); } } catch (err) { console.error(err); } finally { setIsLoadingMore(false); } }; fetchMoreData(); } }, [currentPage, type, primarySelection, secondarySelection]); // 设置滚动监听 useEffect(() => { // 如果没有更多数据或正在加载,则不设置监听 if (!hasMore || isLoadingMore || loading) { return; } // 确保 loadingRef 存在 if (!loadingRef.current) { return; } const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && hasMore && !isLoadingMore) { setCurrentPage((prev) => prev + 1); } }, { threshold: 0.1 } ); observer.observe(loadingRef.current); observerRef.current = observer; return () => { if (observerRef.current) { observerRef.current.disconnect(); } }; }, [hasMore, isLoadingMore, loading]); // 处理选择器变化 const handlePrimaryChange = useCallback( (value: string) => { setLoading(true); setPrimarySelection(value); }, [type] ); const handleSecondaryChange = useCallback((value: string) => { setLoading(true); setSecondarySelection(value); }, []); const getPageTitle = () => { // 根据 type 生成标题 return type === 'movie' ? '电影' : type === 'tv' ? '电视剧' : '综艺'; }; const getActivePath = () => { const params = new URLSearchParams(); if (type) params.set('type', type); const queryString = params.toString(); const activePath = `/douban${queryString ? `?${queryString}` : ''}`; return activePath; }; return (
{/* 页面标题和选择器 */}
{/* 页面标题 */}

{getPageTitle()}

来自豆瓣的精选内容

{/* 选择器组件 */}
{/* 内容展示区域 */}
{/* 内容网格 */}
{loading || !selectorsReady ? // 显示骨架屏 skeletonData.map((index) => ) : // 显示实际数据 doubanData.map((item, index) => (
))}
{/* 加载更多指示器 */} {hasMore && !loading && (
{ if (el && el.offsetParent !== null) { ( loadingRef as React.MutableRefObject ).current = el; } }} className='flex justify-center mt-12 py-8' > {isLoadingMore && (
加载中...
)}
)} {/* 没有更多数据提示 */} {!hasMore && doubanData.length > 0 && (
已加载全部内容
)} {/* 空状态 */} {!loading && doubanData.length === 0 && (
暂无相关内容
)}
); } export default function DoubanPage() { return ( ); }