/* eslint-disable @next/next/no-img-element */ import { useRouter } from 'next/navigation'; import React, { useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import { SearchResult } from '@/lib/types'; interface EpisodeSelectorProps { /** 总集数 */ totalEpisodes: number; /** 每页显示多少集,默认 50 */ episodesPerPage?: number; /** 当前选中的集数(1 开始) */ value?: number; /** 用户点击选集后的回调 */ onChange?: (episodeNumber: number) => void; /** 换源相关 */ onSourceChange?: (source: string, id: string, title: string) => void; currentSource?: string; currentId?: string; videoTitle?: string; videoYear?: string; availableSources?: SearchResult[]; onSearchSources?: (query: string) => void; sourceSearchLoading?: boolean; sourceSearchError?: string | null; } /** * 选集组件,支持分页、自动滚动聚焦当前分页标签,以及换源功能。 */ const EpisodeSelector: React.FC = ({ totalEpisodes, episodesPerPage = 50, value = 1, onChange, onSourceChange, currentSource, currentId, videoTitle, availableSources = [], onSearchSources, sourceSearchLoading = false, sourceSearchError = null, }) => { const router = useRouter(); const pageCount = Math.ceil(totalEpisodes / episodesPerPage); // 主要的 tab 状态:'episodes' 或 'sources' // 当只有一集时默认展示 "换源",并隐藏 "选集" 标签 const [activeTab, setActiveTab] = useState<'episodes' | 'sources'>( totalEpisodes > 1 ? 'episodes' : 'sources' ); // 当前分页索引(0 开始) const initialPage = Math.floor((value - 1) / episodesPerPage); const [currentPage, setCurrentPage] = useState(initialPage); // 是否倒序显示 const [descending, setDescending] = useState(false); // 升序分页标签 const categoriesAsc = useMemo(() => { return Array.from({ length: pageCount }, (_, i) => { const start = i * episodesPerPage + 1; const end = Math.min(start + episodesPerPage - 1, totalEpisodes); return `${start}-${end}`; }); }, [pageCount, episodesPerPage, totalEpisodes]); // 分页标签始终保持升序 const categories = categoriesAsc; const categoryContainerRef = useRef(null); const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]); // 当分页切换时,将激活的分页标签滚动到视口中间 useEffect(() => { const btn = buttonRefs.current[currentPage]; if (btn) { btn.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest', }); } }, [currentPage, pageCount]); // 处理换源tab点击,只在点击时才搜索 const handleSourceTabClick = () => { setActiveTab('sources'); // 只在点击时搜索,且只搜索一次 if (availableSources.length === 0 && videoTitle && onSearchSources) { onSearchSources(videoTitle); } }; const handleCategoryClick = useCallback((index: number) => { setCurrentPage(index); }, []); const handleEpisodeClick = useCallback( (episodeNumber: number) => { onChange?.(episodeNumber); }, [onChange] ); const handleSourceClick = useCallback( (source: SearchResult) => { onSourceChange?.(source.source, source.id, source.title); }, [onSourceChange] ); // 如果组件初始即显示 "换源",自动触发搜索一次 useEffect(() => { if ( activeTab === 'sources' && availableSources.length === 0 && videoTitle && onSearchSources ) { onSearchSources(videoTitle); } // 只在依赖变化时尝试,availableSources 长度变化可阻止重复搜索 // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeTab, availableSources.length, videoTitle]); const currentStart = currentPage * episodesPerPage + 1; const currentEnd = Math.min( currentStart + episodesPerPage - 1, totalEpisodes ); return (
{/* 主要的 Tab 切换 - 无缝融入设计 */}
{totalEpisodes > 1 && (
setActiveTab('episodes')} className={`flex-1 py-3 px-6 text-center cursor-pointer transition-all duration-200 font-medium ${ activeTab === 'episodes' ? 'text-green-500 dark:text-green-400' : 'text-gray-700 hover:text-green-600 bg-black/5 dark:bg-white/5 dark:text-gray-300 dark:hover:text-green-400 hover:bg-black/3 dark:hover:bg-white/3' } `.trim()} > 选集
)}
换源
{/* 选集 Tab 内容 */} {activeTab === 'episodes' && ( <> {/* 分类标签 */}
{categories.map((label, idx) => { const isActive = idx === currentPage; return ( ); })}
{/* 向上/向下按钮 */}
{/* 集数网格 */}
{(() => { const len = currentEnd - currentStart + 1; const episodes = Array.from({ length: len }, (_, i) => descending ? currentEnd - i : currentStart + i ); return episodes; })().map((episodeNumber) => { const isActive = episodeNumber === value; return ( ); })}
)} {/* 换源 Tab 内容 */} {activeTab === 'sources' && (
{sourceSearchLoading && (
搜索中...
)} {sourceSearchError && (
⚠️

{sourceSearchError}

)} {!sourceSearchLoading && !sourceSearchError && availableSources.length === 0 && (
📺

暂无可用的换源

)} {!sourceSearchLoading && !sourceSearchError && availableSources.length > 0 && (
{availableSources .sort((a, b) => { const aIsCurrent = a.source?.toString() === currentSource?.toString() && a.id?.toString() === currentId?.toString(); const bIsCurrent = b.source?.toString() === currentSource?.toString() && b.id?.toString() === currentId?.toString(); if (aIsCurrent && !bIsCurrent) return -1; if (!aIsCurrent && bIsCurrent) return 1; return 0; }) .map((source) => { const isCurrentSource = source.source?.toString() === currentSource?.toString() && source.id?.toString() === currentId?.toString(); return (
!isCurrentSource && handleSourceClick(source) } className={`flex items-start gap-3 p-3 rounded-lg cursor-pointer transition-all duration-200 ${ isCurrentSource ? 'bg-green-500/10 dark:bg-green-500/20 border-green-500/30 border' : 'hover:bg-gray-200/50 dark:hover:bg-white/10 hover:scale-[1.02]' }`.trim()} > {/* 封面 */}
{source.episodes && source.episodes.length > 0 && ( {source.title} { const target = e.target as HTMLImageElement; target.style.display = 'none'; }} /> )}
{/* 信息区域 */}

{source.title}

{source.source_name}
{source.episodes.length > 1 && ( 共 {source.episodes.length} 集 )}
); })}
)}
)}
); }; export default EpisodeSelector;