u-pagination.vue 6.6 KB

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