// src/views/Community/index.js import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; import { fetchPopularKeywords, fetchHotEvents } from '../../store/slices/communityDataSlice'; import { Box, Container, useColorModeValue, } from '@chakra-ui/react'; // 导入组件 import EventTimelineCard from './components/EventTimelineCard'; import DynamicNewsCard from './components/DynamicNewsCard'; import MarketReviewCard from './components/MarketReviewCard'; import HotEventsSection from './components/HotEventsSection'; import EventModals from './components/EventModals'; // 导入自定义 Hooks import { useEventData } from './hooks/useEventData'; import { useEventFilters } from './hooks/useEventFilters'; import { useCommunityEvents } from './hooks/useCommunityEvents'; // 导入时间工具函数 import { getCurrentTradingTimeRange, getMarketReviewTimeRange, filterEventsByTimeRange } from '../../utils/tradingTimeUtils'; import { logger } from '../../utils/logger'; import { useNotification } from '../../contexts/NotificationContext'; import { usePostHogTrack } from '../../hooks/usePostHogRedux'; import { RETENTION_EVENTS } from '../../lib/constants'; // 导航栏已由 MainLayout 提供,无需在此导入 const Community = () => { const navigate = useNavigate(); const dispatch = useDispatch(); const { track } = usePostHogTrack(); // PostHog 追踪(保留用于兼容) // Redux状态 const { popularKeywords, hotEvents } = useSelector(state => state.communityData); // Chakra UI hooks const bgColor = useColorModeValue('gray.50', 'gray.900'); // Ref:用于滚动到实时事件时间轴 const eventTimelineRef = useRef(null); const hasScrolledRef = useRef(false); // 标记是否已滚动 // ⚡ 通知权限引导 const { showCommunityGuide } = useNotification(); // Modal/Drawer状态 const [selectedEvent, setSelectedEvent] = useState(null); const [selectedEventForStock, setSelectedEventForStock] = useState(null); // 动态新闻数据状态 const [dynamicNewsEvents, setDynamicNewsEvents] = useState([]); const [dynamicNewsLoading, setDynamicNewsLoading] = useState(true); // 🎯 初始化Community埋点Hook const communityEvents = useCommunityEvents({ navigate }); // 自定义 Hooks const { filters, updateFilters, handlePageChange, handleEventClick, handleViewDetail } = useEventFilters({ navigate, onEventClick: (event) => setSelectedEventForStock(event), eventTimelineRef }); const { events, pagination, loading, lastUpdateTime } = useEventData(filters); // 计算市场复盘的时间范围和过滤后的事件 const marketReviewData = useMemo(() => { const timeRange = getMarketReviewTimeRange(); const filteredEvents = filterEventsByTimeRange(events, timeRange.startTime, timeRange.endTime); logger.debug('Community', '市场复盘时间范围', { description: timeRange.description, rangeType: timeRange.rangeType, eventCount: filteredEvents.length }); return { events: filteredEvents, timeRange }; }, [events]); // 加载热门关键词和热点事件(使用Redux,内部有缓存判断) useEffect(() => { dispatch(fetchPopularKeywords()); dispatch(fetchHotEvents()); }, [dispatch]); // 加载动态新闻数据 useEffect(() => { const fetchDynamicNews = async () => { setDynamicNewsLoading(true); try { // 检查是否使用 mock 模式 // 开发阶段默认使用 mock 数据 const useMock = true; // TODO: 生产环境改为环境变量控制 // const useMock = process.env.REACT_APP_USE_MOCK === 'true' || // localStorage.getItem('use_mock_data') === 'true'; if (useMock) { // 使用 mock 数据 const { generateMockEvents } = await import('../../mocks/data/events'); const mockData = generateMockEvents({ page: 1, per_page: 30 }); // 调试:检查第一个事件的 related_stocks 数据 if (mockData.events[0]?.related_stocks) { console.log('Mock 数据第一个事件的股票:', mockData.events[0].related_stocks); } setDynamicNewsEvents(mockData.events); logger.info('Community', '动态新闻(Mock)加载成功', { count: mockData.events.length, mode: 'mock', firstEventStocks: mockData.events[0]?.related_stocks?.length || 0 }); } else { // 使用真实 API const timeRange = getCurrentTradingTimeRange(); const response = await fetch( `/api/events/dynamic-news?start_time=${timeRange.startTime.toISOString()}&end_time=${timeRange.endTime.toISOString()}&count=30`, { credentials: 'include' } ); const data = await response.json(); if (data.success && data.data) { setDynamicNewsEvents(data.data); logger.info('Community', '动态新闻加载成功', { count: data.data.length, timeRange: timeRange.description, mode: 'api' }); } else { logger.warn('Community', '动态新闻加载失败', data); setDynamicNewsEvents([]); } } } catch (error) { logger.error('Community', '动态新闻加载异常', error); setDynamicNewsEvents([]); } finally { setDynamicNewsLoading(false); } }; fetchDynamicNews(); // 每5分钟刷新一次动态新闻 const interval = setInterval(fetchDynamicNews, 5 * 60 * 1000); return () => clearInterval(interval); }, []); // 🎯 PostHog 追踪:页面浏览 // useEffect(() => { // track(RETENTION_EVENTS.COMMUNITY_PAGE_VIEWED, { // timestamp: new Date().toISOString(), // has_hot_events: hotEvents && hotEvents.length > 0, // has_keywords: popularKeywords && popularKeywords.length > 0, // }); // }, [track]); // 只在组件挂载时执行一次 // 🎯 追踪新闻列表查看(当事件列表加载完成后) useEffect(() => { if (events && events.length > 0 && !loading) { communityEvents.trackNewsListViewed({ totalCount: pagination?.total || events.length, sortBy: filters.sort, importance: filters.importance, dateRange: filters.date_range, industryFilter: filters.industry_code, }); } }, [events, loading, pagination, filters, communityEvents]); // ⚡ 首次访问社区时,延迟显示权限引导 useEffect(() => { if (showCommunityGuide) { const timer = setTimeout(() => { logger.info('Community', '显示社区权限引导'); showCommunityGuide(); }, 5000); // 延迟 5 秒,让用户先浏览页面 return () => clearTimeout(timer); } }, [showCommunityGuide]); // 只在组件挂载时执行一次 // ⚡ 滚动到实时事件区域(由搜索框聚焦触发) const scrollToTimeline = useCallback(() => { if (!hasScrolledRef.current && eventTimelineRef.current) { eventTimelineRef.current.scrollIntoView({ behavior: 'smooth', // 平滑滚动动画 block: 'start', // 元素顶部对齐视口顶部,标题正好可见 inline: 'nearest' // 水平方向最小滚动 }); hasScrolledRef.current = true; // 标记已滚动 logger.debug('Community', '用户触发搜索,滚动到实时事件时间轴'); } }, []); return ( {/* 主内容区域 */} {/* 热点事件区域 */} {/* 实时要闻·动态追踪 - 横向滚动 */} {/* 市场复盘 - 左右布局 */} {/* {}} /> */} {/* 实时事件 - 原纵向列表 */} {/* */} {/* 事件弹窗 */} setSelectedEvent(null), event: selectedEvent, onEventClose: () => setSelectedEvent(null) }} stockDrawerState={{ visible: !!selectedEventForStock, event: selectedEventForStock, onClose: () => setSelectedEventForStock(null) }} /> ); }; export default Community;