diff --git a/src/app/aggregate/page.tsx b/src/app/aggregate/page.tsx index 7a8cf1f..67275b4 100644 --- a/src/app/aggregate/page.tsx +++ b/src/app/aggregate/page.tsx @@ -251,28 +251,6 @@ function AggregatePageClient() {
{/* 主信息区:左图右文 */}
- {/* 返回按钮 */} - {/* 封面 */}
(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [playRecord, setPlayRecord] = useState(null); - const [favorited, setFavorited] = useState(false); - // 是否倒序显示选集 - const [reverseEpisodeOrder, setReverseEpisodeOrder] = useState(false); - - const fallbackTitle = searchParams.get('title') || ''; - const fallbackYear = searchParams.get('year') || ''; - - // 格式化剩余时间(如 1h 50m) - const formatDuration = (seconds: number) => { - const h = Math.floor(seconds / 3600); - const m = Math.floor((seconds % 3600) / 60); - const parts: string[] = []; - if (h) parts.push(`${h}h`); - if (m) parts.push(`${m}m`); - if (parts.length === 0) parts.push('0m'); - return parts.join(' '); - }; - - useEffect(() => { - const source = searchParams.get('source'); - const id = searchParams.get('id'); - - if (!source || !id) { - setError('缺少必要参数'); - setLoading(false); - return; - } - - const fetchData = async () => { - try { - // 获取视频详情 - const detailData = await fetchVideoDetail({ - source, - id, - fallbackTitle: fallbackTitle.trim(), - fallbackYear, - }); - setDetail(detailData); - - // 获取播放记录 - const allRecords = await getAllPlayRecords(); - const key = generateStorageKey(source, id); - setPlayRecord(allRecords[key] || null); - - // 检查收藏状态 - try { - const fav = await isFavorited(source, id); - setFavorited(fav); - } catch (checkErr) { - console.error('检查收藏状态失败:', checkErr); - } - } catch (err) { - setError(err instanceof Error ? err.message : '获取详情失败'); - } finally { - setLoading(false); - } - }; - - fetchData(); - }, [searchParams]); - - // 切换收藏状态 - const handleToggleFavorite = async () => { - const source = searchParams.get('source'); - const id = searchParams.get('id'); - if (!source || !id || !detail) return; - - try { - const newState = await toggleFavorite(source, id, { - title: detail.title, - source_name: detail.source_name, - year: detail.year || fallbackYear || '', - cover: detail.poster || '', - total_episodes: detail.episodes.length || 1, - save_time: Date.now(), - }); - setFavorited(newState); - } catch (err) { - console.error('切换收藏失败:', err); - } - }; - - return ( - -
- {/* 顶部返回按钮已移入右侧信息容器 */} - {loading ? ( -
-
-
- ) : error ? ( -
-
-
加载失败
-
{error}
-
-
- ) : !detail ? ( -
-
-
未找到视频详情
-
-
- ) : ( -
- {/* 主信息区:左图右文 */} -
- {/* 返回按钮放置在主信息区左上角 */} - - {/* 封面 */} -
- {detail.title -
- {/* 右侧信息 */} -
-

- {detail.title || fallbackTitle} - {detail.douban_id && ( - e.stopPropagation()} - className='ml-2' - > - - - )} -

-
- {detail.class && ( - - {detail.class} - - )} - {(detail.year || fallbackYear) && ( - {detail.year || fallbackYear} - )} - {detail.source_name && ( - - {detail.source_name} - - )} - {detail.type_name && {detail.type_name}} -
- {/* 按钮区域 */} -
- {playRecord ? ( - <> - {/* 恢复播放 */} - -
- 恢复播放 -
- {/* 从头开始 */} - -
- 从头开始 -
- - ) : ( - <> - {/* 播放 */} - -
- 播放 -
- - )} - {/* 爱心按钮 */} - -
- {/* 播放记录进度条 */} - {playRecord && ( -
- {/* 进度条 */} -
-
-
- {/* 剩余时间 */} - - {playRecord.total_episodes > 1 - ? `第${playRecord.index}集 剩余 ` - : '剩余 '} - {formatDuration( - playRecord.total_time - playRecord.play_time - )} - -
- )} - {detail.desc && ( -
- {detail.desc} -
- )} -
-
- {/* 选集按钮区 */} - {detail.episodes && detail.episodes.length > 0 && ( -
-
-
选集
-
- 共 {detail.episodes.length} 集 -
- {/* 倒序切换 */} - setReverseEpisodeOrder((prev) => !prev)} - className={`ml-4 text-sm cursor-pointer select-none transition-colors ${ - reverseEpisodeOrder - ? 'text-green-500' - : 'text-gray-400 hover:text-gray-500' - }`} - > - 倒序 - -
-
- {(reverseEpisodeOrder - ? Array.from( - { length: detail.episodes.length }, - (_, i) => i - ).reverse() - : Array.from( - { length: detail.episodes.length }, - (_, i) => i - ) - ).map((idx) => ( - - {idx + 1} - - ))} -
-
- )} -
- )} -
-
- ); -} - -export default function DetailPage() { - return ( - - - - ); -} diff --git a/src/components/EpisodeSelector.tsx b/src/components/EpisodeSelector.tsx index 073856a..9e4bb44 100644 --- a/src/components/EpisodeSelector.tsx +++ b/src/components/EpisodeSelector.tsx @@ -53,8 +53,9 @@ const EpisodeSelector: React.FC = ({ const pageCount = Math.ceil(totalEpisodes / episodesPerPage); // 主要的 tab 状态:'episodes' 或 'sources' + // 当只有一集时默认展示 "换源",并隐藏 "选集" 标签 const [activeTab, setActiveTab] = useState<'episodes' | 'sources'>( - 'episodes' + totalEpisodes > 1 ? 'episodes' : 'sources' ); // 当前分页索引(0 开始) @@ -118,6 +119,20 @@ const EpisodeSelector: React.FC = ({ [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, @@ -128,18 +143,20 @@ const EpisodeSelector: React.FC = ({
{/* 主要的 Tab 切换 - 无缝融入设计 */}
-
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()} - > - 选集 -
+ {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()} + > + 选集 +
+ )}
{ return (
{/* 移动端头部 */} - + {/* 主要布局容器 */}
@@ -26,9 +28,11 @@ const PageLayout = ({ children, activePath = '/' }: PageLayoutProps) => { {/* 主内容区域 */}
{/* 桌面端左上角返回按钮 */} -
- -
+ {['/play', '/aggregate'].includes(activePath) && ( +
+ +
+ )} {/* 桌面端顶部按钮 */}
diff --git a/src/components/VideoCard.tsx b/src/components/VideoCard.tsx index 4c92e56..3b114f0 100644 --- a/src/components/VideoCard.tsx +++ b/src/components/VideoCard.tsx @@ -153,7 +153,7 @@ export default function VideoCard({ return (