foundation.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. /* eslint-disable max-len */
  2. /* eslint-disable no-param-reassign */
  3. import BaseFoundation, { DefaultAdapter } from '../base/foundation';
  4. import { numbers } from './constants';
  5. export interface PaginationAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
  6. setPageList: (pageListState: AdapterPageList) => void;
  7. setDisabled: (prevIsDisabled: boolean, nextIsDisabled: boolean) => void;
  8. updateTotal: (total: number) => void;
  9. updatePageSize: (pageSize: number) => void;
  10. updateQuickJumpPage: (quickJumpPage: string | number) => void;
  11. setCurrentPage: (pageIndex: number) => void;
  12. registerKeyDownHandler: (handler: KeyDownHandler) => void;
  13. unregisterKeyDownHandler: (handler: KeyDownHandler) => void;
  14. notifyPageChange: (pageIndex: number | string) => void;
  15. notifyPageSizeChange: (pageSize: number) => void;
  16. notifyChange: (pageIndex: number | string, pageSize: number) => void;
  17. }
  18. export type PageRenderText = number | '...';
  19. export type PageList = PageRenderText[];
  20. export interface AdapterPageList {
  21. pageList: PageRenderText[];
  22. restLeftPageList: number[];
  23. restRightPageList: number[];
  24. }
  25. export type KeyDownHandler = any;
  26. class PaginationFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<PaginationAdapter<P, S>, P, S> {
  27. constructor(adapter: PaginationAdapter<P, S>) {
  28. super({ ...adapter });
  29. }
  30. init() {
  31. const { currentPage, total, pageSize } = this.getStates();
  32. // If pageSize is set, pageSizeOpts does not work
  33. this._updateDisabled({ currentPage, total, pageSize });
  34. this._updatePageList({ currentPage, total, pageSize });
  35. this._registerEventHandler();
  36. }
  37. destroy() {
  38. this._unregisterEventHandler();
  39. }
  40. _registerEventHandler() {
  41. this._adapter.registerKeyDownHandler(this.handleKeyDown);
  42. }
  43. _unregisterEventHandler() {
  44. this._adapter.unregisterKeyDownHandler(this.handleKeyDown);
  45. }
  46. _updateDisabled(pageInfo: { currentPage: number; total: number; pageSize: number }) {
  47. const { currentPage, total, pageSize } = pageInfo;
  48. const totalPageNum = this._getTotalPageNumber(total, pageSize);
  49. let prevIsDisabled = false;
  50. let nextIsDisabled = false;
  51. if (currentPage === 1) {
  52. prevIsDisabled = true;
  53. nextIsDisabled = totalPageNum < 2;
  54. } else if (currentPage === totalPageNum) {
  55. prevIsDisabled = false;
  56. nextIsDisabled = true;
  57. }
  58. this._adapter.setDisabled(prevIsDisabled, nextIsDisabled);
  59. }
  60. goPage(targetPageIndex: number | '...') {
  61. if (targetPageIndex === '...') {
  62. return;
  63. }
  64. const { pageSize, currentPage } = this.getStates();
  65. const isControlComponent = this._isInProps('currentPage');
  66. if (targetPageIndex === currentPage) {
  67. return;
  68. }
  69. if (!isControlComponent) {
  70. this.updatePage(targetPageIndex);
  71. this._adapter.notifyPageChange(targetPageIndex);
  72. this._adapter.notifyChange(targetPageIndex, pageSize);
  73. } else {
  74. this._adapter.notifyPageChange(targetPageIndex);
  75. this._adapter.notifyChange(targetPageIndex, pageSize);
  76. }
  77. }
  78. updatePage(targetPageIndex = 1, total?: number, pageSize?: number) {
  79. // maybe undefined or null
  80. if (total === null || typeof total === 'undefined') {
  81. total = this.getState('total');
  82. }
  83. if (pageSize === null || typeof pageSize === 'undefined') {
  84. pageSize = this.getState('pageSize');
  85. }
  86. this._updateDisabled({ currentPage: targetPageIndex, total, pageSize });
  87. this._updatePageList({ currentPage: targetPageIndex, total, pageSize });
  88. this._adapter.updateTotal(total);
  89. this._adapter.setCurrentPage(targetPageIndex);
  90. this._adapter.updatePageSize(pageSize);
  91. }
  92. goPrev() {
  93. const { currentPage } = this.getStates();
  94. if (currentPage > 1) {
  95. this.goPage(currentPage - 1);
  96. }
  97. }
  98. goNext() {
  99. const { currentPage, total, pageSize } = this.getStates();
  100. const totalPageNum = this._getTotalPageNumber(total, pageSize);
  101. if (currentPage <= totalPageNum - 1) {
  102. this.goPage(currentPage as number + 1);
  103. }
  104. }
  105. _updatePageList(pageListInfo: { currentPage: number; total: number; pageSize: number }) {
  106. const { currentPage, total, pageSize } = pageListInfo;
  107. let pageList: PageList = [];
  108. let restLeftPageList: number[] = []; // pages before ...
  109. let restRightPageList: number[] = []; // pages after ...
  110. /** Pager truncation logic (t is the total number of pages, c is the current page):
  111. - No need to truncate when t<=7 pages
  112. - When t>7
  113. - When c<4, the fourth is a truncation symbol (...)
  114. - When c=4, the sixth is the truncation symbol (...)
  115. - When 4<c<t-3, the second and sixth are truncation symbols (...)
  116. - When t-3<=c<=t, the second is the truncation symbol (...), followed by the 5th from the bottom-the 1st from the bottom
  117. Truncation character + number, the total number is 7
  118. 分页器截断逻辑(t为总页数,c为当前页):
  119. - t<=7 页的时候不需要截断
  120. - 当 t>7 时
  121. - 当 c<4 时,第4个为截断符号(...)
  122. - 当 c=4 时,第6个为截断符号(...)
  123. - 当 4<c<t-3 时,第2个与第6个为截断符号(...)
  124. - 当 t-3<=c<=t 时,第 2 个为截断符号(...),后面为倒数第5个-倒数第1个
  125. 截断符+数字 总共个数为7个
  126. */
  127. const totalPageNum = this._getTotalPageNumber(total, pageSize);
  128. const { PAGE_SHOW_MAX, REST_PAGE_MAX_SIZE } = numbers;
  129. if (totalPageNum <= PAGE_SHOW_MAX) {
  130. pageList = Array.from({ length: totalPageNum }, (v, i) => i + 1);
  131. restLeftPageList = [];
  132. restRightPageList = [];
  133. } else {
  134. switch (true) {
  135. case currentPage < 4:
  136. pageList = [1, 2, 3, 4, '...', totalPageNum - 1, totalPageNum];
  137. // length: (totalPageNum - 1) - 4
  138. restRightPageList = Array.from({ length: Math.min(totalPageNum - 6, REST_PAGE_MAX_SIZE) }, (v, i) => i + 5);
  139. restLeftPageList = [];
  140. break;
  141. case currentPage === 4:
  142. pageList = [1, 2, 3, 4, 5, '...', totalPageNum];
  143. restRightPageList = Array.from({ length: Math.min(totalPageNum - 6, REST_PAGE_MAX_SIZE) }, (v, i) => i + 6);
  144. restLeftPageList = [];
  145. break;
  146. case 4 < currentPage && currentPage < totalPageNum - 3:
  147. const middle = Array.from({ length: 3 }, (v, i) => currentPage + (i - 1));
  148. pageList = ([1] as PageList).concat('...', middle, '...', totalPageNum);
  149. // length: total-(currentPage+1)-1
  150. restRightPageList = Array.from(
  151. { length: Math.min(totalPageNum - currentPage - 2, REST_PAGE_MAX_SIZE) },
  152. (v, i) => currentPage + i + 2
  153. );
  154. restLeftPageList = Array.from({ length: Math.min(currentPage - 3, REST_PAGE_MAX_SIZE) }, (v, i) => i + 2);
  155. break;
  156. case currentPage - 3 <= currentPage && currentPage <= totalPageNum:
  157. const right = Array.from({ length: 5 }, (v, i) => totalPageNum - (4 - i));
  158. pageList = [1, '...' as const].concat(right);
  159. restRightPageList = [];
  160. restLeftPageList = Array.from({ length: Math.min(right[0] - 2, REST_PAGE_MAX_SIZE) }, (v, i) => i + 2);
  161. break;
  162. default:
  163. break;
  164. }
  165. }
  166. this._adapter.setPageList({ pageList, restLeftPageList, restRightPageList });
  167. // this._adapter.setRestLeftPageList(restLeftPageList);
  168. // this._adapter.setRestRightPageList(restRightPageList);
  169. }
  170. changePageSize(newPageSize: number) {
  171. const { pageSize } = this.getStates();
  172. this._adapter.updatePageSize(newPageSize);
  173. this._adapter.notifyPageSizeChange(newPageSize);
  174. const { total, currentPage } = this.getStates();
  175. // After converting the switching page capacity, which page is the current page
  176. const currentPageFirstItemIndex = (currentPage - 1) * pageSize + 1;
  177. const newCurrentPage = Math.ceil(currentPageFirstItemIndex / newPageSize);
  178. this.updatePage(newCurrentPage, total, newPageSize);
  179. if (currentPage !== newCurrentPage) {
  180. this._adapter.notifyPageChange(newCurrentPage);
  181. }
  182. this._adapter.notifyChange(newCurrentPage, newPageSize);
  183. }
  184. // TODO handle tab/enter events
  185. // eslint-disable-next-line @typescript-eslint/no-empty-function
  186. handleKeyDown() {
  187. }
  188. // If pageSize is not in the Opts array, insert it
  189. pageSizeInOpts() {
  190. const { pageSizeOpts } = this.getProps();
  191. const { pageSize } = this.getStates();
  192. const newPageSizeOpts = [...pageSizeOpts];
  193. if (newPageSizeOpts.indexOf(pageSize) === -1) {
  194. const firstLargerIndex = newPageSizeOpts.findIndex(el => el > pageSize);
  195. newPageSizeOpts.splice(firstLargerIndex, 0, pageSize);
  196. }
  197. return newPageSizeOpts;
  198. }
  199. handleQuickJumpNumberChange(targetPage: string | number) {
  200. this._adapter.updateQuickJumpPage(targetPage);
  201. }
  202. _handleQuickJump(quickJumpPage: string | number) {
  203. let page = Number(quickJumpPage);
  204. const { pageSize, total } = this.getStates();
  205. const totalPageNum = this._getTotalPageNumber(total, pageSize);
  206. if (Number.isNaN(page)) {
  207. return;
  208. }
  209. // If the user input is greater than totalPage
  210. if (page > totalPageNum) {
  211. page = totalPageNum;
  212. }
  213. if (page <= 0) {
  214. page = 1;
  215. }
  216. // clear inputnumber
  217. this._adapter.updateQuickJumpPage('');
  218. this.goPage(page);
  219. }
  220. handleQuickJumpBlur() {
  221. const { quickJumpPage } = this.getStates();
  222. // no need to operate when inputnumber blur & quickJumpPage is empty
  223. if ((typeof quickJumpPage === 'string' && quickJumpPage) || typeof quickJumpPage === 'number') {
  224. this._handleQuickJump(quickJumpPage);
  225. }
  226. }
  227. handleQuickJumpEnterPress(targetPage: any) {
  228. this._handleQuickJump(targetPage);
  229. }
  230. _getTotalPageNumber(total: number, pageSize: number) {
  231. const totalPageNum = Math.ceil(total / pageSize);
  232. return totalPageNum;
  233. }
  234. }
  235. export default PaginationFoundation;