diff --git a/src/views/Community/components/DynamicNewsCard.js b/src/views/Community/components/DynamicNewsCard.js index c59e0078..16c2f9b4 100644 --- a/src/views/Community/components/DynamicNewsCard.js +++ b/src/views/Community/components/DynamicNewsCard.js @@ -107,7 +107,8 @@ const DynamicNewsCard = forwardRef(({ isAccumulateMode, // 新增:是否累积模式 handlePageChange, handleModeToggle, - loadNextPage // 新增:加载下一页 + loadNextPage, // 新增:加载下一页 + loadPrevPage // 新增:加载上一页 } = usePagination({ allCachedEvents, total, @@ -244,6 +245,7 @@ const DynamicNewsCard = forwardRef(({ displayEvents={displayEvents} // 新增:累积显示的事件列表 isAccumulateMode={isAccumulateMode} // 新增:是否累积模式 loadNextPage={loadNextPage} // 新增:加载下一页 + loadPrevPage={loadPrevPage} // 新增:加载上一页 onFourRowEventClick={handleFourRowEventClick} // 新增:四排模式事件点击 selectedEvent={selectedEvent} onEventSelect={setSelectedEvent} diff --git a/src/views/Community/components/DynamicNewsCard/EventScrollList.js b/src/views/Community/components/DynamicNewsCard/EventScrollList.js index d47db8c2..10a8cc44 100644 --- a/src/views/Community/components/DynamicNewsCard/EventScrollList.js +++ b/src/views/Community/components/DynamicNewsCard/EventScrollList.js @@ -46,6 +46,7 @@ const EventScrollList = ({ displayEvents, // 累积显示的事件列表(四排模式用) isAccumulateMode, // 是否累积模式 loadNextPage, // 加载下一页(无限滚动) + loadPrevPage, // 加载上一页(双向无限滚动) onFourRowEventClick, // 四排模式事件点击回调(打开弹窗) selectedEvent, onEventSelect, @@ -270,7 +271,7 @@ const EventScrollList = ({ )} - {/* 模式3: 四排网格模式 - 使用虚拟滚动 + 无限滚动 */} + {/* 模式3: 四排网格模式 - 使用虚拟滚动 + 双向无限滚动 */} {mode === 'four-row' && ( @@ -289,7 +291,7 @@ const EventScrollList = ({ {/* 模式4: 纵向分栏模式 - 横向布局(时间在左,卡片在右) */} {mode === 'vertical' && ( - {/* 左侧:事件列表 (33.3%) - 使用虚拟滚动 + 无限滚动 */} + {/* 左侧:事件列表 (33.3%) - 使用虚拟滚动 + 双向无限滚动 */} diff --git a/src/views/Community/components/DynamicNewsCard/VirtualizedFourRowGrid.js b/src/views/Community/components/DynamicNewsCard/VirtualizedFourRowGrid.js index 9b941c75..74bb6d74 100644 --- a/src/views/Community/components/DynamicNewsCard/VirtualizedFourRowGrid.js +++ b/src/views/Community/components/DynamicNewsCard/VirtualizedFourRowGrid.js @@ -34,11 +34,13 @@ const VirtualizedFourRowGrid = ({ getTimelineBoxStyle, borderColor, loadNextPage, + loadPrevPage, // 新增:加载上一页 hasMore, loading, }) => { const parentRef = useRef(null); const isLoadingMore = useRef(false); // 防止重复加载 + const previousScrollHeight = useRef(0); // 记录加载前的滚动高度(用于位置保持) // 滚动条颜色(主题适配) const scrollbarTrackBg = useColorModeValue('#f1f1f1', '#2D3748'); @@ -62,30 +64,42 @@ const VirtualizedFourRowGrid = ({ overscan: 2, // 预加载2行(上下各1行) }); - // 无限滚动逻辑 - 监听滚动事件,到达底部时加载下一页 + // 双向无限滚动逻辑 - 监听滚动事件,到达底部加载下一页,到达顶部加载上一页 useEffect(() => { const scrollElement = parentRef.current; - if (!scrollElement || !loadNextPage) return; + if (!scrollElement) return; const handleScroll = async () => { // 防止重复触发 - if (isLoadingMore.current || !hasMore || loading) return; + if (isLoadingMore.current || loading) return; const { scrollTop, scrollHeight, clientHeight } = scrollElement; const scrollPercentage = (scrollTop + clientHeight) / scrollHeight; - // 滚动到 60% 时开始加载下一页(降低阈值,更早触发) - if (scrollPercentage > 0.6) { - console.log('%c📜 [无限滚动] 到达底部,加载下一页', 'color: #8B5CF6; font-weight: bold;'); + // 向下滚动:滚动到 60% 时开始加载下一页 + if (loadNextPage && hasMore && scrollPercentage > 0.6) { + console.log('%c📜 [双向滚动] 到达底部,加载下一页', 'color: #8B5CF6; font-weight: bold;'); isLoadingMore.current = true; await loadNextPage(); isLoadingMore.current = false; } + + // 向上滚动:滚动到顶部 10% 以内时加载上一页 + if (loadPrevPage && scrollTop < clientHeight * 0.1) { + console.log('%c📜 [双向滚动] 到达顶部,加载上一页', 'color: #10B981; font-weight: bold;'); + isLoadingMore.current = true; + + // 记录加载前的滚动高度(用于位置保持) + previousScrollHeight.current = scrollHeight; + + await loadPrevPage(); + isLoadingMore.current = false; + } }; scrollElement.addEventListener('scroll', handleScroll); return () => scrollElement.removeEventListener('scroll', handleScroll); - }, [loadNextPage, hasMore, loading]); + }, [loadNextPage, loadPrevPage, hasMore, loading]); // 主动检测内容高度 - 如果内容不足以填满容器,主动加载下一页 useEffect(() => { @@ -116,6 +130,36 @@ const VirtualizedFourRowGrid = ({ return () => clearTimeout(timer); }, [events.length, hasMore, loading, loadNextPage]); + // 滚动位置保持 - 加载上一页后,调整 scrollTop 使用户看到的内容位置不变 + useEffect(() => { + const scrollElement = parentRef.current; + if (!scrollElement || previousScrollHeight.current === 0) return; + + // 延迟执行,确保虚拟滚动已重新渲染并测量了新高度 + const timer = setTimeout(() => { + const currentScrollHeight = scrollElement.scrollHeight; + const heightDifference = currentScrollHeight - previousScrollHeight.current; + + // 如果高度增加了(说明上一页数据已加载),调整滚动位置 + if (heightDifference > 0) { + console.log('%c📜 [位置保持] 调整滚动位置', 'color: #10B981; font-weight: bold;', { + previousHeight: previousScrollHeight.current, + currentHeight: currentScrollHeight, + heightDifference, + newScrollTop: scrollElement.scrollTop + heightDifference + }); + + // 调整 scrollTop,使用户看到的内容位置不变 + scrollElement.scrollTop += heightDifference; + + // 重置记录 + previousScrollHeight.current = 0; + } + }, 300); + + return () => clearTimeout(timer); + }, [events.length]); // 监听 events 变化,加载上一页后会增加 events 数量 + // 底部加载指示器 const renderLoadingIndicator = () => { if (!hasMore) { diff --git a/src/views/Community/components/DynamicNewsCard/hooks/usePagination.js b/src/views/Community/components/DynamicNewsCard/hooks/usePagination.js index 7c734a6d..2ac070fb 100644 --- a/src/views/Community/components/DynamicNewsCard/hooks/usePagination.js +++ b/src/views/Community/components/DynamicNewsCard/hooks/usePagination.js @@ -53,8 +53,8 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t // 检查是否还有更多数据 const hasMore = cachedCount < total; - // 判断是否使用累积模式(四排模式) - const isAccumulateMode = mode === DISPLAY_MODES.FOUR_ROW; + // 判断是否使用累积模式(四排模式 + 纵向模式) + const isAccumulateMode = mode === DISPLAY_MODES.FOUR_ROW || mode === DISPLAY_MODES.VERTICAL; // 从缓存中切片获取当前页数据(过滤 null 占位符) const currentPageEvents = useMemo(() => { @@ -282,6 +282,29 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t } }, [currentPage, totalPages, loadingPage, handlePageChange]); + // 加载上一页(用于双向无限滚动) + const loadPrevPage = useCallback(async () => { + if (currentPage <= 1 || loadingPage !== null) { + logger.debug('DynamicNewsCard', '无法加载上一页', { + currentPage, + loadingPage, + reason: currentPage <= 1 ? '已是第一页' : '正在加载中' + }); + return Promise.resolve(false); // 已经是第一页或正在加载 + } + + const prevPage = currentPage - 1; + logger.debug('DynamicNewsCard', '懒加载:加载上一页', { currentPage, prevPage }); + + try { + await handlePageChange(prevPage); + return true; + } catch (error) { + logger.error('DynamicNewsCard', '懒加载上一页失败', error, { prevPage }); + return false; + } + }, [currentPage, loadingPage, handlePageChange]); + // 模式切换处理 const handleModeToggle = useCallback((newMode) => { if (newMode === mode) return; @@ -337,6 +360,7 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t // 方法 handlePageChange, handleModeToggle, - loadNextPage // 新增:加载下一页(用于无限滚动) + loadNextPage, // 新增:加载下一页(用于无限滚动) + loadPrevPage // 新增:加载上一页(用于双向无限滚动) }; };