详情面板支持切换数据源
This commit is contained in:
@@ -326,6 +326,7 @@ function HomeClient() {
|
|||||||
rate={movie.rate}
|
rate={movie.rate}
|
||||||
type='movie'
|
type='movie'
|
||||||
from='douban'
|
from='douban'
|
||||||
|
douban_id={movie.id ? parseInt(movie.id) : undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -485,6 +486,7 @@ function HomeClient() {
|
|||||||
rate={tvShow.rate}
|
rate={tvShow.rate}
|
||||||
type='tv'
|
type='tv'
|
||||||
from='douban'
|
from='douban'
|
||||||
|
douban_id={tvShow.id ? parseInt(tvShow.id) : undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -531,6 +533,7 @@ function HomeClient() {
|
|||||||
rate={varietyShow.rate}
|
rate={varietyShow.rate}
|
||||||
type='tv'
|
type='tv'
|
||||||
from='douban'
|
from='douban'
|
||||||
|
douban_id={varietyShow.id ? parseInt(varietyShow.id) : undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
+352
-112
@@ -96,6 +96,12 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
const [showImageViewer, setShowImageViewer] = useState(false);
|
const [showImageViewer, setShowImageViewer] = useState(false);
|
||||||
const [selectedImage, setSelectedImage] = useState<string>('');
|
const [selectedImage, setSelectedImage] = useState<string>('');
|
||||||
|
|
||||||
|
// 数据源状态管理
|
||||||
|
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<DetailData | null>(null);
|
||||||
|
|
||||||
// 拖动滚动状态
|
// 拖动滚动状态
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const [isMouseDown, setIsMouseDown] = useState(false);
|
const [isMouseDown, setIsMouseDown] = useState(false);
|
||||||
@@ -218,17 +224,27 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 如果正在使用 TMDB 数据,强制使用 TMDB
|
||||||
|
if (isUsingTmdb && title) {
|
||||||
|
await fetchTmdbData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 优先使用苹果CMS数据(短剧等)
|
// 优先使用苹果CMS数据(短剧等)
|
||||||
// 如果 cmsData 存在但 desc 为空,尝试通过 source-detail API 获取
|
// 如果 cmsData 存在但 desc 为空,尝试通过 source-detail API 获取
|
||||||
if (cmsData) {
|
if (cmsData) {
|
||||||
|
setCurrentSource('cms');
|
||||||
|
setOriginalSource('cms');
|
||||||
if (cmsData.desc) {
|
if (cmsData.desc) {
|
||||||
// 有 desc,直接使用
|
// 有 desc,直接使用
|
||||||
setDetailData({
|
const data = {
|
||||||
title: title,
|
title: title,
|
||||||
intro: cmsData.desc,
|
intro: cmsData.desc,
|
||||||
episodesCount: cmsData.episodes?.length,
|
episodesCount: cmsData.episodes?.length,
|
||||||
poster: poster ? processImageUrl(poster) : poster,
|
poster: poster ? processImageUrl(poster) : poster,
|
||||||
});
|
};
|
||||||
|
setDetailData(data);
|
||||||
|
setOriginalDetailData(data);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -241,13 +257,15 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
);
|
);
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setDetailData({
|
const detailData = {
|
||||||
title: data.title || title,
|
title: data.title || title,
|
||||||
intro: data.desc || '',
|
intro: data.desc || '',
|
||||||
episodesCount: data.episodes?.length || cmsData.episodes?.length,
|
episodesCount: data.episodes?.length || cmsData.episodes?.length,
|
||||||
poster: data.poster ? processImageUrl(data.poster) : poster,
|
poster: data.poster ? processImageUrl(data.poster) : poster,
|
||||||
year: data.year,
|
year: data.year,
|
||||||
});
|
};
|
||||||
|
setDetailData(detailData);
|
||||||
|
setOriginalDetailData(detailData);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -260,6 +278,8 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
|
|
||||||
// 优先使用 Bangumi ID(因为 isBangumi 为 true 时,doubanId 实际上是 bangumiId)
|
// 优先使用 Bangumi ID(因为 isBangumi 为 true 时,doubanId 实际上是 bangumiId)
|
||||||
if (bangumiId || (isBangumi && doubanId)) {
|
if (bangumiId || (isBangumi && doubanId)) {
|
||||||
|
setCurrentSource('bangumi');
|
||||||
|
setOriginalSource('bangumi');
|
||||||
const actualBangumiId = bangumiId || doubanId;
|
const actualBangumiId = bangumiId || doubanId;
|
||||||
const response = await fetch(`https://api.bgm.tv/v0/subjects/${actualBangumiId}`);
|
const response = await fetch(`https://api.bgm.tv/v0/subjects/${actualBangumiId}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -267,7 +287,7 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
setDetailData({
|
const detailData = {
|
||||||
title: data.name_cn || data.name,
|
title: data.name_cn || data.name,
|
||||||
originalTitle: data.name,
|
originalTitle: data.name,
|
||||||
year: data.date ? data.date.substring(0, 4) : undefined,
|
year: data.date ? data.date.substring(0, 4) : undefined,
|
||||||
@@ -282,19 +302,23 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
genres: data.tags?.map((tag: any) => tag.name).slice(0, 5),
|
genres: data.tags?.map((tag: any) => tag.name).slice(0, 5),
|
||||||
episodesCount: data.eps,
|
episodesCount: data.eps,
|
||||||
releaseDate: data.date,
|
releaseDate: data.date,
|
||||||
});
|
};
|
||||||
|
setDetailData(detailData);
|
||||||
|
setOriginalDetailData(detailData);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用豆瓣ID
|
// 使用豆瓣ID
|
||||||
if (doubanId && !isBangumi) {
|
if (doubanId && !isBangumi) {
|
||||||
|
setCurrentSource('douban');
|
||||||
|
setOriginalSource('douban');
|
||||||
const response = await fetch(`/api/douban/detail?id=${doubanId}`);
|
const response = await fetch(`/api/douban/detail?id=${doubanId}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('获取豆瓣详情失败');
|
throw new Error('获取豆瓣详情失败');
|
||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
setDetailData({
|
const detailData = {
|
||||||
title: data.title,
|
title: data.title,
|
||||||
originalTitle: data.original_title,
|
originalTitle: data.original_title,
|
||||||
year: data.year,
|
year: data.year,
|
||||||
@@ -313,113 +337,18 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
languages: data.languages,
|
languages: data.languages,
|
||||||
duration: data.durations?.[0],
|
duration: data.durations?.[0],
|
||||||
episodesCount: data.episodes_count,
|
episodesCount: data.episodes_count,
|
||||||
});
|
};
|
||||||
|
setDetailData(detailData);
|
||||||
|
setOriginalDetailData(detailData);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用 TMDB 搜索
|
// 使用 TMDB 搜索
|
||||||
if (title) {
|
if (title) {
|
||||||
// 移除季度信息进行搜索
|
setCurrentSource('tmdb');
|
||||||
let searchTitle = title;
|
setOriginalSource('tmdb');
|
||||||
let extractedSeasonNumber = seasonNumber;
|
await fetchTmdbData();
|
||||||
|
return;
|
||||||
// 匹配各种季度格式: 第一季、第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<string, number> = {
|
|
||||||
'一': 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('未找到相关内容');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('缺少必要的查询参数');
|
throw new Error('缺少必要的查询参数');
|
||||||
@@ -431,8 +360,251 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 提取 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<string, number> = {
|
||||||
|
'一': 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();
|
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<string, number> = {
|
||||||
|
'一': 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)
|
// 异步获取季度和集数详情(仅TMDB)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -608,8 +780,43 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="p-6 text-center">
|
<div className="p-6">
|
||||||
<p className="text-red-500 dark:text-red-400">{error}</p>
|
<div className="text-center mb-6">
|
||||||
|
<p className="text-red-500 dark:text-red-400">{error}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 数据源显示和切换 - 错误时也显示 */}
|
||||||
|
<div className="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm text-gray-500 dark:text-gray-400">数据来源:</span>
|
||||||
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-300 uppercase">
|
||||||
|
{currentSource === 'douban' && 'Douban'}
|
||||||
|
{currentSource === 'bangumi' && 'Bangumi'}
|
||||||
|
{currentSource === 'cms' && 'CMS'}
|
||||||
|
{currentSource === 'tmdb' && 'TMDB'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{currentSource !== 'tmdb' && (
|
||||||
|
<button
|
||||||
|
onClick={handleToggleSource}
|
||||||
|
disabled={loading}
|
||||||
|
className="px-3 py-1.5 text-sm rounded-lg bg-green-500 hover:bg-green-600 text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
切换到 TMDB
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{currentSource === 'tmdb' && originalSource !== 'tmdb' && originalDetailData && (
|
||||||
|
<button
|
||||||
|
onClick={handleToggleSource}
|
||||||
|
disabled={loading}
|
||||||
|
className="px-3 py-1.5 text-sm rounded-lg bg-gray-500 hover:bg-gray-600 text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
切换回 {originalSource === 'douban' ? 'Douban' : originalSource === 'bangumi' ? 'Bangumi' : 'CMS'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -907,6 +1114,39 @@ const DetailPanel: React.FC<DetailPanelProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 数据源显示和切换 */}
|
||||||
|
<div className="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm text-gray-500 dark:text-gray-400">数据来源:</span>
|
||||||
|
<span className="text-sm font-medium text-gray-700 dark:text-gray-300 uppercase">
|
||||||
|
{currentSource === 'douban' && 'Douban'}
|
||||||
|
{currentSource === 'bangumi' && 'Bangumi'}
|
||||||
|
{currentSource === 'cms' && 'CMS'}
|
||||||
|
{currentSource === 'tmdb' && 'TMDB'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{currentSource !== 'tmdb' && (
|
||||||
|
<button
|
||||||
|
onClick={handleToggleSource}
|
||||||
|
disabled={loading}
|
||||||
|
className="px-3 py-1.5 text-sm rounded-lg bg-green-500 hover:bg-green-600 text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
切换到 TMDB
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{currentSource === 'tmdb' && originalSource !== 'tmdb' && originalDetailData && (
|
||||||
|
<button
|
||||||
|
onClick={handleToggleSource}
|
||||||
|
disabled={loading}
|
||||||
|
className="px-3 py-1.5 text-sm rounded-lg bg-gray-500 hover:bg-gray-600 text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
切换回 {originalSource === 'douban' ? 'Douban' : originalSource === 'bangumi' ? 'Bangumi' : 'CMS'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user