From 4a1f2d676cd1b1bd8de77fd3173ce5db84c02b10 Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Tue, 6 Jan 2026 18:25:48 +0800 Subject: [PATCH] =?UTF-8?q?community=E5=A2=9E=E5=8A=A0=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EventDetailPanel/CompactMetaBar.js | 30 ++++++- .../components/DynamicNews/DynamicNewsCard.js | 82 ++++++++++++++++++- src/views/Community/index.js | 12 ++- 3 files changed, 119 insertions(+), 5 deletions(-) diff --git a/src/components/EventDetailPanel/CompactMetaBar.js b/src/components/EventDetailPanel/CompactMetaBar.js index fa6f0f98..44a17a70 100644 --- a/src/components/EventDetailPanel/CompactMetaBar.js +++ b/src/components/EventDetailPanel/CompactMetaBar.js @@ -7,11 +7,15 @@ import { Badge, Text, Icon, + IconButton, + Tooltip, useColorModeValue, } from '@chakra-ui/react'; +import { useNavigate } from 'react-router-dom'; import { EventFollowButton } from '@views/Community/components/EventCard/atoms'; -import { Eye } from 'lucide-react'; +import { Eye, ExternalLink } from 'lucide-react'; import ShareButton from '@components/ShareButton'; +import { getEventDetailUrl } from '@utils/idEncoder'; /** * 精简信息栏组件 @@ -26,9 +30,17 @@ import ShareButton from '@components/ShareButton'; * @param {Function} props.onToggleFollow - 切换关注回调 */ const CompactMetaBar = ({ event, importance, isFollowing, followerCount, onToggleFollow }) => { + const navigate = useNavigate(); const viewCountBg = useColorModeValue('white', 'gray.700'); const viewCountTextColor = useColorModeValue('gray.600', 'gray.300'); + // 跳转到事件详情页 + const handleViewDetail = () => { + if (event?.id) { + navigate(getEventDetailUrl(event.id)); + } + }; + // 获取重要性文本 const getImportanceText = () => { const levelMap = { @@ -104,6 +116,22 @@ const CompactMetaBar = ({ event, importance, isFollowing, followerCount, onToggl variant="icon" size="sm" /> + + {/* 查看详情按钮 */} + + } + aria-label="查看事件详情" + size="sm" + variant="ghost" + colorScheme="blue" + onClick={handleViewDetail} + _hover={{ + bg: 'blue.500', + color: 'white', + }} + /> + ); }; diff --git a/src/views/Community/components/DynamicNews/DynamicNewsCard.js b/src/views/Community/components/DynamicNews/DynamicNewsCard.js index e09bdd95..c5b9a8e2 100644 --- a/src/views/Community/components/DynamicNews/DynamicNewsCard.js +++ b/src/views/Community/components/DynamicNews/DynamicNewsCard.js @@ -42,6 +42,7 @@ import { PAGINATION_CONFIG, DISPLAY_MODES, REFRESH_DEBOUNCE_DELAY } from './cons import { PROFESSIONAL_COLORS } from '@constants/professionalTheme'; import { debounce } from '@utils/debounce'; import { useDevice } from '@hooks/useDevice'; +import { eventService } from '@services/eventService'; // 🔍 调试:渲染计数器 let dynamicNewsCardRenderCount = 0; @@ -55,6 +56,7 @@ let dynamicNewsCardRenderCount = 0; * @param {Function} onSearchFocus - 搜索框获得焦点回调 * @param {Function} onEventClick - 事件点击回调 * @param {Function} onViewDetail - 查看详情回调 + * @param {number|null} initialEventId - 从 URL 获取的初始事件 ID(用于分享链接定位) * @param {Object} trackingFunctions - PostHog 追踪函数集合 * @param {Object} ref - 用于滚动的ref */ @@ -66,6 +68,7 @@ const DynamicNewsCardComponent = forwardRef(({ onSearchFocus, onEventClick, onViewDetail, + initialEventId = null, trackingFunctions = {}, ...rest }, ref) => { @@ -183,6 +186,8 @@ const [currentMode, setCurrentMode] = useState('vertical'); const hasAutoSelectedFirstEvent = useRef(false); // 追踪筛选条件 useEffect 是否是第一次渲染(避免初始加载时重复请求) const isFirstRenderForFilters = useRef(true); + // 追踪是否已尝试从 API 获取 URL 指定的事件 + const hasTriedFetchingInitialEvent = useRef(false); // 使用分页 Hook const { @@ -395,6 +400,7 @@ const [currentMode, setCurrentMode] = useState('vertical'); hasInitialized.current = false; isFirstRenderForFilters.current = true; hasAutoSelectedFirstEvent.current = false; + hasTriedFetchingInitialEvent.current = false; console.log('%c🧹 [卸载] 重置初始化标记', 'color: #F59E0B; font-weight: bold;'); }; }, [dispatch, mode, pageSize]); // 移除 currentMode 依赖,避免模式切换时重复请求 @@ -477,9 +483,81 @@ const [currentMode, setCurrentMode] = useState('vertical'); } }, [mode, currentMode, allCachedEventsByPage, allCachedEvents, dispatch, filters]); // 添加 filters 依赖 - // 自动选中逻辑 - 只在首次加载时自动选中第一个事件,翻页时不自动选中 + // ⚡ URL 指定事件 ID 时,从 API 获取事件详情(用于分享链接直接定位) + useEffect(() => { + // 只有在有 initialEventId 且还没尝试获取过时才执行 + if (!initialEventId || hasTriedFetchingInitialEvent.current) return; + + // 等待数据加载完成 + if (loading) return; + + // 检查事件是否已在当前页数据中 + const eventInCurrentPage = currentPageEvents.find(e => e.id === initialEventId); + if (eventInCurrentPage) { + // 事件在当前页,标记已尝试,让自动选中逻辑处理 + hasTriedFetchingInitialEvent.current = true; + return; + } + + // 事件不在当前页,从 API 获取 + hasTriedFetchingInitialEvent.current = true; + console.log('%c🔍 [URL定位] 事件不在当前页,从 API 获取详情', 'color: #8B5CF6; font-weight: bold;', { initialEventId }); + + eventService.getEventDetail(initialEventId) + .then(response => { + if (response.success && response.data) { + console.log('%c✅ [URL定位] 成功获取事件详情', 'color: #10B981; font-weight: bold;', { event: response.data.title }); + hasAutoSelectedFirstEvent.current = true; + setSelectedEvent(response.data); + + // 🎯 追踪事件点击(URL 定位 - API 获取) + if (trackingFunctions.trackNewsArticleClicked) { + trackingFunctions.trackNewsArticleClicked({ + eventId: response.data.id, + eventTitle: response.data.title, + importance: response.data.importance, + source: 'url_param_api', + displayMode: mode, + timestamp: new Date().toISOString(), + }); + } + } else { + console.log('%c⚠️ [URL定位] 事件不存在或获取失败', 'color: #F59E0B;', { initialEventId }); + } + }) + .catch(error => { + console.error('%c❌ [URL定位] 获取事件详情失败', 'color: #EF4444;', error); + }); + }, [initialEventId, loading, currentPageEvents, mode, trackingFunctions]); + + // 自动选中逻辑 - 支持从 URL 参数定位到指定事件 useEffect(() => { if (currentPageEvents.length > 0) { + // 情况0: URL 指定了初始事件 ID,优先定位到该事件 + if (initialEventId && !hasAutoSelectedFirstEvent.current) { + const targetEvent = currentPageEvents.find(e => e.id === initialEventId); + if (targetEvent) { + console.log('%c🎯 [URL定位] 自动选中 URL 指定的事件', 'color: #8B5CF6; font-weight: bold;', { eventId: initialEventId }); + hasAutoSelectedFirstEvent.current = true; + setSelectedEvent(targetEvent); + + // 🎯 追踪事件点击(URL 定位) + if (trackingFunctions.trackNewsArticleClicked) { + trackingFunctions.trackNewsArticleClicked({ + eventId: targetEvent.id, + eventTitle: targetEvent.title, + importance: targetEvent.importance, + source: 'url_param', + displayMode: mode, + timestamp: new Date().toISOString(), + }); + } + return; + } + // 如果在当前页没找到,继续等待数据加载(不阻止首次自动选中) + console.log('%c⏳ [URL定位] 目标事件不在当前页,继续等待...', 'color: #F59E0B;', { initialEventId }); + } + // 情况1: 首次加载 - 自动选中第一个事件并触发详情加载 if (!hasAutoSelectedFirstEvent.current && !selectedEvent) { console.log('%c🎯 [首次加载] 自动选中第一个事件', 'color: #10B981; font-weight: bold;'); @@ -505,7 +583,7 @@ const [currentMode, setCurrentMode] = useState('vertical'); e => e.id === selectedEvent?.id ); } - }, [currentPageEvents, selectedEvent?.id, mode, trackingFunctions]); + }, [currentPageEvents, selectedEvent?.id, mode, trackingFunctions, initialEventId]); // 组件卸载时清理选中状态 useEffect(() => { diff --git a/src/views/Community/index.js b/src/views/Community/index.js index 3b397414..61e2db17 100644 --- a/src/views/Community/index.js +++ b/src/views/Community/index.js @@ -1,6 +1,6 @@ // src/views/Community/index.js -import React, { useEffect, useRef, lazy, Suspense } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; +import React, { useEffect, useRef, lazy, Suspense, useMemo } from 'react'; +import { useNavigate, useLocation, useSearchParams } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; import { fetchPopularKeywords, @@ -32,8 +32,15 @@ import { flushPendingEventsBeforeUnload } from '@/utils/trackingHelpers'; const Community = () => { const navigate = useNavigate(); const location = useLocation(); // ⚡ 获取当前路由信息(用于判断是否在 /community 页面) + const [searchParams] = useSearchParams(); const dispatch = useDispatch(); + // 从 URL 获取初始事件 ID(用于分享链接直接定位到指定事件) + const initialEventId = useMemo(() => { + const eventId = searchParams.get('event'); + return eventId ? parseInt(eventId, 10) : null; + }, [searchParams]); + // Redux状态 const { popularKeywords, hotEvents } = useSelector(state => state.communityData); @@ -166,6 +173,7 @@ const Community = () => { onSearch={updateFilters} onEventClick={handleEventClick} onViewDetail={handleViewDetail} + initialEventId={initialEventId} // ⚡ 从 URL 获取的初始事件 ID trackingFunctions={{ trackNewsArticleClicked: communityEvents.trackNewsArticleClicked, trackNewsDetailOpened: communityEvents.trackNewsDetailOpened,