Pagination.svelte 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. <script>
  2. import { createEventDispatcher, 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 from '../../lang/zh_CN';
  8. // 定义事件派发器
  9. // Define event dispatcher
  10. const dispatch = createEventDispatcher();
  11. // 当前语言
  12. // current language
  13. const currentLang = getContext('STDF_lang') || zh_CN;
  14. const commonLang = currentLang.common;
  15. const paginationLang = currentLang.pagination;
  16. /**
  17. * 总条数
  18. * total
  19. * @type {number}
  20. * @default 0
  21. */
  22. export let total = 0;
  23. /**
  24. * 每页条数
  25. * pageSize
  26. * @type {number}
  27. * @default 10
  28. */
  29. export let pageSize = 10;
  30. /**
  31. * 当前页
  32. * current
  33. * @type {number}
  34. * @default 1
  35. */
  36. export let current = 1;
  37. /**
  38. * 最大显示页码数
  39. * max show page
  40. * @type {5|7|9|11}
  41. * @default 7
  42. */
  43. export let maxShowPage = 7;
  44. /**
  45. * 圆角
  46. * radius
  47. * @type {'base'|'md'|'lg'|'xl'|'full'|'none'}
  48. * @default 'md'
  49. */
  50. export let radius = 'md';
  51. /**
  52. * 高亮页码类型
  53. * highlight page type
  54. * @type {'border'|'block'|'bold'}
  55. * @default 'bold'
  56. */
  57. export let type = 'bold';
  58. /**
  59. * 省略页码列数
  60. * second level page column
  61. * @type {number}
  62. * @default 3
  63. */
  64. export let pageCol = 3;
  65. /**
  66. * 是否显示后省略号的省略页码
  67. * show second level page in next ellipsis
  68. * @type {boolean}
  69. * @default false
  70. */
  71. export let showNextOmitPage = false;
  72. /**
  73. * 是否显示前省略号的省略页码
  74. * show second level page in pre ellipsis
  75. * @type {boolean}
  76. * @default false
  77. */
  78. export let showPreOmitPage = false;
  79. /**
  80. * 注入CSS
  81. * Inject CSS
  82. * @type {string}
  83. * @default ''
  84. */
  85. export let injClass = '';
  86. /**
  87. * 无数据文本
  88. * No data text
  89. * @type {string}
  90. * @default Current language common.noData
  91. */
  92. export let noDataText = commonLang.noData;
  93. /**
  94. * 仅一页文本
  95. * only one page text
  96. * @type {string}
  97. * @default Current language pagination.defaultOnlyOnePage
  98. */
  99. export let onePageText = paginationLang.defaultOnlyOnePage;
  100. /**
  101. * 连续模式
  102. * continuous mode
  103. * @type {boolean}
  104. * @default false
  105. */
  106. export let continuous = false;
  107. // 总页数
  108. // totalPage
  109. $: totalPage = Math.ceil(total / pageSize);
  110. // 当前页码大于等于 maxShowPage-3 时,不显示前面的省略号
  111. // current >= maxShowPage-3, not show pre ellipsis
  112. $: showPreEllipsis = current > maxShowPage - 2;
  113. // 当前页码小于等于 totalPage - (maxShowPage-3) 时,不显示后面的省略号
  114. // current <= totalPage - (maxShowPage-3), not show next ellipsis
  115. $: showNextEllipsis = current <= maxShowPage - 2 || current <= totalPage - (maxShowPage - 3);
  116. // 当显示前面的省略号时,中间显示的页码数 middleShowPage 个数为 maxShowPage - 4,内容为当前页码(maxShowPage为5)或者当前页码和前后一项(maxShowPage为7)或者当前页码和前后两项(maxShowPage为9)
  117. // 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)
  118. let middleShowPage = [];
  119. // 前省略号内页码数组
  120. // pre ellipsis page array
  121. let preEllipsisPages = [];
  122. // 后省略号内页码数组
  123. // next ellipsis page array
  124. let nextEllipsisPages = [];
  125. // 当页码变化时对一系列数据动态计算
  126. // Dynamic calculation of a series of data when the page number changes
  127. $: {
  128. if (showPreEllipsis && showNextEllipsis) {
  129. if (maxShowPage === 5) {
  130. middleShowPage = [current];
  131. } else if (maxShowPage === 7) {
  132. middleShowPage = [current - 1, current, current + 1];
  133. } else if (maxShowPage === 9) {
  134. middleShowPage = [current - 2, current - 1, current, current + 1, current + 2];
  135. } else if (maxShowPage === 11) {
  136. middleShowPage = [current - 3, current - 2, current - 1, current, current + 1, current + 2, current + 3];
  137. }
  138. } else {
  139. middleShowPage = [];
  140. }
  141. // 当仅显示后省略号时
  142. // when only show next ellipsis
  143. if (!showPreEllipsis && showNextEllipsis) {
  144. // 此时 nextEllipsisPages 为 maxShowPage - 2 到 总页码数 - 1
  145. // nextEllipsisPages is maxShowPage - 2 to total page - 1
  146. nextEllipsisPages = Array.from({ length: totalPage }, (v, k) => k + 1).slice(maxShowPage - 2, totalPage - 1);
  147. }
  148. // 当仅显示前省略号时
  149. // when only show pre ellipsis
  150. if (showPreEllipsis && !showNextEllipsis) {
  151. // 此时 preEllipsisPages 为 2 到 总页码数 - (maxShowPage-2)
  152. // preEllipsisPages is 2 to total page - (maxShowPage-2)
  153. preEllipsisPages = Array.from({ length: totalPage }, (v, k) => k + 1).slice(1, totalPage - (maxShowPage - 2));
  154. }
  155. // 当显示前后省略号时
  156. // when show pre and next ellipsis
  157. if (showPreEllipsis && showNextEllipsis) {
  158. // 此时 nextEllipsisPages 为 middleShowPage 最后一项 +1 ,到总页码数组的最后第二项
  159. // nextEllipsisPages is middleShowPage last item +1, to total page array last second item
  160. nextEllipsisPages = Array.from({ length: totalPage }, (v, k) => k + 1).slice(
  161. middleShowPage[middleShowPage.length - 1],
  162. totalPage - 1,
  163. );
  164. // 此时 preEllipsisPages 为 2 到 middleShowPage 第一项 -1
  165. // preEllipsisPages is 2 to middleShowPage first item -1
  166. preEllipsisPages = Array.from({ length: totalPage }, (v, k) => k + 1).slice(1, middleShowPage[0] - 1);
  167. }
  168. }
  169. // 特殊情况处理
  170. // Special case handling
  171. if (totalPage <= maxShowPage) {
  172. showNextOmitPage = false;
  173. }
  174. // 点击后省略号事件
  175. // click next ellipsis event
  176. const clickNextEllipsisFunc = () => {
  177. if (nextEllipsisPages.length > 0) {
  178. showNextOmitPage = !showNextOmitPage;
  179. }
  180. };
  181. // 点击前省略号事件
  182. // click pre ellipsis event
  183. const clickPreEllipsisFunc = () => {
  184. if (preEllipsisPages.length > 0) {
  185. showPreOmitPage = !showPreOmitPage;
  186. }
  187. };
  188. // 页码改变的回调,参数是改变后的页码及每页条数
  189. // onChange
  190. const onChange = () => {
  191. showNextOmitPage = false;
  192. showPreOmitPage = false;
  193. dispatch('change', current);
  194. };
  195. // 点击下一页
  196. // click next page
  197. const nextFunc = () => {
  198. if (current < totalPage) {
  199. current++;
  200. dispatch('next', current);
  201. onChange();
  202. }
  203. };
  204. // 点击上一页
  205. // click pre page
  206. const preFunc = () => {
  207. if (current > 1) {
  208. current--;
  209. dispatch('pre', current);
  210. onChange();
  211. }
  212. };
  213. // 点击页码
  214. // click page
  215. const clickItemFunc = index => {
  216. current = index;
  217. onChange();
  218. };
  219. // 点击省略页码事件
  220. // click second page item event
  221. const clickSecondPageItemFunc = e => {
  222. current = e.detail;
  223. showNextOmitPage = false;
  224. showPreOmitPage = false;
  225. onChange();
  226. };
  227. // 类型样式
  228. // type class
  229. const typeClass = {
  230. border: 'border-primary dark:border-dark text-primary dark:text-dark',
  231. block: 'bg-primary text-white dark:bg-dark dark:text-black',
  232. bold: 'font-bold text-primary dark:text-dark border-transparent',
  233. };
  234. // 圆角样式
  235. // radius class
  236. const radiusClass = { base: 'rounded', md: 'rounded-md', lg: 'rounded-lg', xl: 'rounded-xl', full: 'rounded-full', none: 'rounded-none' };
  237. </script>
  238. <div class="py-1 bg-white dark:bg-black flex justify-between text-center text-sm relative {injClass}">
  239. <!-- svelte-ignore a11y-click-events-have-key-events -->
  240. <!-- svelte-ignore a11y-no-static-element-interactions -->
  241. <div
  242. class="flex-1 py-2 border border-transparent transition-all {current > 1
  243. ? 'text-primary dark:text-dark'
  244. : 'text-primary/30 dark:text-dark/30'} {radiusClass[radius] || radiusClass.base} active:scale-75"
  245. on:click={preFunc}
  246. >
  247. <Icon name="ri-arrow-left-s-line" size={18} />
  248. </div>
  249. {#if totalPage === 0}
  250. <div class="flex-1 py-2 border border-transparent">{noDataText}</div>
  251. {:else if totalPage === 1}
  252. <div class="flex-1 py-2 border border-transparent">{onePageText}</div>
  253. {:else if totalPage > 1 && totalPage <= maxShowPage}
  254. <!-- eslint-disable-next-line no-unused-vars -->
  255. {#each new Array(totalPage) as item, index}
  256. <Page active={current === index + 1} {type} {radius} on:click={() => !continuous && clickItemFunc(index + 1)}>
  257. {index + 1}
  258. </Page>
  259. {/each}
  260. {:else}
  261. <Page active={current === 1} {type} {radius} on:click={() => !continuous && clickItemFunc(1)}>1</Page>
  262. {#if showPreEllipsis}
  263. <!-- svelte-ignore a11y-click-events-have-key-events -->
  264. <!-- svelte-ignore a11y-no-static-element-interactions -->
  265. <div
  266. class="flex-1 py-2 border {showPreOmitPage
  267. ? typeClass[type] || typeClass.border
  268. : 'border-transparent' + (type === 'bold' ? ' opacity-50' : '')} {radiusClass[radius] || radiusClass.base}"
  269. on:click={!continuous && clickPreEllipsisFunc}
  270. >
  271. {#if type === 'bold' && showPreOmitPage}
  272. <Icon name="ri-more-fill" size={18} />
  273. {:else}
  274. <Icon name="ri-more-line" size={18} />
  275. {/if}
  276. </div>
  277. {/if}
  278. {#if !showPreEllipsis && current <= maxShowPage - 1}
  279. <!-- eslint-disable-next-line no-unused-vars -->
  280. {#each new Array(maxShowPage - 3) as item, index}
  281. <Page active={current === index + 2} {type} {radius} on:click={() => !continuous && clickItemFunc(index + 2)}>
  282. {index + 2}
  283. </Page>
  284. {/each}
  285. {/if}
  286. {#if middleShowPage.length > 0}
  287. {#each middleShowPage as item, index}
  288. <Page active={index === (middleShowPage.length - 1) / 2} {type} {radius} on:click={() => !continuous && clickItemFunc(item)}>
  289. {item}
  290. </Page>
  291. {/each}
  292. {/if}
  293. {#if !showNextEllipsis && current > totalPage - (maxShowPage - 3)}
  294. <!-- eslint-disable-next-line no-unused-vars -->
  295. {#each new Array(maxShowPage - 3) as item, index}
  296. <Page
  297. active={current === totalPage + index + 3 - maxShowPage}
  298. {type}
  299. {radius}
  300. on:click={() => !continuous && clickItemFunc(totalPage + index + 3 - maxShowPage)}
  301. >
  302. {totalPage + index + 3 - maxShowPage}
  303. </Page>
  304. {/each}
  305. {/if}
  306. {#if showNextEllipsis}
  307. <!-- svelte-ignore a11y-click-events-have-key-events -->
  308. <!-- svelte-ignore a11y-no-static-element-interactions -->
  309. <div
  310. class="flex-1 py-2 border {showNextOmitPage
  311. ? typeClass[type] || typeClass.border
  312. : 'border-transparent' + (type === 'bold' ? ' opacity-50' : '')} {radiusClass[radius] || radiusClass.base}"
  313. on:click={!continuous && clickNextEllipsisFunc}
  314. >
  315. {#if type === 'bold' && showNextOmitPage}
  316. <Icon name="ri-more-fill" size={18} />
  317. {:else}
  318. <Icon name="ri-more-line" size={18} />
  319. {/if}
  320. </div>
  321. {/if}
  322. <Page active={current === totalPage} {type} {radius} on:click={() => !continuous && clickItemFunc(totalPage)}>{totalPage}</Page>
  323. {/if}
  324. <!-- svelte-ignore a11y-click-events-have-key-events -->
  325. <!-- svelte-ignore a11y-no-static-element-interactions -->
  326. <div
  327. class="flex-1 py-2 border border-transparent transition-all {current < totalPage
  328. ? 'text-primary dark:text-dark'
  329. : 'text-primary/30 dark:text-dark/30'} {radiusClass[radius] || radiusClass.base} active:scale-75"
  330. on:click={nextFunc}
  331. >
  332. <Icon name="ri-arrow-right-s-line" size={20} />
  333. </div>
  334. {#if showNextOmitPage}
  335. <SecondPageNext {pageCol} Pages={nextEllipsisPages} {type} {radius} on:clickItem={clickSecondPageItemFunc} {maxShowPage} />
  336. {/if}
  337. {#if showPreOmitPage}
  338. <SecondPagePre {pageCol} Pages={preEllipsisPages} {type} {radius} on:clickItem={clickSecondPageItemFunc} {maxShowPage} />
  339. {/if}
  340. </div>