'use client'; import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; import { useCallback, useEffect, useRef, useState } from 'react'; import { getEpisodes, searchAnime } from '@/lib/danmaku/api'; import type { DanmakuAnime, DanmakuComment, DanmakuEpisode, DanmakuSelection, } from '@/lib/danmaku/types'; interface DanmakuPanelProps { videoTitle: string; currentEpisodeIndex: number; onDanmakuSelect: (selection: DanmakuSelection) => void; currentSelection: DanmakuSelection | null; onUploadDanmaku?: (comments: DanmakuComment[]) => void; } export default function DanmakuPanel({ videoTitle, currentEpisodeIndex, onDanmakuSelect, currentSelection, onUploadDanmaku, }: DanmakuPanelProps) { const [searchKeyword, setSearchKeyword] = useState(''); const [searchResults, setSearchResults] = useState([]); const [selectedAnime, setSelectedAnime] = useState(null); const [episodes, setEpisodes] = useState([]); const [isSearching, setIsSearching] = useState(false); const [isLoadingEpisodes, setIsLoadingEpisodes] = useState(false); const [searchError, setSearchError] = useState(null); const initializedRef = useRef(false); // 标记是否已初始化过 const fileInputRef = useRef(null); // 搜索弹幕 const handleSearch = useCallback(async (keyword: string) => { if (!keyword.trim()) { setSearchError('请输入搜索关键词'); return; } setIsSearching(true); setSearchError(null); try { const response = await searchAnime(keyword.trim()); if (response.success && response.animes.length > 0) { setSearchResults(response.animes); setSearchError(null); } else { setSearchResults([]); setSearchError( response.errorMessage || '未找到匹配的剧集,请尝试其他关键词' ); } } catch (error) { console.error('搜索失败:', error); setSearchError('搜索失败,请检查弹幕 API 服务是否正常运行'); setSearchResults([]); } finally { setIsSearching(false); } }, []); // 选择动漫,加载剧集列表 const handleAnimeSelect = useCallback(async (anime: DanmakuAnime) => { setSelectedAnime(anime); setIsLoadingEpisodes(true); try { const response = await getEpisodes(anime.animeId); if (response.success && response.bangumi.episodes.length > 0) { setEpisodes(response.bangumi.episodes); } else { setEpisodes([]); setSearchError('该剧集暂无弹幕信息'); } } catch (error) { console.error('获取剧集失败:', error); setEpisodes([]); setSearchError('获取剧集失败'); } finally { setIsLoadingEpisodes(false); } }, []); // 选择剧集 const handleEpisodeSelect = useCallback( (episode: DanmakuEpisode) => { if (!selectedAnime) return; const selection: DanmakuSelection = { animeId: selectedAnime.animeId, episodeId: episode.episodeId, animeTitle: selectedAnime.animeTitle, episodeTitle: episode.episodeTitle, searchKeyword: searchKeyword.trim() || undefined, // 使用当前搜索框的关键词 }; onDanmakuSelect(selection); }, [selectedAnime, searchKeyword, onDanmakuSelect] ); // 回到搜索结果 const handleBackToResults = useCallback(() => { setSelectedAnime(null); setEpisodes([]); }, []); // 判断当前剧集是否已选中 const isEpisodeSelected = useCallback( (episodeId: number) => { return currentSelection?.episodeId === episodeId; }, [currentSelection] ); // 处理文件上传 const handleFileUpload = useCallback(async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; if (!file.name.endsWith('.xml')) { setSearchError('请上传XML格式的弹幕文件'); return; } try { const text = await file.text(); const { parseXmlDanmaku } = await import('@/lib/danmaku/xml-parser'); const comments = parseXmlDanmaku(text); if (comments.length === 0) { setSearchError('弹幕文件解析失败或文件为空'); return; } onUploadDanmaku?.(comments); setSearchError(null); } catch (error) { console.error('上传弹幕失败:', error); setSearchError('弹幕文件解析失败'); } finally { if (fileInputRef.current) { fileInputRef.current.value = ''; } } }, [onUploadDanmaku]); // 当视频标题首次加载时,初始化搜索关键词(仅执行一次) useEffect(() => { if (videoTitle && !initializedRef.current) { setSearchKeyword(videoTitle); initializedRef.current = true; // 标记已初始化,防止后续自动填充 } }, [videoTitle]); return (
{/* 搜索区域 - 固定在顶部 */}
setSearchKeyword(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { handleSearch(searchKeyword); } }} placeholder='输入剧集名称搜索弹幕...' autoComplete='off' autoCorrect='off' autoCapitalize='off' spellCheck='false' data-form-type='other' data-lpignore='true' className='flex-1 min-w-[220px] rounded-lg border border-gray-300 px-3 py-2 text-sm transition-colors focus:border-green-500 focus:outline-none focus:ring-2 focus:ring-green-500/20 dark:border-gray-600 dark:bg-gray-800 dark:text-white sm:px-4' disabled={isSearching} />
{/* 错误提示 */} {searchError && (
{searchError}
)}
{/* 可滚动内容区域 */}
{/* 当前选择的弹幕信息 */} {currentSelection && (

当前弹幕

{currentSelection.animeTitle}

{currentSelection.episodeTitle}

{currentSelection.danmakuCount !== undefined && (

弹幕数量: {currentSelection.danmakuCount} {currentSelection.danmakuOriginalCount && ` (原始 ${currentSelection.danmakuOriginalCount} 条)`}

)}
)} {/* 内容区域 */}
{/* 显示剧集列表 */} {selectedAnime && (
{/* 返回按钮 */} {/* 动漫标题 */}

{selectedAnime.animeTitle}

{/* 加载中 */} {isLoadingEpisodes && (
)} {/* 剧集列表 */} {!isLoadingEpisodes && episodes.length > 0 && (
{episodes.map((episode, index) => { const isSelected = isEpisodeSelected(episode.episodeId); return ( ); })}
)} {!isLoadingEpisodes && episodes.length === 0 && (
暂无剧集信息
)}
)} {/* 显示搜索结果 */} {!selectedAnime && searchResults.length > 0 && (
{searchResults.map((anime) => (
handleAnimeSelect(anime)} className='flex cursor-pointer items-start gap-3 rounded-lg bg-gray-100 p-3 transition-colors hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700' > {/* 封面 */} {anime.imageUrl && (
{anime.animeTitle} { e.currentTarget.style.display = 'none'; }} />
)} {/* 信息 */}

{anime.animeTitle}

{/* 自定义 tooltip */}
{anime.animeTitle}
{anime.typeDescription || anime.type} {anime.episodeCount && ( {anime.episodeCount} 集 )} {anime.startDate && {anime.startDate}}
))}
)} {/* 空状态 */} {!selectedAnime && searchResults.length === 0 && !isSearching && (

输入剧集名称搜索弹幕

)}
{/* 上传弹幕区域 - 移动端:在滚动容器内 */} {onUploadDanmaku && (
)}
{/* 上传弹幕区域 - PC端:固定在底部 */} {onUploadDanmaku && (
)}
); }