diff --git a/src/views/Community/components/DynamicNewsCard/hooks/usePagination.js b/src/views/Community/components/DynamicNewsCard/hooks/usePagination.js index 4c6f0e4d..11426d98 100644 --- a/src/views/Community/components/DynamicNewsCard/hooks/usePagination.js +++ b/src/views/Community/components/DynamicNewsCard/hooks/usePagination.js @@ -22,25 +22,29 @@ import { * @returns {Object} 分页状态和方法 */ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, toast }) => { - // 组件挂载状态跟踪 - 用于防止内存泄漏 - const isMountedRef = useRef(true); - // 本地状态 const [currentPage, setCurrentPage] = useState(PAGINATION_CONFIG.INITIAL_PAGE); const [loadingPage, setLoadingPage] = useState(null); const [mode, setMode] = useState(DEFAULT_MODE); - // 组件卸载时更新挂载状态 - useEffect(() => { - return () => { - isMountedRef.current = false; - }; - }, []); + // 累积显示的事件列表(用于四排模式的无限滚动) + const [accumulatedEvents, setAccumulatedEvents] = useState([]); // 根据模式决定每页显示数量 - const pageSize = mode === DISPLAY_MODES.CAROUSEL - ? PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE - : PAGINATION_CONFIG.GRID_PAGE_SIZE; + const pageSize = (() => { + switch (mode) { + case DISPLAY_MODES.CAROUSEL: + return PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE; + case DISPLAY_MODES.GRID: + return PAGINATION_CONFIG.GRID_PAGE_SIZE; + case DISPLAY_MODES.FOUR_ROW: + return PAGINATION_CONFIG.FOUR_ROW_PAGE_SIZE; + case DISPLAY_MODES.VERTICAL: + return PAGINATION_CONFIG.VERTICAL_PAGE_SIZE; + default: + return PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE; + } + })(); // 计算总页数(基于服务端总数据量) const totalPages = Math.ceil(total / pageSize) || 1; @@ -48,6 +52,9 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t // 检查是否还有更多数据 const hasMore = cachedCount < total; + // 判断是否使用累积模式(四排模式) + const isAccumulateMode = mode === DISPLAY_MODES.FOUR_ROW; + // 从缓存中切片获取当前页数据(过滤 null 占位符) const currentPageEvents = useMemo(() => { const startIndex = (currentPage - 1) * pageSize; @@ -55,6 +62,17 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t return allCachedEvents.slice(startIndex, endIndex).filter(event => event !== null); }, [allCachedEvents, currentPage, pageSize]); + // 当前显示的事件列表(累积模式 vs 分页模式) + const displayEvents = useMemo(() => { + if (isAccumulateMode) { + // 四排模式:累积显示所有已加载的事件 + return accumulatedEvents; + } else { + // 其他模式:只显示当前页 + return currentPageEvents; + } + }, [isAccumulateMode, accumulatedEvents, currentPageEvents]); + /** * 子函数1: 检查目标页缓存状态 * @param {number} targetPage - 目标页码 @@ -65,7 +83,10 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t const targetPageEndIndex = targetPageStartIndex + pageSize; const targetPageData = allCachedEvents.slice(targetPageStartIndex, targetPageEndIndex); const validTargetData = targetPageData.filter(e => e !== null); - const expectedCount = Math.min(pageSize, total - targetPageStartIndex); + // 修复:确保 expectedCount 不为负数 + // - 当 total = 0 时,expectedCount = pageSize,强制发起请求 + // - 当 total - targetPageStartIndex < 0 时,expectedCount = 0 + const expectedCount = total === 0 ? pageSize : Math.max(0, Math.min(pageSize, total - targetPageStartIndex)); const isTargetPageCached = validTargetData.length >= expectedCount; logger.debug('DynamicNewsCard', '目标页缓存检查', { @@ -89,275 +110,177 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t }; }, [allCachedEvents, pageSize, total]); + // 已删除: calculatePreloadRange(不再需要预加载) + + // 已删除: findMissingPages(不再需要查找缺失页面) + /** - * 子函数2: 计算预加载范围 + * 加载单个页面数据 * @param {number} targetPage - 目标页码 - * @param {number} fromPage - 来源页码 - * @returns {Array} 预加载页码数组 - */ - const calculatePreloadRange = useCallback((targetPage, fromPage) => { - const isSequentialNavigation = Math.abs(targetPage - fromPage) === 1; - - let preloadRange; - if (isSequentialNavigation) { - // 连续翻页:前后各N页(N = PRELOAD_RANGE) - const start = Math.max(1, targetPage - PAGINATION_CONFIG.PRELOAD_RANGE); - const end = Math.min(totalPages, targetPage + PAGINATION_CONFIG.PRELOAD_RANGE); - preloadRange = Array.from( - { length: end - start + 1 }, - (_, i) => start + i - ); - } else { - // 跳转翻页:只加载当前页 - preloadRange = [targetPage]; - } - - logger.debug('DynamicNewsCard', '计算预加载范围', { - targetPage, - fromPage, - isSequentialNavigation, - preloadRange - }); - - return preloadRange; - }, [totalPages]); - - /** - * 子函数3: 查找缺失页面 - * @param {Array} preloadRange - 预加载范围 - * @returns {Array} 缺失页码数组 - */ - const findMissingPages = useCallback((preloadRange) => { - const missingPages = preloadRange.filter(page => { - const pageStartIndex = (page - 1) * pageSize; - const pageEndIndex = pageStartIndex + pageSize; - - // 如果该页超出数组范围,说明未缓存 - if (pageEndIndex > allCachedEvents.length) { - logger.debug('DynamicNewsCard', `页面${page}超出数组范围`, { - pageStartIndex, - pageEndIndex, - allCachedEventsLength: allCachedEvents.length - }); - return true; - } - - // 检查该页的数据是否包含 null 占位符或数据不足 - const pageData = allCachedEvents.slice(pageStartIndex, pageEndIndex); - const validData = pageData.filter(e => e !== null); - const expectedCount = Math.min(pageSize, total - pageStartIndex); - const hasNullOrIncomplete = validData.length < expectedCount; - - logger.debug('DynamicNewsCard', `页面${page}数据检查`, { - pageStartIndex, - pageEndIndex, - pageDataLength: pageData.length, - validDataLength: validData.length, - expectedCount, - hasNullOrIncomplete - }); - - return hasNullOrIncomplete; - }); - - logger.debug('DynamicNewsCard', '缺失页面检测完成', { - preloadRange, - missingPages, - missingPagesCount: missingPages.length - }); - - return missingPages; - }, [allCachedEvents, pageSize, total]); - - /** - * 子函数4: 加载页面数据 - * @param {Array} missingPages - 缺失页码数组 - * @param {number} targetPage - 目标页码 - * @param {boolean} silentMode - 静默模式(后台预加载) * @returns {Promise} 是否加载成功 */ - const loadPages = useCallback(async (missingPages, targetPage, silentMode = false) => { - // 检查组件是否已卸载 - if (!isMountedRef.current) { - logger.debug('DynamicNewsCard', '组件已卸载,取消加载'); - return false; - } - - if (!silentMode) { - // 显示 loading 状态 - setLoadingPage(targetPage); - } + const loadPage = useCallback(async (targetPage) => { + // 显示 loading 状态 + setLoadingPage(targetPage); try { + console.log(`%c🟢 [API请求] 开始加载第${targetPage}页数据`, 'color: #16A34A; font-weight: bold;'); + console.log(`%c 请求参数: page=${targetPage}, per_page=${pageSize}`, 'color: #16A34A;'); + logger.debug('DynamicNewsCard', '开始加载页面数据', { - missingPages, targetPage, - silentMode, pageSize }); - // 拆分为单页请求,避免 per_page 动态值导致后端返回空数据 - for (const page of missingPages) { - // 每次请求前检查组件是否已卸载 - if (!isMountedRef.current) { - logger.debug('DynamicNewsCard', '组件已卸载,中止加载'); - return false; - } + await dispatch(fetchDynamicNews({ + page: targetPage, + per_page: pageSize, + pageSize: pageSize, + clearCache: false + })).unwrap(); - logger.debug('DynamicNewsCard', `开始加载第 ${page} 页`); - - await dispatch(fetchDynamicNews({ - page: page, - per_page: pageSize, // 固定值(5或10),不使用动态计算 - pageSize: pageSize, - clearCache: false - })).unwrap(); - - logger.debug('DynamicNewsCard', `第 ${page} 页加载完成`); - } - - logger.debug('DynamicNewsCard', '所有页面加载完成', { - missingPages, - silentMode - }); + console.log(`%c🟢 [API请求] 第${targetPage}页加载完成`, 'color: #16A34A; font-weight: bold;'); + logger.debug('DynamicNewsCard', `第 ${targetPage} 页加载完成`); return true; } catch (error) { - logger.error('DynamicNewsCard', 'loadPages', error, { - targetPage, - silentMode, - missingPages + logger.error('DynamicNewsCard', 'loadPage', error, { + targetPage }); - // 只在组件仍挂载时显示错误提示 - if (!silentMode && isMountedRef.current) { - toast({ - title: '加载失败', - description: `无法加载第 ${targetPage} 页数据,请稍后重试`, - status: 'error', - duration: TOAST_CONFIG.DURATION_ERROR, - isClosable: true, - position: 'top' - }); - } + toast({ + title: '加载失败', + description: `无法加载第 ${targetPage} 页数据,请稍后重试`, + status: 'error', + duration: TOAST_CONFIG.DURATION_ERROR, + isClosable: true, + position: 'top' + }); return false; } finally { - // 只在组件仍挂载时清除加载状态 - if (!silentMode && isMountedRef.current) { - setLoadingPage(null); - } + setLoadingPage(null); } }, [dispatch, pageSize, toast]); - // 翻页处理(智能预加载)- 使用子函数重构 + // 翻页处理(简化版 - 无预加载) const handlePageChange = useCallback(async (newPage) => { - // 检查组件是否已卸载 - if (!isMountedRef.current) { - logger.debug('DynamicNewsCard', '组件已卸载,取消翻页'); - return; - } + console.log(`%c🔵 [翻页逻辑] handlePageChange 开始`, 'color: #3B82F6; font-weight: bold;'); + console.log(`%c 当前页: ${currentPage}, 目标页: ${newPage}, 总页数: ${totalPages}`, 'color: #3B82F6;'); + console.log(`%c 每页大小: ${pageSize}, 缓存总数: ${allCachedEvents.length}, 服务端总数: ${total}`, 'color: #3B82F6;'); - // 🔍 诊断日志 - 记录翻页开始状态 logger.debug('DynamicNewsCard', '开始翻页', { currentPage, newPage, pageSize, totalPages, - hasMore, total, allCachedEventsLength: allCachedEvents.length, cachedCount }); - // 步骤1: 检查目标页缓存状态 - const { isTargetPageCached } = checkTargetPageCache(newPage); + // 特殊处理:返回第一页 - 清空缓存重新加载 + if (newPage === 1) { + logger.debug('DynamicNewsCard', '返回第一页,清空缓存重新加载'); + setCurrentPage(1); + dispatch(fetchDynamicNews({ + page: 1, + per_page: pageSize, + pageSize: pageSize, + clearCache: true // 清空缓存 + })); + return; + } - // 步骤2: 计算预加载范围 - const preloadRange = calculatePreloadRange(newPage, currentPage); + // 检查目标页缓存状态 + const { isTargetPageCached, targetPageInfo } = checkTargetPageCache(newPage); - // 步骤3: 查找缺失页面 - const missingPages = findMissingPages(preloadRange); + console.log(`%c🟡 [缓存检查] 目标页${newPage}缓存状态`, 'color: #EAB308; font-weight: bold;'); + console.log(`%c 是否已缓存: ${isTargetPageCached ? '✅ 是' : '❌ 否'}`, `color: ${isTargetPageCached ? '#16A34A' : '#DC2626'};`); + console.log(`%c 索引范围: ${targetPageInfo.startIndex}-${targetPageInfo.endIndex}`, 'color: #EAB308;'); + console.log(`%c 实际数量: ${targetPageInfo.validCount}, 期望数量: ${targetPageInfo.expectedCount}`, 'color: #EAB308;'); - // 步骤4: 根据情况加载数据 - if (isTargetPageCached && missingPages.length > 0 && hasMore) { - // 场景A: 目标页已缓存,立即切换,后台静默预加载其他页 - logger.debug('DynamicNewsCard', '目标页已缓存,立即切换 + 后台预加载', { - currentPage, - newPage, - 缺失页面: missingPages - }); - - // 只在组件仍挂载时更新状态 - if (isMountedRef.current) { - setCurrentPage(newPage); - } - await loadPages(missingPages, newPage, true); // 静默模式 - } else if (missingPages.length > 0 && hasMore) { - // 场景B: 目标页未缓存,显示 loading 并等待加载完成 - logger.debug('DynamicNewsCard', '目标页未缓存,显示 loading', { - currentPage, - newPage, - 缺失页面: missingPages - }); - - const success = await loadPages(missingPages, newPage, false); // 非静默模式 - // 只在加载成功且组件仍挂载时更新状态 - if (success && isMountedRef.current) { - setCurrentPage(newPage); - } - } else if (missingPages.length === 0) { - // 场景C: 所有页面均已缓存,直接切换 - logger.debug('DynamicNewsCard', '无需加载,直接切换', { - currentPage, - newPage, - reason: '所有页面均已缓存' - }); - - // 只在组件仍挂载时更新状态 - if (isMountedRef.current) { - setCurrentPage(newPage); - } + if (isTargetPageCached) { + // 目标页已缓存,直接切换 + console.log(`%c🟡 [缓存] 目标页已缓存,直接切换到第${newPage}页`, 'color: #16A34A; font-weight: bold;'); + logger.debug('DynamicNewsCard', '目标页已缓存,直接切换', { newPage }); + setCurrentPage(newPage); } else { - // 场景D: 意外分支(有缺失页面但 hasMore=false) - logger.warn('DynamicNewsCard', '意外分支:有缺失页面但无法加载', { - missingPages, - hasMore, - currentPage, - newPage, - total, - cachedCount - }); + // 目标页未缓存,显示 loading 并加载数据 + console.log(`%c🟡 [缓存] 目标页未缓存,需要加载第${newPage}页数据`, 'color: #DC2626; font-weight: bold;'); + logger.debug('DynamicNewsCard', '目标页未缓存,加载数据', { newPage }); + const success = await loadPage(newPage); - // 只在组件仍挂载时更新状态 - if (isMountedRef.current) { + // 加载成功后切换页面 + if (success) { + console.log(`%c🟢 [加载成功] 切换到第${newPage}页`, 'color: #16A34A; font-weight: bold;'); setCurrentPage(newPage); - - toast({ - title: '数据不完整', - description: `第 ${newPage} 页数据可能不完整`, - status: 'warning', - duration: TOAST_CONFIG.DURATION_WARNING, - isClosable: true, - position: 'top' - }); + } else { + console.log(`%c❌ [加载失败] 未能切换到第${newPage}页`, 'color: #DC2626; font-weight: bold;'); } } }, [ currentPage, pageSize, totalPages, - hasMore, total, allCachedEvents.length, cachedCount, checkTargetPageCache, - calculatePreloadRange, - findMissingPages, - loadPages, + loadPage, + dispatch, toast ]); + // 更新累积列表(四排模式专用) + useEffect(() => { + if (isAccumulateMode) { + // 计算已加载的所有事件(从第1页到当前页) + const startIndex = 0; + const endIndex = currentPage * pageSize; + const accumulated = allCachedEvents.slice(startIndex, endIndex).filter(e => e !== null); + + logger.debug('DynamicNewsCard', '更新累积事件列表', { + currentPage, + pageSize, + startIndex, + endIndex, + accumulatedLength: accumulated.length + }); + + setAccumulatedEvents(accumulated); + } else { + // 非累积模式时清空累积列表 + if (accumulatedEvents.length > 0) { + setAccumulatedEvents([]); + } + } + }, [isAccumulateMode, currentPage, pageSize, allCachedEvents]); + + // 加载下一页(用于无限滚动) + const loadNextPage = useCallback(async () => { + if (currentPage >= totalPages || loadingPage !== null) { + logger.debug('DynamicNewsCard', '无法加载下一页', { + currentPage, + totalPages, + loadingPage, + reason: currentPage >= totalPages ? '已是最后一页' : '正在加载中' + }); + return Promise.resolve(false); // 没有更多数据或正在加载 + } + + const nextPage = currentPage + 1; + logger.debug('DynamicNewsCard', '懒加载:加载下一页', { currentPage, nextPage }); + + try { + await handlePageChange(nextPage); + return true; + } catch (error) { + logger.error('DynamicNewsCard', '懒加载失败', error, { nextPage }); + return false; + } + }, [currentPage, totalPages, loadingPage, handlePageChange]); + // 模式切换处理 const handleModeToggle = useCallback((newMode) => { if (newMode === mode) return; @@ -365,9 +288,20 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t setMode(newMode); setCurrentPage(PAGINATION_CONFIG.INITIAL_PAGE); - const newPageSize = newMode === DISPLAY_MODES.CAROUSEL - ? PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE - : PAGINATION_CONFIG.GRID_PAGE_SIZE; + const newPageSize = (() => { + switch (newMode) { + case DISPLAY_MODES.CAROUSEL: + return PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE; + case DISPLAY_MODES.GRID: + return PAGINATION_CONFIG.GRID_PAGE_SIZE; + case DISPLAY_MODES.FOUR_ROW: + return PAGINATION_CONFIG.FOUR_ROW_PAGE_SIZE; + case DISPLAY_MODES.VERTICAL: + return PAGINATION_CONFIG.VERTICAL_PAGE_SIZE; + default: + return PAGINATION_CONFIG.CAROUSEL_PAGE_SIZE; + } + })(); // 检查第1页的数据是否完整(排除 null) const firstPageData = allCachedEvents.slice(0, newPageSize); @@ -395,9 +329,12 @@ export const usePagination = ({ allCachedEvents, total, cachedCount, dispatch, t totalPages, hasMore, currentPageEvents, + displayEvents, // 新增:当前显示的事件列表(累积或分页) + isAccumulateMode, // 新增:是否累积模式 // 方法 handlePageChange, - handleModeToggle + handleModeToggle, + loadNextPage // 新增:加载下一页(用于无限滚动) }; };