From 54dd196af4bdcf483ea35525deaa56d630b9e883 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Tue, 17 Feb 2026 00:46:12 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AF=A6=E6=83=85=E9=9D=A2=E6=9D=BF=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=88=87=E6=8D=A2=E6=95=B0=E6=8D=AE=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/page.tsx | 3 + src/components/DetailPanel.tsx | 464 +++++++++++++++++++++++++-------- 2 files changed, 355 insertions(+), 112 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 074625e..df41650 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -326,6 +326,7 @@ function HomeClient() { rate={movie.rate} type='movie' from='douban' + douban_id={movie.id ? parseInt(movie.id) : undefined} /> ))} @@ -485,6 +486,7 @@ function HomeClient() { rate={tvShow.rate} type='tv' from='douban' + douban_id={tvShow.id ? parseInt(tvShow.id) : undefined} /> ))} @@ -531,6 +533,7 @@ function HomeClient() { rate={varietyShow.rate} type='tv' from='douban' + douban_id={varietyShow.id ? parseInt(varietyShow.id) : undefined} /> ))} diff --git a/src/components/DetailPanel.tsx b/src/components/DetailPanel.tsx index 1270885..d38f7fa 100644 --- a/src/components/DetailPanel.tsx +++ b/src/components/DetailPanel.tsx @@ -96,6 +96,12 @@ const DetailPanel: React.FC = ({ const [showImageViewer, setShowImageViewer] = useState(false); const [selectedImage, setSelectedImage] = useState(''); + // 数据源状态管理 + const [currentSource, setCurrentSource] = useState<'douban' | 'bangumi' | 'cms' | 'tmdb'>('tmdb'); + const [originalSource, setOriginalSource] = useState<'douban' | 'bangumi' | 'cms' | 'tmdb'>('tmdb'); + const [isUsingTmdb, setIsUsingTmdb] = useState(false); + const [originalDetailData, setOriginalDetailData] = useState(null); + // 拖动滚动状态 const [isDragging, setIsDragging] = useState(false); const [isMouseDown, setIsMouseDown] = useState(false); @@ -218,17 +224,27 @@ const DetailPanel: React.FC = ({ setError(null); try { + // 如果正在使用 TMDB 数据,强制使用 TMDB + if (isUsingTmdb && title) { + await fetchTmdbData(); + return; + } + // 优先使用苹果CMS数据(短剧等) // 如果 cmsData 存在但 desc 为空,尝试通过 source-detail API 获取 if (cmsData) { + setCurrentSource('cms'); + setOriginalSource('cms'); if (cmsData.desc) { // 有 desc,直接使用 - setDetailData({ + const data = { title: title, intro: cmsData.desc, episodesCount: cmsData.episodes?.length, poster: poster ? processImageUrl(poster) : poster, - }); + }; + setDetailData(data); + setOriginalDetailData(data); setLoading(false); return; } @@ -241,13 +257,15 @@ const DetailPanel: React.FC = ({ ); if (response.ok) { const data = await response.json(); - setDetailData({ + const detailData = { title: data.title || title, intro: data.desc || '', episodesCount: data.episodes?.length || cmsData.episodes?.length, poster: data.poster ? processImageUrl(data.poster) : poster, year: data.year, - }); + }; + setDetailData(detailData); + setOriginalDetailData(detailData); setLoading(false); return; } @@ -260,6 +278,8 @@ const DetailPanel: React.FC = ({ // 优先使用 Bangumi ID(因为 isBangumi 为 true 时,doubanId 实际上是 bangumiId) if (bangumiId || (isBangumi && doubanId)) { + setCurrentSource('bangumi'); + setOriginalSource('bangumi'); const actualBangumiId = bangumiId || doubanId; const response = await fetch(`https://api.bgm.tv/v0/subjects/${actualBangumiId}`); if (!response.ok) { @@ -267,7 +287,7 @@ const DetailPanel: React.FC = ({ } const data = await response.json(); - setDetailData({ + const detailData = { title: data.name_cn || data.name, originalTitle: data.name, year: data.date ? data.date.substring(0, 4) : undefined, @@ -282,19 +302,23 @@ const DetailPanel: React.FC = ({ genres: data.tags?.map((tag: any) => tag.name).slice(0, 5), episodesCount: data.eps, releaseDate: data.date, - }); + }; + setDetailData(detailData); + setOriginalDetailData(detailData); return; } // 使用豆瓣ID if (doubanId && !isBangumi) { + setCurrentSource('douban'); + setOriginalSource('douban'); const response = await fetch(`/api/douban/detail?id=${doubanId}`); if (!response.ok) { throw new Error('获取豆瓣详情失败'); } const data = await response.json(); - setDetailData({ + const detailData = { title: data.title, originalTitle: data.original_title, year: data.year, @@ -313,113 +337,18 @@ const DetailPanel: React.FC = ({ languages: data.languages, duration: data.durations?.[0], episodesCount: data.episodes_count, - }); + }; + setDetailData(detailData); + setOriginalDetailData(detailData); return; } // 使用 TMDB 搜索 if (title) { - // 移除季度信息进行搜索 - let searchTitle = title; - let extractedSeasonNumber = seasonNumber; - - // 匹配各种季度格式: 第一季、第1季、第一部、Season 1、S1等 - const seasonPatterns = [ - /第([一二三四五六七八九十\d]+)[季部]/, - /Season\s*(\d+)/i, - /S(\d+)/i, - ]; - - for (const pattern of seasonPatterns) { - const match = title.match(pattern); - if (match) { - searchTitle = title.replace(pattern, '').trim(); - // 如果没有传入seasonNumber,尝试从标题中提取 - if (!extractedSeasonNumber) { - const seasonStr = match[1]; - // 中文数字转数字 - const chineseNumbers: Record = { - '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, - '六': 6, '七': 7, '八': 8, '九': 9, '十': 10, - }; - extractedSeasonNumber = chineseNumbers[seasonStr] || parseInt(seasonStr) || undefined; - } - break; - } - } - - const searchResponse = await fetch( - `/api/tmdb/search?query=${encodeURIComponent(searchTitle)}` - ); - if (!searchResponse.ok) { - throw new Error('搜索失败'); - } - const searchData = await searchResponse.json(); - - if (searchData.results && searchData.results.length > 0) { - const result = searchData.results[0]; - const detailId = result.id; - const mediaType = result.media_type || type; - - // 获取详情 - const detailResponse = await fetch(`/api/tmdb/detail?id=${detailId}&type=${mediaType}`); - if (!detailResponse.ok) { - throw new Error('获取TMDB详情失败'); - } - const detailResult = await detailResponse.json(); - - // 如果有季度信息,尝试获取季度详情 - let seasonData = null; - if (extractedSeasonNumber && mediaType === 'tv') { - try { - const seasonResponse = await fetch( - `/api/tmdb/seasons?id=${detailId}&season=${extractedSeasonNumber}` - ); - if (seasonResponse.ok) { - seasonData = await seasonResponse.json(); - } - } catch (err) { - console.error('获取季度信息失败', err); - } - } - - setDetailData({ - title: mediaType === 'movie' ? detailResult.title : detailResult.name, - originalTitle: - mediaType === 'movie' ? detailResult.original_title : detailResult.original_name, - year: - mediaType === 'movie' - ? detailResult.release_date?.substring(0, 4) - : detailResult.first_air_date?.substring(0, 4), - poster: detailResult.poster_path - ? processImageUrl(getTMDBImageUrl(detailResult.poster_path, 'w500')) - : poster, - rating: detailResult.vote_average - ? { - value: detailResult.vote_average, - count: detailResult.vote_count, - } - : undefined, - intro: seasonData?.overview || detailResult.overview, - genres: detailResult.genres?.map((g: any) => g.name), - countries: detailResult.production_countries?.map((c: any) => c.name), - languages: detailResult.spoken_languages?.map((l: any) => l.name), - duration: detailResult.runtime ? `${detailResult.runtime}分钟` : undefined, - episodesCount: seasonData?.episodes?.length || detailResult.number_of_episodes, - releaseDate: - mediaType === 'movie' ? detailResult.release_date : detailResult.first_air_date, - status: detailResult.status, - tagline: detailResult.tagline, - seasons: detailResult.number_of_seasons, - overview: detailResult.overview, - tmdbId: detailId, - mediaType: mediaType, - seasonNumber: extractedSeasonNumber, - }); - return; - } - - throw new Error('未找到相关内容'); + setCurrentSource('tmdb'); + setOriginalSource('tmdb'); + await fetchTmdbData(); + return; } throw new Error('缺少必要的查询参数'); @@ -431,8 +360,251 @@ const DetailPanel: React.FC = ({ } }; + // 提取 TMDB 数据获取逻辑为独立函数 + const fetchTmdbData = async () => { + setCurrentSource('tmdb'); + // 移除季度信息进行搜索 + let searchTitle = title; + let extractedSeasonNumber = seasonNumber; + + // 匹配各种季度格式: 第一季、第1季、第一部、Season 1、S1等 + const seasonPatterns = [ + /第([一二三四五六七八九十\d]+)[季部]/, + /Season\s*(\d+)/i, + /S(\d+)/i, + ]; + + for (const pattern of seasonPatterns) { + const match = title.match(pattern); + if (match) { + searchTitle = title.replace(pattern, '').trim(); + // 如果没有传入seasonNumber,尝试从标题中提取 + if (!extractedSeasonNumber) { + const seasonStr = match[1]; + // 中文数字转数字 + const chineseNumbers: Record = { + '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, + '六': 6, '七': 7, '八': 8, '九': 9, '十': 10, + }; + extractedSeasonNumber = chineseNumbers[seasonStr] || parseInt(seasonStr) || undefined; + } + break; + } + } + + const searchResponse = await fetch( + `/api/tmdb/search?query=${encodeURIComponent(searchTitle)}` + ); + if (!searchResponse.ok) { + throw new Error('搜索失败'); + } + const searchData = await searchResponse.json(); + + if (searchData.results && searchData.results.length > 0) { + const result = searchData.results[0]; + const detailId = result.id; + const mediaType = result.media_type || type; + + // 获取详情 + const detailResponse = await fetch(`/api/tmdb/detail?id=${detailId}&type=${mediaType}`); + if (!detailResponse.ok) { + throw new Error('获取TMDB详情失败'); + } + const detailResult = await detailResponse.json(); + + // 如果有季度信息,尝试获取季度详情 + let seasonData = null; + if (extractedSeasonNumber && mediaType === 'tv') { + try { + const seasonResponse = await fetch( + `/api/tmdb/seasons?id=${detailId}&season=${extractedSeasonNumber}` + ); + if (seasonResponse.ok) { + seasonData = await seasonResponse.json(); + } + } catch (err) { + console.error('获取季度信息失败', err); + } + } + + setDetailData({ + title: mediaType === 'movie' ? detailResult.title : detailResult.name, + originalTitle: + mediaType === 'movie' ? detailResult.original_title : detailResult.original_name, + year: + mediaType === 'movie' + ? detailResult.release_date?.substring(0, 4) + : detailResult.first_air_date?.substring(0, 4), + poster: detailResult.poster_path + ? processImageUrl(getTMDBImageUrl(detailResult.poster_path, 'w500')) + : poster, + rating: detailResult.vote_average + ? { + value: detailResult.vote_average, + count: detailResult.vote_count, + } + : undefined, + intro: seasonData?.overview || detailResult.overview, + genres: detailResult.genres?.map((g: any) => g.name), + countries: detailResult.production_countries?.map((c: any) => c.name), + languages: detailResult.spoken_languages?.map((l: any) => l.name), + duration: detailResult.runtime ? `${detailResult.runtime}分钟` : undefined, + episodesCount: seasonData?.episodes?.length || detailResult.number_of_episodes, + releaseDate: + mediaType === 'movie' ? detailResult.release_date : detailResult.first_air_date, + status: detailResult.status, + tagline: detailResult.tagline, + seasons: detailResult.number_of_seasons, + overview: detailResult.overview, + tmdbId: detailId, + mediaType: mediaType, + seasonNumber: extractedSeasonNumber, + }); + return; + } + + throw new Error('未找到相关内容'); + }; + fetchDetail(); - }, [isOpen, doubanId, bangumiId, isBangumi, tmdbId, title, type, seasonNumber, poster, cmsData, sourceId, source]); + }, [isOpen, doubanId, bangumiId, isBangumi, tmdbId, title, type, seasonNumber, poster, cmsData, sourceId, source, isUsingTmdb]); + + // 切换数据源的函数 + const handleToggleSource = async () => { + if (currentSource === 'tmdb') { + // 切换回原始数据源 + if (originalDetailData) { + setDetailData(originalDetailData); + setCurrentSource(originalSource); + setError(null); + } + } else { + // 切换到 TMDB + // 保存当前数据 + if (detailData && !originalDetailData) { + setOriginalDetailData(detailData); + } + + setLoading(true); + setError(null); + try { + await fetchTmdbDataForToggle(); + } catch (err) { + console.error('切换到TMDB失败:', err); + setError(err instanceof Error ? err.message : '切换到TMDB失败'); + // 切换失败,但保持 currentSource 为 tmdb,这样可以显示切换回按钮 + setCurrentSource('tmdb'); + } finally { + setLoading(false); + } + } + }; + + // 用于切换时获取 TMDB 数据 + const fetchTmdbDataForToggle = async () => { + // 移除季度信息进行搜索 + let searchTitle = title; + let extractedSeasonNumber = seasonNumber; + + // 匹配各种季度格式: 第一季、第1季、第一部、Season 1、S1等 + const seasonPatterns = [ + /第([一二三四五六七八九十\d]+)[季部]/, + /Season\s*(\d+)/i, + /S(\d+)/i, + ]; + + for (const pattern of seasonPatterns) { + const match = title.match(pattern); + if (match) { + searchTitle = title.replace(pattern, '').trim(); + // 如果没有传入seasonNumber,尝试从标题中提取 + if (!extractedSeasonNumber) { + const seasonStr = match[1]; + // 中文数字转数字 + const chineseNumbers: Record = { + '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, + '六': 6, '七': 7, '八': 8, '九': 9, '十': 10, + }; + extractedSeasonNumber = chineseNumbers[seasonStr] || parseInt(seasonStr) || undefined; + } + break; + } + } + + const searchResponse = await fetch( + `/api/tmdb/search?query=${encodeURIComponent(searchTitle)}` + ); + if (!searchResponse.ok) { + throw new Error('搜索失败'); + } + const searchData = await searchResponse.json(); + + if (searchData.results && searchData.results.length > 0) { + const result = searchData.results[0]; + const detailId = result.id; + const mediaType = result.media_type || type; + + // 获取详情 + const detailResponse = await fetch(`/api/tmdb/detail?id=${detailId}&type=${mediaType}`); + if (!detailResponse.ok) { + throw new Error('获取TMDB详情失败'); + } + const detailResult = await detailResponse.json(); + + // 如果有季度信息,尝试获取季度详情 + let seasonData = null; + if (extractedSeasonNumber && mediaType === 'tv') { + try { + const seasonResponse = await fetch( + `/api/tmdb/seasons?id=${detailId}&season=${extractedSeasonNumber}` + ); + if (seasonResponse.ok) { + seasonData = await seasonResponse.json(); + } + } catch (err) { + console.error('获取季度信息失败', err); + } + } + + setDetailData({ + title: mediaType === 'movie' ? detailResult.title : detailResult.name, + originalTitle: + mediaType === 'movie' ? detailResult.original_title : detailResult.original_name, + year: + mediaType === 'movie' + ? detailResult.release_date?.substring(0, 4) + : detailResult.first_air_date?.substring(0, 4), + poster: detailResult.poster_path + ? processImageUrl(getTMDBImageUrl(detailResult.poster_path, 'w500')) + : poster, + rating: detailResult.vote_average + ? { + value: detailResult.vote_average, + count: detailResult.vote_count, + } + : undefined, + intro: seasonData?.overview || detailResult.overview, + genres: detailResult.genres?.map((g: any) => g.name), + countries: detailResult.production_countries?.map((c: any) => c.name), + languages: detailResult.spoken_languages?.map((l: any) => l.name), + duration: detailResult.runtime ? `${detailResult.runtime}分钟` : undefined, + episodesCount: seasonData?.episodes?.length || detailResult.number_of_episodes, + releaseDate: + mediaType === 'movie' ? detailResult.release_date : detailResult.first_air_date, + status: detailResult.status, + tagline: detailResult.tagline, + seasons: detailResult.number_of_seasons, + overview: detailResult.overview, + tmdbId: detailId, + mediaType: mediaType, + seasonNumber: extractedSeasonNumber, + }); + setCurrentSource('tmdb'); + return; + } + + throw new Error('未找到相关内容'); + }; // 异步获取季度和集数详情(仅TMDB) useEffect(() => { @@ -608,8 +780,43 @@ const DetailPanel: React.FC = ({ )} {error && ( -
-

{error}

+
+
+

{error}

+
+ + {/* 数据源显示和切换 - 错误时也显示 */} +
+
+
+ 数据来源: + + {currentSource === 'douban' && 'Douban'} + {currentSource === 'bangumi' && 'Bangumi'} + {currentSource === 'cms' && 'CMS'} + {currentSource === 'tmdb' && 'TMDB'} + +
+ {currentSource !== 'tmdb' && ( + + )} + {currentSource === 'tmdb' && originalSource !== 'tmdb' && originalDetailData && ( + + )} +
+
)} @@ -907,6 +1114,39 @@ const DetailPanel: React.FC = ({ )}
)} + + {/* 数据源显示和切换 */} +
+
+
+ 数据来源: + + {currentSource === 'douban' && 'Douban'} + {currentSource === 'bangumi' && 'Bangumi'} + {currentSource === 'cms' && 'CMS'} + {currentSource === 'tmdb' && 'TMDB'} + +
+ {currentSource !== 'tmdb' && ( + + )} + {currentSource === 'tmdb' && originalSource !== 'tmdb' && originalDetailData && ( + + )} +
+
)}