// src/views/Community/components/DynamicNewsCard.js // 横向滚动事件卡片组件(实时要闻·动态追踪) import React, { forwardRef, useState, useEffect, useMemo, useCallback, useRef } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Card, CardHeader, CardBody, Box, Flex, VStack, HStack, Heading, Text, Badge, Center, Spinner, Modal, ModalOverlay, ModalContent, ModalHeader, ModalBody, ModalCloseButton, useColorModeValue, useToast, useDisclosure } from '@chakra-ui/react'; import { TimeIcon } from '@chakra-ui/icons'; import EventScrollList from './DynamicNewsCard/EventScrollList'; import DynamicNewsDetailPanel from './DynamicNewsDetail'; import UnifiedSearchBox from './UnifiedSearchBox'; import { fetchDynamicNews, toggleEventFollow, selectEventFollowStatus } from '../../../store/slices/communityDataSlice'; import { usePagination } from './DynamicNewsCard/hooks/usePagination'; import { PAGINATION_CONFIG } from './DynamicNewsCard/constants'; // 🔍 调试:渲染计数器 let dynamicNewsCardRenderCount = 0; /** * 实时要闻·动态追踪 - 事件展示卡片组件 * @param {Array} allCachedEvents - 完整缓存事件列表(从 Redux 传入) * @param {boolean} loading - 加载状态 * @param {number} total - 服务端总数量 * @param {number} cachedCount - 已缓存数量 * @param {Object} filters - 筛选条件 * @param {Array} popularKeywords - 热门关键词 * @param {Date} lastUpdateTime - 最后更新时间 * @param {Function} onSearch - 搜索回调 * @param {Function} onSearchFocus - 搜索框获得焦点回调 * @param {Function} onEventClick - 事件点击回调 * @param {Function} onViewDetail - 查看详情回调 * @param {Object} ref - 用于滚动的ref */ const DynamicNewsCard = forwardRef(({ allCachedEvents = [], loading, total = 0, cachedCount = 0, filters = {}, popularKeywords = [], lastUpdateTime, onSearch, onSearchFocus, onEventClick, onViewDetail, ...rest }, ref) => { // 🔍 调试:记录每次渲染 dynamicNewsCardRenderCount++; console.log(`%c🔍 [DynamicNewsCard] 渲染 #${dynamicNewsCardRenderCount} - allCachedEvents.length=${allCachedEvents.length}, total=${total}`, 'color: #FF9800; font-weight: bold; font-size: 14px;'); const dispatch = useDispatch(); const toast = useToast(); const cardBg = useColorModeValue('white', 'gray.800'); const borderColor = useColorModeValue('gray.200', 'gray.700'); // 从 Redux 读取关注状态 const eventFollowStatus = useSelector(selectEventFollowStatus); // 关注按钮点击处理 const handleToggleFollow = useCallback((eventId) => { dispatch(toggleEventFollow(eventId)); }, [dispatch]); // 本地状态 const [selectedEvent, setSelectedEvent] = useState(null); // 弹窗状态(用于四排模式) const { isOpen: isModalOpen, onOpen: onModalOpen, onClose: onModalClose } = useDisclosure(); const [modalEvent, setModalEvent] = useState(null); // 初始化标记 - 确保初始加载只执行一次 const hasInitialized = useRef(false); // 追踪是否已自动选中过首个事件 const hasAutoSelectedFirstEvent = useRef(false); // 使用分页 Hook const { currentPage, mode, loadingPage, pageSize, totalPages, hasMore, currentPageEvents, displayEvents, // 新增:累积显示的事件列表 isAccumulateMode, // 新增:是否累积模式 handlePageChange, handleModeToggle, loadNextPage // 新增:加载下一页 } = usePagination({ allCachedEvents, total, cachedCount, dispatch, toast, filters // 传递筛选条件 }); // 四排模式的事件点击处理(打开弹窗) const handleFourRowEventClick = useCallback((event) => { console.log('%c🔲 [四排模式] 点击事件,打开详情弹窗', 'color: #8B5CF6; font-weight: bold;', { eventId: event.id, title: event.title }); setModalEvent(event); onModalOpen(); }, [onModalOpen]); // 初始加载 - 只在组件首次挂载且未初始化时执行 useEffect(() => { if (!hasInitialized.current && allCachedEvents.length === 0) { hasInitialized.current = true; dispatch(fetchDynamicNews({ page: PAGINATION_CONFIG.INITIAL_PAGE, per_page: PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE, pageSize: PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE, // 传递 pageSize 确保索引计算一致 clearCache: true, ...filters // 应用初始筛选条件 })); } }, [dispatch, allCachedEvents.length]); // 监听筛选条件变化 - 清空缓存并重新请求数据 useEffect(() => { // 跳过初始加载(由上面的 useEffect 处理) if (!hasInitialized.current) return; console.log('%c🔍 [筛选] 筛选条件改变,重新请求数据', 'color: #8B5CF6; font-weight: bold;', filters); // 筛选条件改变时,清空缓存并从第1页开始加载 dispatch(fetchDynamicNews({ page: PAGINATION_CONFIG.INITIAL_PAGE, per_page: pageSize, pageSize: pageSize, clearCache: true, // 清空缓存 ...filters // 应用新的筛选条件 })); }, [ filters.sort, filters.importance, filters.q, filters.date_range, filters.industry_code, dispatch ]); // 只监听筛选参数的变化,不监听 page // 自动选中逻辑 - 只在首次加载时自动选中第一个事件,翻页时不自动选中 useEffect(() => { if (currentPageEvents.length > 0) { // 情况1: 首次加载 - 自动选中第一个事件并触发详情加载 if (!hasAutoSelectedFirstEvent.current && !selectedEvent) { console.log('%c🎯 [首次加载] 自动选中第一个事件', 'color: #10B981; font-weight: bold;'); hasAutoSelectedFirstEvent.current = true; setSelectedEvent(currentPageEvents[0]); return; } // 情况2: 翻页 - 如果选中的事件不在当前页,根据模式决定处理方式 const selectedEventInCurrentPage = currentPageEvents.find( e => e.id === selectedEvent?.id ); if (selectedEvent && !selectedEventInCurrentPage) { console.log('%c📄 [翻页] 选中的事件不在当前页', 'color: #F59E0B; font-weight: bold;'); // 单排/双排/纵向模式:自动选中当前页第一个事件(保持详情显示) // 四排模式:清空选中状态(不需要底部详情) if (mode === 'carousel' || mode === 'grid' || mode === 'vertical') { console.log('%c📄 [翻页] 自动选中当前页第一个事件', 'color: #10B981; font-weight: bold;'); setSelectedEvent(currentPageEvents[0]); } else { console.log('%c📄 [翻页] 清空选中状态(四排模式)', 'color: #F59E0B; font-weight: bold;'); setSelectedEvent(null); } } } }, [currentPageEvents, selectedEvent?.id, mode]); // 组件卸载时清理选中状态 useEffect(() => { return () => { setSelectedEvent(null); }; }, []); return ( {/* 标题部分 */} 实时要闻·动态追踪 实时 盘中 快讯 最后更新: {lastUpdateTime?.toLocaleTimeString() || '未知'} {/* 搜索和筛选组件 */} {/* 主体内容 */} {/* 横向滚动事件列表 - 始终渲染(除非为空) */} {currentPageEvents && currentPageEvents.length > 0 ? ( ) : !loading ? ( /* Empty 状态 - 只在非加载且无数据时显示 */
暂无事件数据
) : ( /* 首次加载状态 */
正在加载最新事件...
)} {/* 详情面板 - 仅在单排/双排模式下显示(四排模式不显示,纵向模式已在右侧显示) */} {currentPageEvents && currentPageEvents.length > 0 && selectedEvent && (mode === 'carousel' || mode === 'grid') && ( )}
{/* 四排模式详情弹窗 - 未打开时不渲染 */} {isModalOpen && ( {modalEvent?.title || '事件详情'} {modalEvent && } )}
); }); DynamicNewsCard.displayName = 'DynamicNewsCard'; export default DynamicNewsCard;