From 1cff530f8a171108f39c65c0c793853c70969e60 Mon Sep 17 00:00:00 2001 From: lsy Date: Thu, 27 Mar 2025 21:40:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E8=B1=86=E7=93=A3?= =?UTF-8?q?=E8=AF=BB=E4=B9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/WorldHeatmap.tsx | 155 +++++++++++++++++++++++++++++--- src/pages/api/douban.ts | 92 +++++++++++++++---- 2 files changed, 214 insertions(+), 33 deletions(-) diff --git a/src/components/WorldHeatmap.tsx b/src/components/WorldHeatmap.tsx index 2ba32a9..a83d8cb 100644 --- a/src/components/WorldHeatmap.tsx +++ b/src/components/WorldHeatmap.tsx @@ -9,11 +9,22 @@ interface WorldHeatmapProps { const WorldHeatmap: React.FC = ({ visitedPlaces }) => { const chartRef = useRef(null); + const chartInstanceRef = useRef(null); useEffect(() => { if (!chartRef.current) return; - const chart = echarts.init(chartRef.current); + // 确保之前的实例被正确销毁 + if (chartInstanceRef.current) { + chartInstanceRef.current.dispose(); + } + + // 初始化图表并保存实例引用 + const chart = echarts.init(chartRef.current, null, { + renderer: 'canvas', + useDirtyRect: false + }); + chartInstanceRef.current = chart; const mergedWorldData = { ...worldData, @@ -41,17 +52,44 @@ const WorldHeatmap: React.FC = ({ visitedPlaces }) => { echarts.registerMap('merged-world', mergedWorldData as any); + // 检查当前是否为暗色模式 + const isDarkMode = document.documentElement.classList.contains('dark'); + + // 根据当前模式设置颜色 + const getChartColors = () => { + return { + textColor: isDarkMode ? '#ffffff' : '#374151', + borderColor: isDarkMode ? '#374151' : '#e5e7eb', + unvisitedColor: isDarkMode ? '#1f2937' : '#e5e7eb', + visitedColor: isDarkMode ? '#059669' : '#10b981', + emphasisColor: isDarkMode ? '#059669' : '#10b981', + tooltipBgColor: isDarkMode ? '#111827' : '#ffffff', + }; + }; + + const colors = getChartColors(); + + // 使用动态颜色方案 const option = { title: { text: '我的旅行足迹', left: 'center', - top: 20 + top: 20, + textStyle: { + color: colors.textColor, + fontWeight: 'bold' + } }, tooltip: { trigger: 'item', formatter: ({name}: {name: string}) => { const visited = visitedPlaces.includes(name); return `${name}
${visited ? '✓ 已去过' : '尚未去过'}`; + }, + backgroundColor: colors.tooltipBgColor, + borderColor: colors.borderColor, + textStyle: { + color: colors.textColor } }, visualMap: { @@ -62,13 +100,14 @@ const WorldHeatmap: React.FC = ({ visitedPlaces }) => { { value: 0, label: '未去过' } ], inRange: { - color: ['#e0e0e0', '#91cc75'] + color: [colors.unvisitedColor, colors.visitedColor] }, outOfRange: { - color: ['#e0e0e0'] + color: [colors.unvisitedColor] }, textStyle: { - color: '#333' + color: colors.textColor, + fontWeight: 500 } }, series: [{ @@ -78,12 +117,18 @@ const WorldHeatmap: React.FC = ({ visitedPlaces }) => { roam: true, emphasis: { label: { - show: true + show: true, + color: colors.textColor }, itemStyle: { - areaColor: '#91cc75' + areaColor: colors.emphasisColor } }, + itemStyle: { + borderColor: colors.borderColor, + borderWidth: 1, + borderType: 'solid' + }, data: mergedWorldData.features.map((feature: any) => ({ name: feature.properties.name, value: visitedPlaces.includes(feature.properties.name) ? 1 : 0 @@ -93,20 +138,102 @@ const WorldHeatmap: React.FC = ({ visitedPlaces }) => { }; chart.setOption(option); + + // 确保图表初始化后立即调整大小以适应容器 + chart.resize(); - window.addEventListener('resize', () => { - chart.resize(); + const handleResize = () => { + if (chartInstanceRef.current) { + chartInstanceRef.current.resize(); + } + }; + + window.addEventListener('resize', handleResize); + + // 监听暗色模式变化并更新图表 + const darkModeObserver = new MutationObserver(() => { + if (!chartRef.current) return; + + // 检查当前是否为暗色模式 + const newIsDarkMode = document.documentElement.classList.contains('dark'); + + if (chartInstanceRef.current) { + // 更新颜色设置 + const newColors = { + textColor: newIsDarkMode ? '#ffffff' : '#374151', + borderColor: newIsDarkMode ? '#4b5563' : '#d1d5db', + unvisitedColor: newIsDarkMode ? '#1f2937' : '#e5e7eb', + visitedColor: newIsDarkMode ? '#059669' : '#10b981', + emphasisColor: newIsDarkMode ? '#059669' : '#10b981', + tooltipBgColor: newIsDarkMode ? '#111827' : '#ffffff', + }; + + // 更新图表选项 + const newOption = { + title: { + textStyle: { + color: newColors.textColor + } + }, + tooltip: { + backgroundColor: newColors.tooltipBgColor, + borderColor: newColors.borderColor, + textStyle: { + color: newColors.textColor + } + }, + visualMap: { + inRange: { + color: [newColors.unvisitedColor, newColors.visitedColor] + }, + outOfRange: { + color: [newColors.unvisitedColor] + }, + textStyle: { + color: newColors.textColor + } + }, + series: [{ + emphasis: { + label: { + show: true, + color: newColors.textColor + }, + itemStyle: { + areaColor: newColors.emphasisColor + } + }, + itemStyle: { + borderColor: newColors.borderColor, + borderWidth: 1, + borderType: 'solid' + } + }] + }; + + // 应用新选项 + chartInstanceRef.current.setOption(newOption); + } }); + darkModeObserver.observe(document.documentElement, { attributes: true }); + return () => { - chart.dispose(); - window.removeEventListener('resize', () => { - chart.resize(); - }); + if (chartInstanceRef.current) { + chartInstanceRef.current.dispose(); + chartInstanceRef.current = null; + } + window.removeEventListener('resize', handleResize); + darkModeObserver.disconnect(); }; }, [visitedPlaces]); - return
; + return ( +
+ ); }; export default WorldHeatmap; \ No newline at end of file diff --git a/src/pages/api/douban.ts b/src/pages/api/douban.ts index 1cce6c9..5828ee3 100644 --- a/src/pages/api/douban.ts +++ b/src/pages/api/douban.ts @@ -4,6 +4,16 @@ import { load } from 'cheerio'; // 添加服务器渲染标记 export const prerender = false; +// 生成随机的bid Cookie值 +function generateBid() { + const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + let result = ''; + for (let i = 0; i < 11; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; +} + export const GET: APIRoute = async ({ request }) => { const url = new URL(request.url); const type = url.searchParams.get('type') || 'movie'; @@ -25,12 +35,19 @@ export const GET: APIRoute = async ({ request }) => { doubanUrl = `https://movie.douban.com/people/${doubanId}/collect?start=${start}&sort=time&rating=all&filter=all&mode=grid`; } + // 生成随机bid + const bid = generateBid(); + const response = await fetch(doubanUrl, { headers: { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', - 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', - 'Referer': 'https://movie.douban.com/' + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36', + 'Sec-Fetch-Site': 'none', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-User': '?1', + 'Sec-Fetch-Dest': 'document', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Accept-Language': 'zh-CN,zh;q=0.9', + 'Cookie': `bid=${bid}` } }); @@ -58,25 +75,62 @@ export const GET: APIRoute = async ({ request }) => { } const items: DoubanItem[] = []; - $('.item.comment-item').each((_, element) => { + + // 尝试不同的选择器 + let itemSelector = '.item.comment-item'; + let itemCount = $(itemSelector).length; + + if (itemCount === 0) { + // 尝试其他可能的选择器 + itemSelector = '.subject-item'; + itemCount = $(itemSelector).length; + } + + $(itemSelector).each((_, element) => { const $element = $(element); - const imageUrl = $element.find('.pic img').attr('src') || ''; - const title = $element.find('.title a em').text().trim(); - const subtitle = $element.find('.title a').text().replace(title, '').trim(); - const link = $element.find('.title a').attr('href') || ''; - const intro = $element.find('.intro').text().trim(); - - // 获取评分,从rating1-t到rating5-t + // 根据选择器调整查找逻辑 + let imageUrl = ''; + let title = ''; + let subtitle = ''; + let link = ''; + let intro = ''; let rating = 0; - for (let i = 1; i <= 5; i++) { - if ($element.find(`.rating${i}-t`).length > 0) { - rating = i; - break; - } - } + let date = ''; - const date = $element.find('.date').text().trim(); + if (itemSelector === '.item.comment-item') { + // 原始逻辑 + imageUrl = $element.find('.pic img').attr('src') || ''; + title = $element.find('.title a em').text().trim(); + subtitle = $element.find('.title a').text().replace(title, '').trim(); + link = $element.find('.title a').attr('href') || ''; + intro = $element.find('.intro').text().trim(); + + // 获取评分,从rating1-t到rating5-t + for (let i = 1; i <= 5; i++) { + if ($element.find(`.rating${i}-t`).length > 0) { + rating = i; + break; + } + } + + date = $element.find('.date').text().trim(); + } else if (itemSelector === '.subject-item') { + // 新的图书页面结构 + imageUrl = $element.find('.pic img').attr('src') || ''; + title = $element.find('.info h2 a').text().trim(); + link = $element.find('.info h2 a').attr('href') || ''; + intro = $element.find('.info .pub').text().trim(); + + // 获取评分 + const ratingClass = $element.find('.rating-star').attr('class') || ''; + const ratingMatch = ratingClass.match(/rating(\d)-t/); + if (ratingMatch) { + rating = parseInt(ratingMatch[1]); + } + + date = $element.find('.info .date').text().trim(); + } items.push({ imageUrl,