Pagination.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import React from 'react';
  2. import { useSearchParams } from 'react-router-dom';
  3. interface PaginationProps {
  4. total: number;
  5. totalPages: number;
  6. currentPage: number;
  7. onPageChange?: (page: number) => void;
  8. showTotal?: boolean;
  9. scrollToTop?: boolean;
  10. pageSize?: number;
  11. pageSizeOptions?: number[];
  12. onPageSizeChange?: (size: number) => void;
  13. }
  14. const Pagination: React.FC<PaginationProps> = ({
  15. total,
  16. totalPages,
  17. currentPage,
  18. onPageChange,
  19. showTotal = true,
  20. scrollToTop = true,
  21. pageSize = 10,
  22. pageSizeOptions = [],
  23. onPageSizeChange,
  24. }) => {
  25. const [searchParams, setSearchParams] = useSearchParams();
  26. // 更新 URL 页码的辅助函数
  27. const updatePageInUrl = (page: number) => {
  28. setSearchParams(prev => {
  29. const newParams = new URLSearchParams(prev);
  30. if (page === 1) {
  31. newParams.delete('page');
  32. } else {
  33. newParams.set('page', page.toString());
  34. }
  35. return newParams;
  36. });
  37. // 如果提供了自定义的 onPageChange 回调,也调用它
  38. if (onPageChange) onPageChange(page);
  39. // 滚动到顶部
  40. if (scrollToTop) {
  41. const mainElement = document.querySelector('main');
  42. if (mainElement) {
  43. mainElement.scrollTop = 0;
  44. }
  45. }
  46. };
  47. // 如果只有一页,不显示分页
  48. if (totalPages <= 1 && (!pageSizeOptions || pageSizeOptions.length === 0)) {
  49. return null;
  50. }
  51. // 生成页码数组
  52. const generatePages = (): (number | string)[] => {
  53. const pages: (number | string)[] = [];
  54. if (totalPages <= 7) {
  55. // 如果总页数少于等于7,显示所有页码
  56. for (let i = 1; i <= totalPages; i++) {
  57. pages.push(i);
  58. }
  59. } else {
  60. // 如果总页数大于7,显示带省略号的页码
  61. if (currentPage <= 3) {
  62. // 当前页在前3页
  63. for (let i = 1; i <= 4; i++) {
  64. pages.push(i);
  65. }
  66. pages.push('...');
  67. pages.push(totalPages);
  68. } else if (currentPage >= totalPages - 2) {
  69. // 当前页在后3页
  70. pages.push(1);
  71. pages.push('...');
  72. for (let i = totalPages - 3; i <= totalPages; i++) {
  73. pages.push(i);
  74. }
  75. } else {
  76. // 当前页在中间
  77. pages.push(1);
  78. pages.push('...');
  79. for (let i = currentPage - 1; i <= currentPage + 1; i++) {
  80. pages.push(i);
  81. }
  82. pages.push('...');
  83. pages.push(totalPages);
  84. }
  85. }
  86. return pages;
  87. };
  88. const pages = generatePages();
  89. return (
  90. <div className="flex items-center justify-center gap-2 mt-6">
  91. {showTotal && (
  92. <span className="text-sm text-gray-600 mr-2">
  93. 共{total}条
  94. </span>
  95. )}
  96. {pageSizeOptions && pageSizeOptions.length > 0 && (
  97. <div className="flex items-center gap-2 mr-4">
  98. <span className="text-sm text-gray-600">每页</span>
  99. <select
  100. value={pageSize}
  101. onChange={(e) => {
  102. const size = Number(e.target.value);
  103. if (onPageSizeChange) onPageSizeChange(size);
  104. // 重置到第一页
  105. updatePageInUrl(1);
  106. }}
  107. className="border border-gray-200 rounded-lg px-2 py-1 text-sm"
  108. >
  109. {pageSizeOptions.map((opt) => (
  110. <option key={opt} value={opt}>{opt}</option>
  111. ))}
  112. </select>
  113. </div>
  114. )}
  115. <div className="flex items-center gap-1">
  116. {pages.map((page, index) => {
  117. if (page === '...') {
  118. return (
  119. <span key={`ellipsis-${index}`} className="px-2 text-gray-400">
  120. </span>
  121. );
  122. }
  123. const pageNum = page as number;
  124. return (
  125. <button
  126. key={pageNum}
  127. onClick={() => updatePageInUrl(pageNum)}
  128. className={`px-3 py-1.5 text-sm font-bold rounded-lg transition-colors ${
  129. pageNum === currentPage
  130. ? 'bg-blue-600 text-white'
  131. : 'bg-white border border-gray-200 text-gray-600 hover:bg-gray-50'
  132. }`}
  133. >
  134. {pageNum}
  135. </button>
  136. );
  137. })}
  138. </div>
  139. <button
  140. onClick={() => {
  141. if (currentPage > 1) {
  142. updatePageInUrl(currentPage - 1);
  143. }
  144. }}
  145. disabled={currentPage === 1}
  146. className="px-4 py-2 bg-white border border-gray-200 rounded-lg text-sm font-bold disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 transition-colors"
  147. >
  148. 上一页
  149. </button>
  150. <button
  151. onClick={() => {
  152. if (currentPage < totalPages) {
  153. updatePageInUrl(currentPage + 1);
  154. }
  155. }}
  156. disabled={currentPage === totalPages}
  157. className="px-4 py-2 bg-white border border-gray-200 rounded-lg text-sm font-bold disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 transition-colors"
  158. >
  159. 下一页
  160. </button>
  161. </div>
  162. );
  163. };
  164. export default Pagination;