diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 3a473a4..610d6b0 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -6181,11 +6181,11 @@ const SiteConfigComponent = ({ 评论配置 - {/* 开启评论 */} + {/* 开启评论与相似推荐 */}

- 开启后将显示豆瓣评论。评论为逆向抓取,请自行承担责任。 + 开启后将显示豆瓣评论与相似推荐。评论为逆向抓取,请自行承担责任。

@@ -6253,7 +6253,7 @@ const SiteConfigComponent = ({

- 开启评论功能 + 开启评论与相似推荐功能

+ {/* 豆瓣推荐区域 */} + {videoDoubanId !== 0 && enableComments && ( +
+
+ {/* 标题 */} +
+

+ + + + 更多推荐 +

+
+ + {/* 推荐内容 */} +
+ +
+
+
+ )} + {/* 豆瓣评论区域 */} {videoDoubanId !== 0 && enableComments && (
diff --git a/src/components/DoubanRecommendations.tsx b/src/components/DoubanRecommendations.tsx new file mode 100644 index 0000000..2301463 --- /dev/null +++ b/src/components/DoubanRecommendations.tsx @@ -0,0 +1,132 @@ +'use client'; + +import { useEffect, useState, useCallback } from 'react'; +import { useEnableComments } from '@/hooks/useEnableComments'; +import VideoCard from '@/components/VideoCard'; +import ScrollableRow from '@/components/ScrollableRow'; + +interface DoubanRecommendation { + doubanId: string; + title: string; + poster: string; + rating: string; +} + +interface DoubanRecommendationsProps { + doubanId: number; +} + +export default function DoubanRecommendations({ doubanId }: DoubanRecommendationsProps) { + const [recommendations, setRecommendations] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const enableComments = useEnableComments(); + + const fetchRecommendations = useCallback(async () => { + try { + console.log('正在获取推荐'); + setLoading(true); + setError(null); + + // 检查localStorage缓存 + const cacheKey = `douban_recommendations_${doubanId}`; + const cached = localStorage.getItem(cacheKey); + + if (cached) { + try { + const { data, timestamp } = JSON.parse(cached); + const cacheAge = Date.now() - timestamp; + const cacheMaxAge = 7 * 24 * 60 * 60 * 1000; // 7天 + + if (cacheAge < cacheMaxAge) { + console.log('使用缓存的推荐数据'); + setRecommendations(data); + setLoading(false); + return; + } + } catch (e) { + console.error('解析缓存失败:', e); + } + } + + const response = await fetch( + `/api/douban-recommendations?id=${doubanId}` + ); + + if (!response.ok) { + throw new Error('获取推荐失败'); + } + + const result = await response.json(); + console.log('获取到推荐:', result.recommendations); + + const recommendationsData = result.recommendations || []; + setRecommendations(recommendationsData); + + // 保存到localStorage + try { + localStorage.setItem(cacheKey, JSON.stringify({ + data: recommendationsData, + timestamp: Date.now() + })); + } catch (e) { + console.error('保存缓存失败:', e); + } + } catch (err) { + console.error('获取推荐失败:', err); + setError(err instanceof Error ? err.message : '获取推荐失败'); + } finally { + setLoading(false); + } + }, [doubanId]); + + useEffect(() => { + if (enableComments && doubanId) { + fetchRecommendations(); + } + }, [enableComments, doubanId, fetchRecommendations]); + + if (!enableComments) { + return null; + } + + if (loading) { + return ( +
+
+
+ ); + } + + if (error) { + return ( +
+ {error} +
+ ); + } + + if (recommendations.length === 0) { + return null; + } + + return ( + + {recommendations.map((rec) => ( +
+ +
+ ))} +
+ ); +} diff --git a/src/components/VideoCard.tsx b/src/components/VideoCard.tsx index 59b69a7..12aa34b 100644 --- a/src/components/VideoCard.tsx +++ b/src/components/VideoCard.tsx @@ -398,7 +398,7 @@ const VideoCard = forwardRef(function VideoCard showPlayButton: !isUpcoming, // 即将上映不显示播放按钮 showHeart: false, showCheckCircle: false, - showDoubanLink: true, + showDoubanLink: false, showRating: !!rate, showYear: false, }, diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 3c287b5..94d7c18 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -32,6 +32,11 @@ function getDoubanImageProxyConfig(): { export function processImageUrl(originalUrl: string): string { if (!originalUrl) return originalUrl; + // 如果已经是代理URL,直接返回 + if (originalUrl.startsWith('/api/image-proxy')) { + return originalUrl; + } + // 仅处理豆瓣图片代理 if (!originalUrl.includes('doubanio.com')) { return originalUrl;