问题描述: - 用户在单排/双排/纵向模式下应用筛选条件后,切换到平铺模式时筛选条件丢失 - usePagination hook 在模式切换时重新请求数据,但未传递筛选参数 修复内容: 1. usePagination.js - 新增 filters 参数接收筛选条件 - handleModeToggle 函数在发起请求时应用 ...filters - 将 filters 添加到依赖数组,确保筛选条件变化时重新执行 2. DynamicNewsCard.js - 将 filters 传递给 usePagination hook - 确保筛选条件在模式切换时保持一致 影响范围: - 所有展示模式切换(单排、双排、纵向、平铺) 测试建议: 1. 应用任意筛选条件(如排序、重要性、关键词) 2. 切换到平铺模式 3. 验证筛选条件是否保持生效 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
310 lines
11 KiB
JavaScript
310 lines
11 KiB
JavaScript
// 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 (
|
||
<Card ref={ref} {...rest} bg={cardBg} borderColor={borderColor} mb={4}>
|
||
{/* 标题部分 */}
|
||
<CardHeader>
|
||
<Flex justify="space-between" align="center">
|
||
<VStack align="start" spacing={1}>
|
||
<Heading size="md">
|
||
<HStack>
|
||
<TimeIcon />
|
||
<Text>实时要闻·动态追踪</Text>
|
||
</HStack>
|
||
</Heading>
|
||
<HStack fontSize="sm" color="gray.500">
|
||
<Badge colorScheme="red">实时</Badge>
|
||
<Badge colorScheme="green">盘中</Badge>
|
||
<Badge colorScheme="blue">快讯</Badge>
|
||
</HStack>
|
||
</VStack>
|
||
<Text fontSize="xs" color="gray.500">
|
||
最后更新: {lastUpdateTime?.toLocaleTimeString() || '未知'}
|
||
</Text>
|
||
</Flex>
|
||
|
||
{/* 搜索和筛选组件 */}
|
||
<Box mt={4}>
|
||
<UnifiedSearchBox
|
||
onSearch={onSearch}
|
||
onSearchFocus={onSearchFocus}
|
||
popularKeywords={popularKeywords}
|
||
filters={filters}
|
||
/>
|
||
</Box>
|
||
</CardHeader>
|
||
|
||
{/* 主体内容 */}
|
||
<CardBody position="relative" pt={0}>
|
||
{/* 横向滚动事件列表 - 始终渲染(除非为空) */}
|
||
{currentPageEvents && currentPageEvents.length > 0 ? (
|
||
<EventScrollList
|
||
events={currentPageEvents}
|
||
displayEvents={displayEvents} // 新增:累积显示的事件列表
|
||
isAccumulateMode={isAccumulateMode} // 新增:是否累积模式
|
||
loadNextPage={loadNextPage} // 新增:加载下一页
|
||
onFourRowEventClick={handleFourRowEventClick} // 新增:四排模式事件点击
|
||
selectedEvent={selectedEvent}
|
||
onEventSelect={setSelectedEvent}
|
||
borderColor={borderColor}
|
||
currentPage={currentPage}
|
||
totalPages={totalPages}
|
||
onPageChange={handlePageChange}
|
||
loading={loadingPage !== null}
|
||
loadingPage={loadingPage}
|
||
mode={mode}
|
||
onModeChange={handleModeToggle}
|
||
eventFollowStatus={eventFollowStatus}
|
||
onToggleFollow={handleToggleFollow}
|
||
hasMore={hasMore}
|
||
/>
|
||
) : !loading ? (
|
||
/* Empty 状态 - 只在非加载且无数据时显示 */
|
||
<Center py={10}>
|
||
<VStack>
|
||
<Text fontSize="lg" color="gray.500">暂无事件数据</Text>
|
||
</VStack>
|
||
</Center>
|
||
) : (
|
||
/* 首次加载状态 */
|
||
<Center py={10}>
|
||
<VStack>
|
||
<Spinner size="xl" color="blue.500" thickness="4px" />
|
||
<Text color="gray.500">正在加载最新事件...</Text>
|
||
</VStack>
|
||
</Center>
|
||
)}
|
||
|
||
{/* 详情面板 - 仅在单排/双排模式下显示(四排模式不显示,纵向模式已在右侧显示) */}
|
||
{currentPageEvents && currentPageEvents.length > 0 && selectedEvent &&
|
||
(mode === 'carousel' || mode === 'grid') && (
|
||
<Box mt={6}>
|
||
<DynamicNewsDetailPanel event={selectedEvent} />
|
||
</Box>
|
||
)}
|
||
</CardBody>
|
||
|
||
{/* 四排模式详情弹窗 - 未打开时不渲染 */}
|
||
{isModalOpen && (
|
||
<Modal isOpen={isModalOpen} onClose={onModalClose} size="6xl" scrollBehavior="inside">
|
||
<ModalOverlay />
|
||
<ModalContent>
|
||
<ModalHeader>
|
||
{modalEvent?.title || '事件详情'}
|
||
</ModalHeader>
|
||
<ModalCloseButton />
|
||
<ModalBody pb={6}>
|
||
{modalEvent && <DynamicNewsDetailPanel event={modalEvent} />}
|
||
</ModalBody>
|
||
</ModalContent>
|
||
</Modal>
|
||
)}
|
||
</Card>
|
||
);
|
||
});
|
||
|
||
DynamicNewsCard.displayName = 'DynamicNewsCard';
|
||
|
||
export default DynamicNewsCard;
|