'use client'; import React, { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; interface OfflineDownloadTask { id: string; source: string; videoId: string; episodeIndex: number; title: string; m3u8Url: string; status: 'pending' | 'downloading' | 'completed' | 'error' | 'paused'; progress: number; totalSegments: number; downloadedSegments: number; errorMessage?: string; createdAt: string; updatedAt: string; downloadDir: string; metadata?: { videoTitle?: string; cover?: string; description?: string; year?: string; rating?: number; totalEpisodes?: number; }; } interface OfflineDownloadPanelProps { isOpen: boolean; onClose: () => void; } export function OfflineDownloadPanel({ isOpen, onClose }: OfflineDownloadPanelProps) { const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(false); const [mounted, setMounted] = useState(false); const [viewMode, setViewMode] = useState<'tasks' | 'library'>('tasks'); // 视图模式:任务列表或视频库 // 确保只在客户端渲染 useEffect(() => { setMounted(true); }, []); // 获取任务列表 const fetchTasks = async () => { try { const response = await fetch('/api/offline-download'); if (response.ok) { const data = await response.json(); setTasks(data.tasks || []); } } catch (error) { console.error('获取离线下载任务列表失败:', error); } }; // 删除任务 const handleDeleteTask = async (taskId: string) => { try { const response = await fetch(`/api/offline-download?taskId=${taskId}`, { method: 'DELETE', }); if (response.ok) { // 从列表中移除 setTasks((prev) => prev.filter((t) => t.id !== taskId)); } else { const data = await response.json(); alert(`删除失败: ${data.error}`); } } catch (error) { console.error('删除任务失败:', error); alert('删除任务失败'); } }; // 重试任务 const handleRetryTask = async (taskId: string) => { try { const response = await fetch(`/api/offline-download?taskId=${taskId}&action=retry`, { method: 'PUT', }); if (response.ok) { const data = await response.json(); // 更新任务状态(保留进度,只重试失败的片段) setTasks((prev) => prev.map((t) => t.id === taskId ? { ...t, status: 'pending', errorMessage: undefined, updatedAt: new Date().toISOString(), } : t ) ); // 立即刷新以获取最新状态 fetchTasks(); } else { const data = await response.json(); alert(`重试失败: ${data.error}`); } } catch (error) { console.error('重试任务失败:', error); alert('重试任务失败'); } }; // 定期刷新任务列表 useEffect(() => { if (isOpen) { fetchTasks(); const interval = setInterval(fetchTasks, 3000); // 每3秒刷新一次 return () => clearInterval(interval); } }, [isOpen]); if (!isOpen || !mounted) { return null; } const getStatusText = (status: OfflineDownloadTask['status']) => { switch (status) { case 'pending': return '等待中'; case 'downloading': return '下载中'; case 'paused': return '已暂停'; case 'completed': return '已完成'; case 'error': return '错误'; default: return '未知'; } }; const getStatusColor = (status: OfflineDownloadTask['status']) => { switch (status) { case 'pending': return 'text-gray-500 dark:text-gray-400'; case 'downloading': return 'text-blue-500 dark:text-blue-400'; case 'paused': return 'text-yellow-500 dark:text-yellow-400'; case 'completed': return 'text-green-500 dark:text-green-400'; case 'error': return 'text-red-500 dark:text-red-400'; default: return 'text-gray-500 dark:text-gray-400'; } }; // 获取视频库中的视频(按videoId分组,包含已完成和有进度的任务) const getLibraryVideos = () => { // 筛选已完成或有下载进度的任务 const libraryTasks = tasks.filter((t) => t.status === 'completed' || t.progress > 0); const videoMap = new Map(); libraryTasks.forEach((task) => { const key = `${task.source}_${task.videoId}`; if (!videoMap.has(key)) { videoMap.set(key, { video: task, episodes: [] }); } videoMap.get(key)!.episodes.push(task); }); // 按集数排序 videoMap.forEach((value) => { value.episodes.sort((a, b) => a.episodeIndex - b.episodeIndex); }); return Array.from(videoMap.values()); }; const libraryVideos = getLibraryVideos(); const panelContent = (
{/* 标题栏 */}

{viewMode === 'tasks' ? '离线下载任务列表' : '视频库'}

{/* 视图切换按钮 */}
{/* 关闭按钮 */}
{/* 内容区域 */}
{loading ? (
) : viewMode === 'library' ? ( // 视频库视图 libraryVideos.length === 0 ? (

暂无已完成的视频

) : ( libraryVideos.map(({ video, episodes }) => (
{/* 封面图 */} {video.metadata?.cover && (
{video.metadata.videoTitle
)} {/* 视频信息 */}

{video.metadata?.videoTitle || video.title}

{video.metadata?.year && (

年份: {video.metadata.year}

)} {video.metadata?.rating && (

评分: {video.metadata.rating.toFixed(1)}

)} {video.metadata?.description && (

{video.metadata.description}

)}
已下载 {episodes.length} 集 {video.metadata?.totalEpisodes && ( / 共 {video.metadata.totalEpisodes} 集 )}
{/* 集数列表 */}
{episodes.map((ep) => (
第{ep.episodeIndex + 1}集
))}
{/* 删除全部按钮 */}
)) ) ) : tasks.length === 0 ? ( // 任务列表为空

暂无离线下载任务

) : ( // 任务列表视图 tasks.map((task) => (
{/* 任务信息 */}

{task.title}

来源: {task.source} | 视频ID: {task.videoId} | 第{task.episodeIndex + 1}集

{getStatusText(task.status)}
{/* 进度条 */} {task.totalSegments > 0 && (
{task.downloadedSegments} / {task.totalSegments} 片段 {task.progress.toFixed(1)}%
)} {/* 错误信息 */} {task.errorMessage && (
{task.errorMessage}
)} {/* 时间信息 */}
创建: {new Date(task.createdAt).toLocaleString('zh-CN')} 更新: {new Date(task.updatedAt).toLocaleString('zh-CN')}
{/* 操作按钮 */}
{/* 重试按钮 - 只在错误或暂停状态显示 */} {(task.status === 'error' || task.status === 'paused') && ( )}
)) )}
); return createPortal(panelContent, document.body); }