Files
vf_react/src/views/Community/components/DynamicNewsCard.js
zdl 64de7d055b fix: 修复模式切换时丢失筛选条件的问题
问题描述:
- 用户在单排/双排/纵向模式下应用筛选条件后,切换到平铺模式时筛选条件丢失
- 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>
2025-11-05 09:35:35 +08:00

310 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;