u-pagination.vue 6.4 KB

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