feat: 实现实时要闻服务端分页功能

功能新增:
- 实时要闻组件支持服务端分页,每次切换页码重新请求数据
- 分页控制器组件,支持数字页码、上下翻页、快速跳转
- Mock 数据量从 100 条增加到 200 条,支持分页测试

技术实现:

1. Redux 状态管理(communityDataSlice.js)
   - fetchDynamicNews 接收分页参数 { page, per_page }
   - 返回数据结构调整为 { events, pagination }
   - initialState 新增 dynamicNewsPagination 字段
   - Reducer 分别存储 events 和 pagination 信息
   - Selector 返回完整的 pagination 数据

2. 组件层(index.js → DynamicNewsCard → EventScrollList)
   - Community/index.js: 获取并传递 pagination 信息
   - DynamicNewsCard.js: 管理分页状态,触发服务端请求
   - EventScrollList.js: 接收服务端 totalPages,渲染当前页数据
   - 页码切换时自动选中第一个事件

3. 分页控制器(PaginationControl.js)
   - 精简版设计:移除首页/末页按钮
   - 上一页/下一页按钮,边界状态自动禁用
   - 智能页码列表(最多5个,使用省略号)
   - 输入框跳转功能,支持回车键
   - Toast 提示非法输入
   - 全部使用 xs 尺寸,紧凑布局

4. Mock 数据(events.js)
   - 总事件数从 100 增加到 200 条
   - 支持服务端分页测试(40 页 × 5 条/页)

分页流程:
1. 初始加载:请求 page=1, per_page=5
2. 切换页码:dispatch(fetchDynamicNews({ page: 2, per_page: 5 }))
3. 后端返回:{ events: [5条], pagination: { page, total, total_pages } }
4. 前端更新:显示新页面数据,更新分页控制器状态

UI 优化:
- 紧凑的分页控制器布局
- 移除冗余元素(首页/末页/总页数提示)
- xs 尺寸按钮,减少视觉负担
- 保留核心功能(翻页、页码、跳转)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-11-03 12:38:25 +08:00
parent 39a2ccd53b
commit cc2777ae20
6 changed files with 304 additions and 15 deletions

View File

@@ -159,28 +159,33 @@ export const fetchHotEvents = createAsyncThunk(
/**
* 获取动态新闻(无缓存,每次都发起请求)
* 用于 DynamicNewsCard 组件,需要保持实时性
* @param {Object} params - 分页参数 { page, per_page }
*/
export const fetchDynamicNews = createAsyncThunk(
'communityData/fetchDynamicNews',
async (_, { rejectWithValue }) => {
async ({ page = 1, per_page = 5 } = {}, { rejectWithValue }) => {
try {
logger.debug('CommunityData', '开始获取动态新闻');
logger.debug('CommunityData', '开始获取动态新闻', { page, per_page });
const response = await eventService.getEvents({
page: 1,
per_page: 5,
page,
per_page,
sort: 'new'
});
if (response.success && response.data?.events) {
logger.info('CommunityData', '动态新闻加载成功', {
count: response.data.events.length,
page: response.data.pagination?.page || page,
total: response.data.pagination?.total || 0
});
return response.data.events;
return {
events: response.data.events,
pagination: response.data.pagination || {}
};
}
logger.warn('CommunityData', '动态新闻返回数据为空', response);
return [];
return { events: [], pagination: {} };
} catch (error) {
logger.error('CommunityData', '获取动态新闻失败', error);
return rejectWithValue(error.message || '获取动态新闻失败');
@@ -197,6 +202,7 @@ const communityDataSlice = createSlice({
popularKeywords: [],
hotEvents: [],
dynamicNews: [], // 动态新闻(无缓存)
dynamicNewsPagination: {}, // 动态新闻分页信息
// 加载状态
loading: {
@@ -279,7 +285,24 @@ const communityDataSlice = createSlice({
// 使用工厂函数创建 reducers消除重复代码
createDataReducers(builder, fetchPopularKeywords, 'popularKeywords');
createDataReducers(builder, fetchHotEvents, 'hotEvents');
createDataReducers(builder, fetchDynamicNews, 'dynamicNews');
// dynamicNews 需要特殊处理(包含 pagination
builder
.addCase(fetchDynamicNews.pending, (state) => {
state.loading.dynamicNews = true;
state.error.dynamicNews = null;
})
.addCase(fetchDynamicNews.fulfilled, (state, action) => {
state.loading.dynamicNews = false;
state.dynamicNews = action.payload.events;
state.dynamicNewsPagination = action.payload.pagination;
state.lastUpdated.dynamicNews = new Date().toISOString();
})
.addCase(fetchDynamicNews.rejected, (state, action) => {
state.loading.dynamicNews = false;
state.error.dynamicNews = action.payload;
logger.error('CommunityData', 'dynamicNews 加载失败', new Error(action.payload));
});
}
});
@@ -314,6 +337,7 @@ export const selectDynamicNewsWithLoading = (state) => ({
data: state.communityData.dynamicNews,
loading: state.communityData.loading.dynamicNews,
error: state.communityData.error.dynamicNews,
pagination: state.communityData.dynamicNewsPagination,
lastUpdated: state.communityData.lastUpdated.dynamicNews
});