Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 

309 righe
7.1 KiB

  1. <template>
  2. <view class="u-pagination">
  3. <!-- 上一页按钮 -->
  4. <view
  5. :class="[
  6. 'u-pagination-btn',
  7. { disabled: currentPage === 1 }
  8. ]"
  9. :style="{ backgroundColor: buttonBgColor, borderColor: buttonBorderColor }"
  10. @click="prev"
  11. >
  12. <template v-if="prevText">
  13. {{ prevText }}
  14. </template>
  15. <up-icon v-else name="arrow-left"></up-icon>
  16. </view>
  17. <!-- 页码列表 -->
  18. <template v-for="(page,index) in displayedPages" :key="index" v-if="layout.includes('pager')">
  19. <view
  20. class="u-pagination-item"
  21. :class="[
  22. 'u-pagination-item',
  23. { active: page === currentPage }
  24. ]"
  25. @click="goTo(page)"
  26. >
  27. {{ page }}
  28. </view>
  29. </template>
  30. <!-- 总数显示 -->
  31. <view v-if="total > 0 && layout.includes('total')" class="u-pagination-total">
  32. 共 {{ total }} 条
  33. </view>
  34. <!-- 每页数量选择器 -->
  35. <picker
  36. v-if="layout.includes('sizes') && normalizedPageSizes.length"
  37. mode="selector"
  38. :range="normalizedPageSizes"
  39. range-key="label"
  40. :value="pageSizeIndex"
  41. @change="handleSizeChange"
  42. class="u-pagination-sizes"
  43. >
  44. <view>{{ pageSizeLabel }}</view>
  45. </picker>
  46. <!-- 下一页按钮 -->
  47. <view
  48. :class="[
  49. 'u-pagination-btn',
  50. { disabled: currentPage === totalPages }
  51. ]"
  52. :style="{ backgroundColor: buttonBgColor, borderColor: buttonBorderColor }"
  53. @click="next"
  54. >
  55. <template v-if="nextText">
  56. {{ nextText }}
  57. </template>
  58. <up-icon v-else name="arrow-right"></up-icon>
  59. </view>
  60. <!-- 跳转输入框 -->
  61. <!-- <view v-if="layout.includes('jumper')">
  62. <text>前往</text>
  63. <input
  64. type="number"
  65. class="u-pagination-jumper"
  66. :value="currentPageInput"
  67. @input="onInputPage"
  68. @confirm="onConfirmPage"
  69. />
  70. <text>页</text>
  71. </view> -->
  72. </view>
  73. </template>
  74. <script>
  75. import { t } from '../../libs/i18n'
  76. export default {
  77. name: 'u-pagination',
  78. props: {
  79. // 当前页码
  80. currentPage: {
  81. type: Number,
  82. default: 1
  83. },
  84. // 每页条目数
  85. pageSize: {
  86. type: Number,
  87. default: 10
  88. },
  89. // 总数据条目数
  90. total: {
  91. type: Number,
  92. default: 0
  93. },
  94. // 上一页按钮文案
  95. prevText: {
  96. type: String,
  97. default: ''
  98. },
  99. // 下一页按钮文案
  100. nextText: {
  101. type: String,
  102. default: ''
  103. },
  104. buttonBgColor: {
  105. type: String,
  106. default: '#f5f7fa'
  107. },
  108. buttonBorderColor: {
  109. type: String,
  110. default: '#dcdfe6'
  111. },
  112. // 可选的每页条目数
  113. pageSizes: {
  114. type: Array,
  115. default: () => [10, 20, 30, 40, 50]
  116. },
  117. // 布局方式(类似 el-pagination)
  118. layout: {
  119. type: String,
  120. default: 'prev, pager, next'
  121. },
  122. // 是否隐藏只有一个页面时的分页控件
  123. hideOnSinglePage: {
  124. type: Boolean,
  125. default: false
  126. }
  127. },
  128. emits: ['update:currentPage', 'update:pageSize', 'current-change', 'size-change'],
  129. data() {
  130. return {
  131. currentPageInput: this.currentPage + ''
  132. };
  133. },
  134. computed: {
  135. totalPages() {
  136. return Math.max(1, Math.ceil(this.total / this.pageSize));
  137. },
  138. normalizedPageSizes() {
  139. if (!Array.isArray(this.pageSizes)) return [];
  140. return this.pageSizes
  141. .map(size => {
  142. if (typeof size === 'number' || typeof size === 'string') {
  143. return {
  144. label: `${size}条/页`,
  145. value: Number(size)
  146. };
  147. }
  148. if (size && typeof size === 'object') {
  149. const value = Number(size.value);
  150. return {
  151. label: size.label || `${value}条/页`,
  152. value
  153. };
  154. }
  155. return null;
  156. })
  157. .filter(size => size && !isNaN(size.value));
  158. },
  159. pageSizeIndex() {
  160. const index = this.normalizedPageSizes.findIndex(size => size.value === this.pageSize);
  161. return index >= 0 ? index : 0;
  162. },
  163. pageSizeLabel() {
  164. const found = this.normalizedPageSizes.find(size => size.value === this.pageSize);
  165. return found?.label || this.pageSize;
  166. },
  167. displayedPages() {
  168. const total = this.totalPages;
  169. const current = this.currentPage;
  170. if (total <= 4) {
  171. return Array.from({ length: total }, (_, i) => i + 1);
  172. }
  173. const pages = [];
  174. // 当前页靠近头部
  175. if (current <= 2) {
  176. for (let i = 1; i <= 4; i++) {
  177. pages.push(i);
  178. }
  179. pages.push('...');
  180. pages.push(total);
  181. }
  182. // 当前页在尾部附近
  183. else if (current >= total - 1) {
  184. pages.push(1);
  185. pages.push('...');
  186. for (let i = total - 3; i <= total; i++) {
  187. pages.push(i);
  188. }
  189. }
  190. // 中间情况
  191. else {
  192. pages.push(1);
  193. pages.push('...');
  194. pages.push(current - 1);
  195. pages.push(current);
  196. pages.push(current + 1);
  197. pages.push('...');
  198. pages.push(total);
  199. }
  200. return pages;
  201. }
  202. // 控制是否隐藏
  203. },
  204. watch: {
  205. currentPage(val) {
  206. this.currentPageInput = val + '';
  207. }
  208. },
  209. methods: {
  210. t,
  211. handleSizeChange(e) {
  212. const selected = Number(e.detail.value);
  213. const size = this.normalizedPageSizes[selected]?.value || this.normalizedPageSizes[0]?.value;
  214. if (!size) return;
  215. this.$emit('update:pageSize', size);
  216. this.$emit('size-change', size);
  217. },
  218. prev() {
  219. if (this.currentPage > 1) {
  220. this.goTo(this.currentPage - 1);
  221. }
  222. },
  223. next() {
  224. if (this.currentPage < this.totalPages) {
  225. this.goTo(this.currentPage + 1);
  226. }
  227. },
  228. goTo(page) {
  229. if (page === '...' || page === this.currentPage) return;
  230. this.$emit('update:currentPage', page);
  231. this.$emit('current-change', page);
  232. },
  233. onInputPage(e) {
  234. this.currentPageInput = e.detail.value;
  235. },
  236. onConfirmPage(e) {
  237. const num = parseInt(e.detail.value);
  238. if (!isNaN(num) && num >= 1 && num <= this.totalPages) {
  239. this.goTo(num);
  240. }
  241. }
  242. }
  243. };
  244. </script>
  245. <style lang="scss" scoped>
  246. .u-pagination {
  247. display: flex;
  248. flex-direction: row;
  249. align-items: center;
  250. justify-content: space-between;
  251. flex-wrap: wrap;
  252. font-size: 14px;
  253. color: #606266;
  254. .u-pagination-total {
  255. margin-right: 10px;
  256. }
  257. .u-pagination-sizes {
  258. margin-right: 10px;
  259. padding: 4px 4px;
  260. border: 1rpx solid #dcdfe6;
  261. border-radius: 4px;
  262. }
  263. .u-pagination-btn {
  264. margin: 0 3px;
  265. padding: 4px 4px;
  266. border: 1rpx solid #dcdfe6;
  267. border-radius: 4px;
  268. background-color: #f5f7fa;
  269. &.disabled {
  270. opacity: 0.5;
  271. }
  272. }
  273. .u-pagination-item {
  274. margin: 0 2px;
  275. padding: 4px 8px;
  276. border-radius: 4px;
  277. &.active {
  278. background-color: #409eff;
  279. color: white;
  280. }
  281. }
  282. .u-pagination-jumper {
  283. width: 40px;
  284. height: 28px;
  285. margin: 0 5px;
  286. padding: 0 5px;
  287. border: 1rpx solid #dcdfe6;
  288. border-radius: 4px;
  289. font-size: 14px;
  290. }
  291. }
  292. </style>