Pagination.svelte 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. <script lang="ts">
  2. import { getContext } from 'svelte';
  3. import Icon from '../icon/Icon.svelte';
  4. import Page from './Page.svelte';
  5. import SecondPageNext from './SecondPageNext.svelte';
  6. import SecondPagePre from './SecondPagePre.svelte';
  7. import { zh_CN, type LangProps } from '../../lang';
  8. import type { PaginationProps } from '../../types';
  9. // 当前语言
  10. // current language
  11. const currentLang: LangProps = getContext('STDF_lang') || zh_CN;
  12. const commonLang: LangProps['common'] = currentLang.common;
  13. const paginationLang: LangProps['pagination'] = currentLang.pagination;
  14. let {
  15. total = 0,
  16. pageSize = 10,
  17. current = $bindable(1),
  18. maxShowPage = 7,
  19. radius = 'md',
  20. type = 'bold',
  21. pageCol = 3,
  22. showNextOmitPage = $bindable(false),
  23. showPreOmitPage = $bindable(false),
  24. injClass = '',
  25. noDataText = commonLang.noData,
  26. onePageText = paginationLang.defaultOnlyOnePage,
  27. continuous = false,
  28. onchange,
  29. onnext,
  30. onpre,
  31. }: PaginationProps = $props();
  32. // 总页数
  33. // totalPage
  34. let totalPage = $derived(Math.ceil(total / pageSize));
  35. // 当前页码大于等于 maxShowPage-3 时,不显示前面的省略号
  36. // current >= maxShowPage-3, not show pre ellipsis
  37. let showPreEllipsis = $derived(current > maxShowPage - 2);
  38. // 当前页码小于等于 totalPage - (maxShowPage-3) 时,不显示后面的省略号
  39. // current <= totalPage - (maxShowPage-3), not show next ellipsis
  40. let showNextEllipsis = $derived(current <= maxShowPage - 2 || current <= totalPage - (maxShowPage - 3));
  41. // 当显示前面的省略号时,中间显示的页码数 middleShowPage 个数为 maxShowPage - 4,内容为当前页码(maxShowPage为5)或者当前页码和前后一项(maxShowPage为7)或者当前页码和前后两项(maxShowPage为9)
  42. // when show pre ellipsis, middleShowPage length is maxShowPage - 4, content is current page (maxShowPage is 5) or current page and pre/next one (maxShowPage is 7) or current page and pre/next two (maxShowPage is 9)
  43. let middleShowPage = $state([]);
  44. // 前省略号内页码数组
  45. // pre ellipsis page array
  46. let preEllipsisPages = $state([]);
  47. // 后省略号内页码数组
  48. // next ellipsis page array
  49. let nextEllipsisPages = $state([]);
  50. $effect(() => {
  51. // 当页码变化时对一系列数据动态计算
  52. // Dynamic calculation of a series of data when the page number changes
  53. if (showPreEllipsis && showNextEllipsis) {
  54. if (maxShowPage === 5) {
  55. middleShowPage = [current];
  56. } else if (maxShowPage === 7) {
  57. middleShowPage = [current - 1, current, current + 1];
  58. } else if (maxShowPage === 9) {
  59. middleShowPage = [current - 2, current - 1, current, current + 1, current + 2];
  60. } else if (maxShowPage === 11) {
  61. middleShowPage = [current - 3, current - 2, current - 1, current, current + 1, current + 2, current + 3];
  62. }
  63. } else {
  64. middleShowPage = [];
  65. }
  66. });
  67. $effect(() => {
  68. // 当仅显示后省略号时
  69. // when only show next ellipsis
  70. if (!showPreEllipsis && showNextEllipsis) {
  71. // 此时 nextEllipsisPages 为 maxShowPage - 2 到 总页码数 - 1
  72. // nextEllipsisPages is maxShowPage - 2 to total page - 1
  73. nextEllipsisPages = Array.from({ length: totalPage }, (v, k) => k + 1).slice(maxShowPage - 2, totalPage - 1);
  74. }
  75. });
  76. $effect(() => {
  77. // 当仅显示前省略号时
  78. // when only show pre ellipsis
  79. if (showPreEllipsis && !showNextEllipsis) {
  80. // 此时 preEllipsisPages 为 2 到 总页码数 - (maxShowPage-2)
  81. // preEllipsisPages is 2 to total page - (maxShowPage-2)
  82. preEllipsisPages = Array.from({ length: totalPage }, (v, k) => k + 1).slice(1, totalPage - (maxShowPage - 2));
  83. }
  84. });
  85. $effect(() => {
  86. // 当显示前后省略号时
  87. // when show pre and next ellipsis
  88. if (showPreEllipsis && showNextEllipsis) {
  89. // 此时 nextEllipsisPages 为 middleShowPage 最后一项 +1 ,到总页码数组的最后第二项
  90. // nextEllipsisPages is middleShowPage last item +1, to total page array last second item
  91. nextEllipsisPages = Array.from({ length: totalPage }, (v, k) => k + 1).slice(
  92. middleShowPage[middleShowPage.length - 1],
  93. totalPage - 1,
  94. );
  95. // 此时 preEllipsisPages 为 2 到 middleShowPage 第一项 -1
  96. // preEllipsisPages is 2 to middleShowPage first item -1
  97. preEllipsisPages = Array.from({ length: totalPage }, (v, k) => k + 1).slice(1, middleShowPage[0] - 1);
  98. }
  99. });
  100. $effect(() => {
  101. // 特殊情况处理
  102. // Special case handling
  103. if (totalPage <= maxShowPage) {
  104. showNextOmitPage = false;
  105. }
  106. });
  107. // 点击后省略号事件
  108. // click next ellipsis event
  109. const clickNextEllipsisFunc = () => {
  110. if (nextEllipsisPages.length > 0) {
  111. showNextOmitPage = !showNextOmitPage;
  112. }
  113. };
  114. // 点击前省略号事件
  115. // click pre ellipsis event
  116. const clickPreEllipsisFunc = () => {
  117. if (preEllipsisPages.length > 0) {
  118. showPreOmitPage = !showPreOmitPage;
  119. }
  120. };
  121. // 页码改变的回调,参数是改变后的页码及每页条数
  122. // onChange
  123. const onChangeFn = () => {
  124. showNextOmitPage = false;
  125. showPreOmitPage = false;
  126. onchange && onchange(current);
  127. };
  128. // 点击下一页
  129. // click next page
  130. const nextFunc = () => {
  131. if (current < totalPage) {
  132. current++;
  133. onnext && onnext(current);
  134. onChangeFn();
  135. }
  136. };
  137. // 点击上一页
  138. // click pre page
  139. const preFunc = () => {
  140. if (current > 1) {
  141. current--;
  142. onpre && onpre(current);
  143. onChangeFn();
  144. }
  145. };
  146. // 点击页码
  147. // click page
  148. const clickItemFunc = index => {
  149. current = index;
  150. onChangeFn();
  151. };
  152. // 点击省略页码事件
  153. // click second page item event
  154. const clickSecondPageItemFunc = index => {
  155. current = index;
  156. showNextOmitPage = false;
  157. showPreOmitPage = false;
  158. onChangeFn();
  159. };
  160. // 类型样式
  161. // type class
  162. const typeClass = {
  163. border: 'border-primary dark:border-dark text-primary dark:text-dark',
  164. block: 'bg-primary text-white dark:bg-dark dark:text-black',
  165. bold: 'font-bold text-primary dark:text-dark border-transparent',
  166. };
  167. // 圆角样式
  168. // radius class
  169. const radiusClass = { base: 'rounded', md: 'rounded-md', lg: 'rounded-lg', xl: 'rounded-xl', full: 'rounded-full', none: 'rounded-none' };
  170. </script>
  171. <div class="py-1 bg-white dark:bg-black flex justify-between text-center text-sm relative {injClass}">
  172. <button
  173. class="flex-1 py-2 border border-transparent transition-all {current > 1
  174. ? 'text-primary dark:text-dark'
  175. : 'text-primary/30 dark:text-dark/30'} {radiusClass[radius] || radiusClass.base} active:scale-75"
  176. onclick={preFunc}
  177. >
  178. <Icon name="ri-arrow-left-s-line" size={18} />
  179. </button>
  180. {#if totalPage === 0}
  181. <div class="flex-1 py-2 border border-transparent">{noDataText}</div>
  182. {:else if totalPage === 1}
  183. <div class="flex-1 py-2 border border-transparent">{onePageText}</div>
  184. {:else if totalPage > 1 && totalPage <= maxShowPage}
  185. {#each new Array(totalPage) as item, index}
  186. <Page active={current === index + 1} {type} {radius} onclick={() => !continuous && clickItemFunc(index + 1)}>
  187. {index + 1}
  188. </Page>
  189. {/each}
  190. {:else}
  191. <Page active={current === 1} {type} {radius} onclick={() => !continuous && clickItemFunc(1)}>1</Page>
  192. {#if showPreEllipsis}
  193. <button
  194. class="flex-1 py-2 border {showPreOmitPage
  195. ? typeClass[type] || typeClass.border
  196. : 'border-transparent' + (type === 'bold' ? ' opacity-50' : '')} {radiusClass[radius] || radiusClass.base}"
  197. onclick={!continuous && clickPreEllipsisFunc}
  198. >
  199. {#if type === 'bold' && showPreOmitPage}
  200. <Icon name="ri-more-fill" size={18} />
  201. {:else}
  202. <Icon name="ri-more-line" size={18} />
  203. {/if}
  204. </button>
  205. {/if}
  206. {#if !showPreEllipsis && current <= maxShowPage - 1}
  207. {#each new Array(maxShowPage - 3) as item, index}
  208. <Page active={current === index + 2} {type} {radius} onclick={() => !continuous && clickItemFunc(index + 2)}>
  209. {index + 2}
  210. </Page>
  211. {/each}
  212. {/if}
  213. {#if middleShowPage.length > 0}
  214. {#each middleShowPage as item, index}
  215. <Page active={index === (middleShowPage.length - 1) / 2} {type} {radius} onclick={() => !continuous && clickItemFunc(item)}>
  216. {item}
  217. </Page>
  218. {/each}
  219. {/if}
  220. {#if !showNextEllipsis && current > totalPage - (maxShowPage - 3)}
  221. {#each new Array(maxShowPage - 3) as item, index}
  222. <Page
  223. active={current === totalPage + index + 3 - maxShowPage}
  224. {type}
  225. {radius}
  226. onclick={() => !continuous && clickItemFunc(totalPage + index + 3 - maxShowPage)}
  227. >
  228. {totalPage + index + 3 - maxShowPage}
  229. </Page>
  230. {/each}
  231. {/if}
  232. {#if showNextEllipsis}
  233. <button
  234. class="flex-1 py-2 border {showNextOmitPage
  235. ? typeClass[type] || typeClass.border
  236. : 'border-transparent' + (type === 'bold' ? ' opacity-50' : '')} {radiusClass[radius] || radiusClass.base}"
  237. onclick={!continuous && clickNextEllipsisFunc}
  238. >
  239. {#if type === 'bold' && showNextOmitPage}
  240. <Icon name="ri-more-fill" size={18} />
  241. {:else}
  242. <Icon name="ri-more-line" size={18} />
  243. {/if}
  244. </button>
  245. {/if}
  246. <Page active={current === totalPage} {type} {radius} onclick={() => !continuous && clickItemFunc(totalPage)}>{totalPage}</Page>
  247. {/if}
  248. <button
  249. class="flex-1 py-2 border border-transparent transition-all {current < totalPage
  250. ? 'text-primary dark:text-dark'
  251. : 'text-primary/30 dark:text-dark/30'} {radiusClass[radius] || radiusClass.base} active:scale-75"
  252. onclick={nextFunc}
  253. >
  254. <Icon name="ri-arrow-right-s-line" size={20} />
  255. </button>
  256. {#if showNextOmitPage}
  257. <SecondPageNext {pageCol} Pages={nextEllipsisPages} {type} {radius} onclickItem={clickSecondPageItemFunc} {maxShowPage} />
  258. {/if}
  259. {#if showPreOmitPage}
  260. <SecondPagePre {pageCol} Pages={preEllipsisPages} {type} {radius} onclickItem={clickSecondPageItemFunc} {maxShowPage} />
  261. {/if}
  262. </div>