Files
vf_react/src/views/Community/components/DynamicNewsCard/PaginationControl.js
zdl 3b5caf8a01 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>
2025-11-03 12:38:25 +08:00

212 lines
5.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/views/Community/components/DynamicNewsCard/PaginationControl.js
// 分页控制器组件
import React, { useState } from 'react';
import {
Box,
HStack,
Button,
Input,
Text,
IconButton,
useColorModeValue,
useToast,
} from '@chakra-ui/react';
import {
ChevronLeftIcon,
ChevronRightIcon,
} from '@chakra-ui/icons';
/**
* 分页控制器组件
* @param {number} currentPage - 当前页码
* @param {number} totalPages - 总页数
* @param {Function} onPageChange - 页码改变回调
*/
const PaginationControl = ({ currentPage, totalPages, onPageChange }) => {
const [jumpPage, setJumpPage] = useState('');
const toast = useToast();
const buttonBg = useColorModeValue('white', 'gray.700');
const activeBg = useColorModeValue('blue.500', 'blue.400');
const activeColor = useColorModeValue('white', 'white');
const borderColor = useColorModeValue('gray.300', 'gray.600');
const hoverBg = useColorModeValue('gray.100', 'gray.600');
// 生成页码数字列表(智能省略)
const getPageNumbers = () => {
const pageNumbers = [];
const maxVisible = 5; // 最多显示5个页码精简版
if (totalPages <= maxVisible) {
// 总页数少,显示全部
for (let i = 1; i <= totalPages; i++) {
pageNumbers.push(i);
}
} else {
// 总页数多,使用省略号
if (currentPage <= 3) {
// 当前页在前面
for (let i = 1; i <= 4; i++) {
pageNumbers.push(i);
}
pageNumbers.push('...');
pageNumbers.push(totalPages);
} else if (currentPage >= totalPages - 2) {
// 当前页在后面
pageNumbers.push(1);
pageNumbers.push('...');
for (let i = totalPages - 3; i <= totalPages; i++) {
pageNumbers.push(i);
}
} else {
// 当前页在中间
pageNumbers.push(1);
pageNumbers.push('...');
pageNumbers.push(currentPage);
pageNumbers.push('...');
pageNumbers.push(totalPages);
}
}
return pageNumbers;
};
// 处理页码跳转
const handleJump = () => {
const page = parseInt(jumpPage, 10);
if (isNaN(page)) {
toast({
title: '请输入有效的页码',
status: 'warning',
duration: 2000,
isClosable: true,
});
return;
}
if (page < 1 || page > totalPages) {
toast({
title: `页码范围1 - ${totalPages}`,
status: 'warning',
duration: 2000,
isClosable: true,
});
return;
}
onPageChange(page);
setJumpPage('');
};
// 处理回车键
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
handleJump();
}
};
const pageNumbers = getPageNumbers();
return (
<Box mb={3}>
<HStack spacing={1.5} justify="center" flexWrap="wrap">
{/* 上一页按钮 */}
<IconButton
icon={<ChevronLeftIcon />}
size="xs"
onClick={() => onPageChange(currentPage - 1)}
isDisabled={currentPage === 1}
bg={buttonBg}
borderWidth="1px"
borderColor={borderColor}
_hover={{ bg: hoverBg }}
aria-label="上一页"
title="上一页"
/>
{/* 数字页码列表 */}
{pageNumbers.map((page, index) => {
if (page === '...') {
return (
<Text
key={`ellipsis-${index}`}
px={1}
fontSize="xs"
color="gray.500"
>
...
</Text>
);
}
return (
<Button
key={page}
size="xs"
onClick={() => onPageChange(page)}
bg={currentPage === page ? activeBg : buttonBg}
color={currentPage === page ? activeColor : undefined}
borderWidth="1px"
borderColor={currentPage === page ? activeBg : borderColor}
_hover={{
bg: currentPage === page ? activeBg : hoverBg,
}}
minW="28px"
>
{page}
</Button>
);
})}
{/* 下一页按钮 */}
<IconButton
icon={<ChevronRightIcon />}
size="xs"
onClick={() => onPageChange(currentPage + 1)}
isDisabled={currentPage === totalPages}
bg={buttonBg}
borderWidth="1px"
borderColor={borderColor}
_hover={{ bg: hoverBg }}
aria-label="下一页"
title="下一页"
/>
{/* 分隔线 */}
<Box w="1px" h="20px" bg={borderColor} mx={1.5} />
{/* 输入框跳转 */}
<HStack spacing={1.5}>
<Text fontSize="xs" color="gray.600">
跳转到
</Text>
<Input
size="xs"
width="50px"
type="number"
min={1}
max={totalPages}
value={jumpPage}
onChange={(e) => setJumpPage(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="页"
bg={buttonBg}
borderColor={borderColor}
/>
<Button
size="xs"
colorScheme="blue"
onClick={handleJump}
>
跳转
</Button>
</HStack>
</HStack>
</Box>
);
};
export default PaginationControl;