feat: 实现 Socket 触发的智能列表自动刷新功能(带防抖)
核心改动: - 扩展 NotificationContext,添加事件更新回调注册机制 - VirtualizedFourRowGrid 添加 forwardRef 暴露 getScrollPosition 方法 - DynamicNewsCard 实现智能刷新逻辑(根据模式和滚动位置判断是否刷新) - Community 页面注册 Socket 回调自动触发刷新 - 创建 TypeScript 通用防抖工具函数(debounce.ts) - 集成防抖机制(2秒延迟),避免短时间内频繁请求 智能刷新策略: - 纵向模式 + 第1页:自动刷新列表 - 纵向模式 + 其他页:不刷新(避免打断用户) - 平铺模式 + 滚动在顶部:自动刷新列表 - 平铺模式 + 滚动不在顶部:仅显示 Toast 提示 防抖效果: - 短时间内收到多个新事件,只执行最后一次刷新 - 减少服务器压力,提升用户体验 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,9 @@ export const NotificationProvider = ({ children }) => {
|
||||
const adaptEventToNotificationRef = useRef(null);
|
||||
const isFirstConnect = useRef(true); // 标记是否首次连接
|
||||
|
||||
// ⚡ 事件更新回调列表(用于在收到 new_event 时通知其他组件刷新数据)
|
||||
const eventUpdateCallbacks = useRef(new Set());
|
||||
|
||||
// ⚡ 使用权限引导管理 Hook
|
||||
const { shouldShowGuide, markGuideAsShown } = usePermissionGuide();
|
||||
|
||||
@@ -160,6 +163,37 @@ export const NotificationProvider = ({ children }) => {
|
||||
});
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 注册事件更新回调(用于在收到新事件时通知其他组件刷新)
|
||||
* @param {Function} callback - 回调函数,接收 eventData 参数
|
||||
* @returns {Function} 取消注册函数
|
||||
*/
|
||||
const registerEventUpdateCallback = useCallback((callback) => {
|
||||
eventUpdateCallbacks.current.add(callback);
|
||||
logger.info('NotificationContext', 'Event update callback registered', {
|
||||
totalCallbacks: eventUpdateCallbacks.current.size
|
||||
});
|
||||
|
||||
// 返回取消注册函数
|
||||
return () => {
|
||||
eventUpdateCallbacks.current.delete(callback);
|
||||
logger.info('NotificationContext', 'Event update callback unregistered', {
|
||||
totalCallbacks: eventUpdateCallbacks.current.size
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 取消注册事件更新回调(已废弃,建议使用 registerEventUpdateCallback 返回的函数)
|
||||
* @param {Function} callback - 要取消的回调函数
|
||||
*/
|
||||
const unregisterEventUpdateCallback = useCallback((callback) => {
|
||||
eventUpdateCallbacks.current.delete(callback);
|
||||
logger.info('NotificationContext', 'Event update callback unregistered (manual)', {
|
||||
totalCallbacks: eventUpdateCallbacks.current.size
|
||||
});
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 请求浏览器通知权限
|
||||
*/
|
||||
@@ -764,6 +798,21 @@ export const NotificationProvider = ({ children }) => {
|
||||
console.log('[NotificationContext] 准备添加通知到队列...');
|
||||
addNotificationRef.current(notification);
|
||||
console.log('[NotificationContext] ✅ 通知已添加到队列');
|
||||
|
||||
// ⚡ 调用所有注册的事件更新回调(用于通知其他组件刷新数据)
|
||||
if (eventUpdateCallbacks.current.size > 0) {
|
||||
console.log(`[NotificationContext] 🔔 触发 ${eventUpdateCallbacks.current.size} 个事件更新回调...`);
|
||||
eventUpdateCallbacks.current.forEach(callback => {
|
||||
try {
|
||||
callback(data);
|
||||
} catch (error) {
|
||||
logger.error('NotificationContext', 'Event update callback error', error);
|
||||
console.error('[NotificationContext] ❌ 事件更新回调执行失败:', error);
|
||||
}
|
||||
});
|
||||
console.log('[NotificationContext] ✅ 所有事件更新回调已触发');
|
||||
}
|
||||
|
||||
console.log('%c════════════════════════════════════════\n', 'color: #FF9800; font-weight: bold;');
|
||||
});
|
||||
|
||||
@@ -1040,6 +1089,9 @@ export const NotificationProvider = ({ children }) => {
|
||||
showWelcomeGuide,
|
||||
showCommunityGuide,
|
||||
showFirstFollowGuide,
|
||||
// ⚡ 新增:事件更新回调注册方法
|
||||
registerEventUpdateCallback,
|
||||
unregisterEventUpdateCallback,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
90
src/utils/debounce.ts
Normal file
90
src/utils/debounce.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
// src/utils/debounce.ts
|
||||
// 防抖工具函数(TypeScript 版本)
|
||||
|
||||
/**
|
||||
* 防抖函数返回类型
|
||||
* @template T - 原函数类型
|
||||
*/
|
||||
export interface DebouncedFunction<T extends (...args: any[]) => any> {
|
||||
/**
|
||||
* 执行防抖后的函数
|
||||
* @param args - 原函数的参数
|
||||
*/
|
||||
(...args: Parameters<T>): void;
|
||||
|
||||
/**
|
||||
* 取消待执行的函数调用
|
||||
*/
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数 - 延迟执行,短时间内多次调用只执行最后一次
|
||||
*
|
||||
* 工作原理:
|
||||
* 1. 调用防抖函数时,清除之前的定时器
|
||||
* 2. 设置新的定时器,延迟 delay 毫秒后执行
|
||||
* 3. 如果在延迟期间再次调用,重复步骤 1-2
|
||||
* 4. 只有最后一次调用会在延迟后实际执行
|
||||
*
|
||||
* 使用场景:
|
||||
* - 搜索框输入:用户停止输入后才发送请求
|
||||
* - 窗口 resize:窗口调整结束后才重新计算布局
|
||||
* - Socket 事件:短时间内收到多个事件,只处理最后一个
|
||||
*
|
||||
* @template T - 函数类型(泛型约束:任意函数)
|
||||
* @param {T} func - 要防抖的函数
|
||||
* @param {number} delay - 延迟时间(毫秒)
|
||||
* @returns {DebouncedFunction<T>} 防抖后的函数(带 cancel 方法)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 示例 1:无参数函数
|
||||
* const debouncedSave = debounce(() => {
|
||||
* console.log('保存数据');
|
||||
* }, 1000);
|
||||
*
|
||||
* debouncedSave(); // 1秒后执行
|
||||
* debouncedSave(); // 取消上次,重新计时 1 秒
|
||||
* debouncedSave.cancel(); // 取消执行
|
||||
*
|
||||
* // 示例 2:带参数函数
|
||||
* const debouncedSearch = debounce((keyword: string) => {
|
||||
* console.log('搜索:', keyword);
|
||||
* }, 500);
|
||||
*
|
||||
* debouncedSearch('react'); // 500ms 后执行
|
||||
* debouncedSearch('redux'); // 取消上次,重新计时
|
||||
* ```
|
||||
*/
|
||||
export function debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
delay: number
|
||||
): DebouncedFunction<T> {
|
||||
// 使用 NodeJS.Timeout 类型(支持浏览器和 Node 环境)
|
||||
let timerId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
// 防抖函数主体
|
||||
const debouncedFn = (...args: Parameters<T>): void => {
|
||||
// 清除之前的定时器(防抖核心逻辑)
|
||||
if (timerId !== null) {
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
|
||||
// 设置新的定时器
|
||||
timerId = setTimeout(() => {
|
||||
func(...args);
|
||||
timerId = null; // 执行后重置定时器 ID
|
||||
}, delay);
|
||||
};
|
||||
|
||||
// 添加 cancel 方法(用于组件卸载时清理)
|
||||
debouncedFn.cancel = (): void => {
|
||||
if (timerId !== null) {
|
||||
clearTimeout(timerId);
|
||||
timerId = null;
|
||||
}
|
||||
};
|
||||
|
||||
return debouncedFn;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/views/Community/components/DynamicNewsCard.js
|
||||
// 横向滚动事件卡片组件(实时要闻·动态追踪)
|
||||
|
||||
import React, { forwardRef, useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||
import React, { forwardRef, useState, useEffect, useMemo, useCallback, useRef, useImperativeHandle } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import {
|
||||
Card,
|
||||
@@ -44,8 +44,9 @@ import {
|
||||
selectFourRowEventsWithLoading
|
||||
} from '../../../store/slices/communityDataSlice';
|
||||
import { usePagination } from './DynamicNewsCard/hooks/usePagination';
|
||||
import { PAGINATION_CONFIG, DISPLAY_MODES } from './DynamicNewsCard/constants';
|
||||
import { PAGINATION_CONFIG, DISPLAY_MODES, REFRESH_DEBOUNCE_DELAY } from './DynamicNewsCard/constants';
|
||||
import { PROFESSIONAL_COLORS } from '../../../constants/professionalTheme';
|
||||
import { debounce } from '../../../utils/debounce';
|
||||
|
||||
// 🔍 调试:渲染计数器
|
||||
let dynamicNewsCardRenderCount = 0;
|
||||
@@ -84,6 +85,7 @@ const DynamicNewsCard = forwardRef(({
|
||||
// Refs
|
||||
const cardHeaderRef = useRef(null);
|
||||
const cardBodyRef = useRef(null);
|
||||
const virtualizedGridRef = useRef(null); // ⚡ VirtualizedFourRowGrid 的 ref(用于获取滚动位置)
|
||||
|
||||
// 从 Redux 读取关注状态
|
||||
const eventFollowStatus = useSelector(selectEventFollowStatus);
|
||||
@@ -208,6 +210,124 @@ const [currentMode, setCurrentMode] = useState('vertical');
|
||||
setCurrentMode(mode);
|
||||
}, [mode]);
|
||||
|
||||
/**
|
||||
* ⚡【核心逻辑】执行刷新的回调函数(包含原有的智能刷新逻辑)
|
||||
*
|
||||
* 此函数会被 debounce 包装,避免短时间内频繁刷新
|
||||
*/
|
||||
const executeRefresh = useCallback(() => {
|
||||
const state = {
|
||||
mode,
|
||||
currentPage: pagination?.current_page || 1,
|
||||
};
|
||||
|
||||
console.log('[DynamicNewsCard] ⏰ executeRefresh() 执行(防抖延迟后)', state);
|
||||
|
||||
if (mode === 'vertical') {
|
||||
// ========== 纵向模式 ==========
|
||||
// 只在第1页时刷新,避免打断用户浏览其他页
|
||||
if (state.currentPage === 1) {
|
||||
console.log('[DynamicNewsCard] 纵向模式 + 第1页 → 刷新列表');
|
||||
handlePageChange(1); // 清空缓存并刷新第1页
|
||||
toast({
|
||||
title: '检测到新事件',
|
||||
status: 'info',
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
});
|
||||
} else {
|
||||
console.log(`[DynamicNewsCard] 纵向模式 + 第${state.currentPage}页 → 不刷新(避免打断用户)`);
|
||||
}
|
||||
} else if (mode === 'four-row') {
|
||||
// ========== 平铺模式 ==========
|
||||
// 检查滚动位置,只有在顶部时才刷新
|
||||
const scrollPos = virtualizedGridRef.current?.getScrollPosition();
|
||||
|
||||
if (scrollPos?.isNearTop) {
|
||||
// 用户在顶部 10% 区域,安全刷新
|
||||
console.log('[DynamicNewsCard] 平铺模式 + 滚动在顶部 → 刷新列表');
|
||||
handlePageChange(1); // 清空并刷新
|
||||
toast({
|
||||
title: '检测到新事件,已刷新',
|
||||
status: 'info',
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
});
|
||||
} else {
|
||||
// 用户不在顶部,显示提示但不自动刷新
|
||||
console.log('[DynamicNewsCard] 平铺模式 + 滚动不在顶部 → 仅提示,不刷新');
|
||||
toast({
|
||||
title: '有新事件发布',
|
||||
description: '滚动到顶部查看',
|
||||
status: 'info',
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [mode, pagination, handlePageChange, toast]);
|
||||
|
||||
/**
|
||||
* ⚡【防抖包装】创建防抖版本的刷新函数
|
||||
*
|
||||
* 使用 useMemo 确保防抖函数在 executeRefresh 不变时保持引用稳定
|
||||
* 防抖延迟:REFRESH_DEBOUNCE_DELAY (2000ms)
|
||||
*
|
||||
* 效果:短时间内收到多个新事件,只执行最后一次刷新
|
||||
*/
|
||||
const debouncedRefresh = useMemo(
|
||||
() => debounce(executeRefresh, REFRESH_DEBOUNCE_DELAY),
|
||||
[executeRefresh]
|
||||
);
|
||||
|
||||
/**
|
||||
* ⚡ 暴露方法给父组件(用于 Socket 自动刷新)
|
||||
*/
|
||||
useImperativeHandle(ref, () => ({
|
||||
/**
|
||||
* 智能刷新方法(带防抖,避免频繁刷新)
|
||||
*
|
||||
* 调用此方法时:
|
||||
* 1. 清除之前的定时器(如果有)
|
||||
* 2. 设置新的定时器(延迟 REFRESH_DEBOUNCE_DELAY 后执行)
|
||||
* 3. 如果在延迟期间再次调用,重复步骤 1-2
|
||||
* 4. 只有最后一次调用会在延迟后实际执行 executeRefresh()
|
||||
*/
|
||||
refresh: () => {
|
||||
console.log('[DynamicNewsCard] 🔔 refresh() 被调用(设置防抖定时器)', {
|
||||
mode,
|
||||
currentPage: pagination?.current_page || 1,
|
||||
debounceDelay: `${REFRESH_DEBOUNCE_DELAY}ms`,
|
||||
});
|
||||
|
||||
// 调用防抖包装后的函数
|
||||
debouncedRefresh();
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取当前状态(用于调试)
|
||||
*/
|
||||
getState: () => ({
|
||||
mode,
|
||||
currentPage: pagination?.current_page || 1,
|
||||
totalPages: pagination?.total_pages || 1,
|
||||
total: pagination?.total || 0,
|
||||
loading,
|
||||
}),
|
||||
}), [mode, pagination, loading, debouncedRefresh]);
|
||||
|
||||
/**
|
||||
* ⚡【清理逻辑】组件卸载时取消待执行的防抖函数
|
||||
*
|
||||
* 作用:避免组件卸载后仍然执行刷新操作(防止内存泄漏和潜在错误)
|
||||
*/
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
console.log('[DynamicNewsCard] 🧹 组件卸载,取消待执行的防抖刷新');
|
||||
debouncedRefresh.cancel();
|
||||
};
|
||||
}, [debouncedRefresh]);
|
||||
|
||||
// 监听 error 状态,显示空数据提示
|
||||
useEffect(() => {
|
||||
if (error && error.includes('暂无更多数据')) {
|
||||
@@ -578,6 +698,7 @@ const [currentMode, setCurrentMode] = useState('vertical');
|
||||
eventFollowStatus={eventFollowStatus}
|
||||
onToggleFollow={handleToggleFollow}
|
||||
hasMore={hasMore}
|
||||
virtualizedGridRef={virtualizedGridRef} // ⚡ 传递 ref 给 VirtualizedFourRowGrid
|
||||
/>
|
||||
</Box>
|
||||
</CardBody>
|
||||
|
||||
@@ -28,6 +28,7 @@ import VerticalModeLayout from './VerticalModeLayout';
|
||||
* @param {boolean} hasMore - 是否还有更多数据
|
||||
* @param {Object} eventFollowStatus - 事件关注状态 { [eventId]: { isFollowing, followerCount } }
|
||||
* @param {Function} onToggleFollow - 关注按钮回调
|
||||
* @param {React.Ref} virtualizedGridRef - VirtualizedFourRowGrid 的 ref(用于获取滚动位置)
|
||||
*/
|
||||
const EventScrollList = ({
|
||||
events,
|
||||
@@ -46,7 +47,8 @@ const EventScrollList = ({
|
||||
mode = 'vertical',
|
||||
hasMore = true,
|
||||
eventFollowStatus = {},
|
||||
onToggleFollow
|
||||
onToggleFollow,
|
||||
virtualizedGridRef
|
||||
}) => {
|
||||
const scrollContainerRef = useRef(null);
|
||||
|
||||
@@ -111,6 +113,7 @@ const EventScrollList = ({
|
||||
>
|
||||
{/* 平铺网格模式 - 使用虚拟滚动 + 双向无限滚动 */}
|
||||
<VirtualizedFourRowGrid
|
||||
ref={virtualizedGridRef} // ⚡ 传递 ref(用于获取滚动位置)
|
||||
display={mode === 'four-row' ? 'block' : 'none'}
|
||||
columnsPerRow={4} // 每行显示4列
|
||||
events={displayEvents || events} // 使用累积列表(如果有)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/views/Community/components/DynamicNewsCard/VirtualizedFourRowGrid.js
|
||||
// 虚拟化网格组件(支持多列布局 + 纵向滚动 + 无限滚动)
|
||||
|
||||
import React, { useRef, useMemo, useEffect } from 'react';
|
||||
import React, { useRef, useMemo, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { Box, Grid, Spinner, Text, VStack, Center, HStack, IconButton, useBreakpointValue } from '@chakra-ui/react';
|
||||
import { RepeatIcon } from '@chakra-ui/icons';
|
||||
@@ -25,7 +25,7 @@ import DynamicNewsEventCard from '../EventCard/DynamicNewsEventCard';
|
||||
* @param {boolean} props.hasMore - 是否还有更多数据
|
||||
* @param {boolean} props.loading - 加载状态
|
||||
*/
|
||||
const VirtualizedFourRowGrid = ({
|
||||
const VirtualizedFourRowGrid = forwardRef(({
|
||||
display = 'block',
|
||||
events,
|
||||
columnsPerRow = 4,
|
||||
@@ -42,7 +42,7 @@ const VirtualizedFourRowGrid = ({
|
||||
loading,
|
||||
error, // 新增:错误状态
|
||||
onRetry, // 新增:重试回调
|
||||
}) => {
|
||||
}, ref) => {
|
||||
const parentRef = useRef(null);
|
||||
const isLoadingMore = useRef(false); // 防止重复加载
|
||||
const lastRefreshTime = useRef(0); // 记录上次刷新时间(用于30秒防抖)
|
||||
@@ -81,6 +81,31 @@ const VirtualizedFourRowGrid = ({
|
||||
overscan: 2, // 预加载2行(上下各1行)
|
||||
});
|
||||
|
||||
/**
|
||||
* ⚡ 暴露方法给父组件(用于 Socket 刷新判断)
|
||||
*/
|
||||
useImperativeHandle(ref, () => ({
|
||||
/**
|
||||
* 获取当前滚动位置信息
|
||||
* @returns {Object|null} 滚动位置信息
|
||||
*/
|
||||
getScrollPosition: () => {
|
||||
const scrollElement = parentRef.current;
|
||||
if (!scrollElement) return null;
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = scrollElement;
|
||||
const isNearTop = scrollTop < clientHeight * 0.1; // 顶部 10% 区域
|
||||
|
||||
return {
|
||||
scrollTop,
|
||||
scrollHeight,
|
||||
clientHeight,
|
||||
isNearTop,
|
||||
scrollPercentage: ((scrollTop + clientHeight) / scrollHeight) * 100,
|
||||
};
|
||||
},
|
||||
}), []);
|
||||
|
||||
/**
|
||||
* 【核心逻辑1】无限滚动 + 顶部刷新 - 监听滚动事件,根据滚动位置自动加载数据或刷新
|
||||
*
|
||||
@@ -360,6 +385,8 @@ const VirtualizedFourRowGrid = ({
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
VirtualizedFourRowGrid.displayName = 'VirtualizedFourRowGrid';
|
||||
|
||||
export default VirtualizedFourRowGrid;
|
||||
|
||||
@@ -38,3 +38,21 @@ export const TOAST_CONFIG = {
|
||||
DURATION_ERROR: 3000, // 错误提示持续时间(毫秒)
|
||||
DURATION_WARNING: 2000, // 警告提示持续时间(毫秒)
|
||||
};
|
||||
|
||||
// ========== Socket 刷新防抖配置 ==========
|
||||
/**
|
||||
* Socket 新事件刷新防抖延迟(毫秒)
|
||||
*
|
||||
* 作用:避免短时间内收到多个新事件时频繁刷新列表
|
||||
*
|
||||
* 场景示例:
|
||||
* - 第 1 秒:收到新事件 → 延迟 2 秒刷新
|
||||
* - 第 2 秒:收到新事件 → 取消上次,重新延迟 2 秒
|
||||
* - 第 3 秒:收到新事件 → 取消上次,重新延迟 2 秒
|
||||
* - 第 5 秒:触发刷新 → 只发送 1 次 API 请求
|
||||
*
|
||||
* 推荐值:2000ms (2 秒)
|
||||
* - 太短(如 500ms)→ 仍可能触发多次刷新
|
||||
* - 太长(如 5000ms)→ 用户感知延迟过高
|
||||
*/
|
||||
export const REFRESH_DEBOUNCE_DELAY = 2000;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/views/Community/index.js
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import {
|
||||
fetchPopularKeywords,
|
||||
@@ -40,6 +40,7 @@ import { PROFESSIONAL_COLORS } from '../../constants/professionalTheme';
|
||||
|
||||
const Community = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation(); // ⚡ 获取当前路由信息(用于判断是否在 /community 页面)
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Redux状态
|
||||
@@ -71,7 +72,10 @@ const Community = () => {
|
||||
});
|
||||
|
||||
// ⚡ 通知权限引导
|
||||
const { browserPermission, requestBrowserPermission } = useNotification();
|
||||
const { browserPermission, requestBrowserPermission, registerEventUpdateCallback } = useNotification();
|
||||
|
||||
// ⚡ DynamicNewsCard 的 ref(用于触发刷新)
|
||||
const dynamicNewsCardRef = useRef(null);
|
||||
|
||||
// 通知横幅显示状态
|
||||
const [showNotificationBanner, setShowNotificationBanner] = useState(false);
|
||||
@@ -160,6 +164,63 @@ const Community = () => {
|
||||
return () => clearTimeout(timer);
|
||||
}, []); // 空依赖数组,只在组件挂载时执行一次
|
||||
|
||||
/**
|
||||
* ⚡ 【核心逻辑】注册 Socket 新事件回调 - 当收到新事件时智能刷新列表
|
||||
*
|
||||
* 工作流程:
|
||||
* 1. Socket 收到 'new_event' 事件 → NotificationContext 触发所有注册的回调
|
||||
* 2. 本回调被触发 → 检查当前路由是否为 /community
|
||||
* 3. 如果在 /community 页面 → 调用 DynamicNewsCard.refresh() 方法
|
||||
* 4. DynamicNewsCard 根据模式和滚动位置决定是否刷新:
|
||||
* - 纵向模式 + 第1页 → 刷新列表
|
||||
* - 纵向模式 + 其他页 → 不刷新(避免打断用户)
|
||||
* - 平铺模式 + 滚动在顶部 → 刷新列表
|
||||
* - 平铺模式 + 滚动不在顶部 → 仅显示 Toast 提示
|
||||
*
|
||||
* 设计要点:
|
||||
* - 使用 registerEventUpdateCallback 注册回调,返回的函数用于清理
|
||||
* - 路由检查:只在 /community 页面触发刷新
|
||||
* - 智能刷新:由 DynamicNewsCard 根据上下文决定刷新策略
|
||||
* - 自动清理:组件卸载时自动注销回调
|
||||
*/
|
||||
useEffect(() => {
|
||||
// 定义回调函数
|
||||
const handleNewEvent = (eventData) => {
|
||||
console.log('[Community] 🔔 收到新事件通知', {
|
||||
currentPath: location.pathname,
|
||||
eventData,
|
||||
});
|
||||
|
||||
// 检查是否在 /community 页面
|
||||
if (location.pathname === '/community') {
|
||||
console.log('[Community] ✅ 当前在事件中心页面,触发 DynamicNewsCard 刷新');
|
||||
|
||||
// 调用 DynamicNewsCard 的 refresh 方法(智能刷新)
|
||||
if (dynamicNewsCardRef.current) {
|
||||
dynamicNewsCardRef.current.refresh();
|
||||
} else {
|
||||
console.warn('[Community] ⚠️ DynamicNewsCard ref 不可用,无法触发刷新');
|
||||
}
|
||||
} else {
|
||||
console.log('[Community] ⏭️ 当前不在事件中心页面,跳过刷新', {
|
||||
currentPath: location.pathname,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 注册回调(返回清理函数)
|
||||
const unregister = registerEventUpdateCallback(handleNewEvent);
|
||||
console.log('[Community] ✅ 已注册 Socket 事件更新回调');
|
||||
|
||||
// 组件卸载时清理
|
||||
return () => {
|
||||
if (unregister) {
|
||||
unregister();
|
||||
console.log('[Community] 🧹 已注销 Socket 事件更新回调');
|
||||
}
|
||||
};
|
||||
}, [location.pathname, registerEventUpdateCallback]); // 依赖路由变化重新注册
|
||||
|
||||
return (
|
||||
<Box minH="100vh" bg={bgColor}>
|
||||
{/* 主内容区域 */}
|
||||
@@ -206,6 +267,7 @@ const Community = () => {
|
||||
|
||||
{/* 实时要闻·动态追踪 - 横向滚动 */}
|
||||
<DynamicNewsCard
|
||||
ref={dynamicNewsCardRef} // ⚡ 传递 ref(用于触发刷新)
|
||||
filters={filters}
|
||||
popularKeywords={popularKeywords}
|
||||
lastUpdateTime={lastUpdateTime}
|
||||
|
||||
Reference in New Issue
Block a user