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:
zdl
2025-11-14 19:04:00 +08:00
parent 9fd618c087
commit ddd6b2d4af
7 changed files with 382 additions and 9 deletions

View File

@@ -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} // 使用累积列表(如果有)

View File

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

View File

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