u-pagination.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <template>
  2. <view
  3. class="u-pagination"
  4. :class="[disabled && 'u-pagination--disabled', simple && 'u-pagination--simple']"
  5. :style="[$u.addStyle(customStyle)]"
  6. >
  7. <!-- 上一页按钮 -->
  8. <view
  9. v-if="showPrevButton"
  10. class="u-pagination__prev"
  11. :class="[{ 'u-pagination__item--disabled': isFirstPage || disabled }]"
  12. :style="[itemStyle()]"
  13. @click="prevPage"
  14. >
  15. <slot name="prev" :disabled="isFirstPage || disabled">
  16. <text :style="[textStyle]">{{ prevText }}</text>
  17. </slot>
  18. </view>
  19. <!-- 简单模式 -->
  20. <text v-if="simple" class="u-pagination__simple" :style="[textStyle]">
  21. {{ innerValue }} / {{ totalPages }}
  22. </text>
  23. <!-- 完整模式页码列表 -->
  24. <template v-if="!simple">
  25. <view
  26. v-for="(page, index) in pageItems"
  27. :key="index"
  28. class="u-pagination__item"
  29. :class="[{
  30. 'u-pagination__item--active': page.isActive,
  31. 'u-pagination__item--disabled': disabled,
  32. 'u-pagination__item--ellipsis': page.type == 'ellipsis'
  33. }]"
  34. :style="[page.isActive ? activeItemStyle(true) : itemStyle(true)]"
  35. @click="handlePageClick(page)"
  36. >
  37. <slot name="page" :label="page.label" :active="page.isActive">
  38. <text :style="[page.isActive ? activeTextStyle : textStyle]">{{ page.label }}</text>
  39. </slot>
  40. </view>
  41. </template>
  42. <!-- 下一页按钮 -->
  43. <view
  44. v-if="showNextButton"
  45. class="u-pagination__next"
  46. :class="{ 'u-pagination__item--disabled': isLastPage || disabled }"
  47. :style="[itemStyle()]"
  48. @click="nextPage"
  49. >
  50. <slot name="next" :disabled="isLastPage || disabled">
  51. <text :style="textStyle">{{ nextText }}</text>
  52. </slot>
  53. </view>
  54. </view>
  55. </template>
  56. <script>
  57. import props from './props.js';
  58. import mixin from '../../libs/mixin/mixin';
  59. import mpMixin from '../../libs/mixin/mpMixin';
  60. /**
  61. * Pagination 分页
  62. * @description 分页器用于分隔长列表,每次只加载一个页面
  63. * @tutorial https://uview.d3u.cn/components/pagination.html
  64. * @property {Number} modelValue 当前页码(默认 1 )
  65. * @property {Number} total 总记录数(默认 0 )
  66. * @property {Number} pageSize 每页显示的记录数(默认 10 )
  67. * @property {Number} pagerCount 显示的页码按钮数量(默认 5 )
  68. * @property {Boolean} disabled 是否禁用分页(默认 false )
  69. * @property {Boolean} forceEllipses 是否显示省略号(默认 false )
  70. * @property {Boolean} simple 是否为简单分页(默认 false )
  71. * @property {Boolean} showPrevButton 是否展示上一页按钮(默认 true )
  72. * @property {Boolean} showNextButton 是否展示下一页按钮(默认 true )
  73. * @property {String} prevText 上一页按钮文字(默认 '上一页' )
  74. * @property {String} nextText 下一页按钮文字(默认 '下一页' )
  75. * @property {String} bgColor 背景色(默认 '#f7f7f7' )
  76. * @property {String} color 文本色(默认 '#606266' )
  77. * @property {String} activeBgColor 激活背景色(默认 '#2979ff' )
  78. * @property {String} activeColor 激活文本色(默认 '#ffffff' )
  79. * @property {String} fontSize 字体尺寸(默认 '14px' )
  80. * @property {String} round 圆角(默认 '4px' )
  81. * @property {String} borderColor 描边色(默认 '#e4e7ed' )
  82. * @property {String} itemWidth 每项宽度(默认 '40px' )
  83. * @property {String} itemHeight 每项高度(默认 '40px' )
  84. * @property {Object} customStyle 自定义样式
  85. * @event {Function} change 切换分页触发
  86. * @example <u-pagination v-model="currentPage" :total="100" :page-size="10"></u-pagination>
  87. */
  88. export default {
  89. name: "u-pagination",
  90. mixins: [mpMixin, mixin, props],
  91. data() {
  92. return {
  93. innerValue: 0
  94. }
  95. },
  96. computed: {
  97. // 总页数
  98. totalPages() {
  99. return Math.max(1, Math.ceil(this.total / this.pageSize));
  100. },
  101. // 是否为第一页
  102. isFirstPage() {
  103. return this.innerValue <= 1;
  104. },
  105. // 是否为最后一页
  106. isLastPage() {
  107. return this.innerValue >= this.totalPages;
  108. },
  109. // 页码项目列表
  110. pageItems() {
  111. const pageList = [];
  112. const totalPageCount = this.totalPages;
  113. const visiblePageCount = this.pagerCount;
  114. const currentPageNum = this.innerValue;
  115. let firstPage = 1;
  116. let lastPage = totalPageCount;
  117. const shouldLimitPages = visiblePageCount < totalPageCount;
  118. if (shouldLimitPages) {
  119. const halfVisible = Math.floor(visiblePageCount / 2);
  120. firstPage = Math.max(currentPageNum - halfVisible, 1);
  121. lastPage = firstPage + visiblePageCount - 1;
  122. if (lastPage > totalPageCount) {
  123. lastPage = totalPageCount;
  124. firstPage = lastPage - visiblePageCount + 1;
  125. }
  126. }
  127. // 生成基础页码列表
  128. for (let pageNum = firstPage; pageNum <= lastPage; pageNum++) {
  129. pageList.push({
  130. pageNumber: pageNum,
  131. label: `${pageNum}`,
  132. isActive: pageNum === currentPageNum,
  133. type: 'page'
  134. });
  135. }
  136. // 处理省略号显示逻辑
  137. if (shouldLimitPages && visiblePageCount > 0 && this.forceEllipses) {
  138. // 添加左侧省略号和首页
  139. if (firstPage > 1) {
  140. pageList.shift();
  141. // 添加左侧省略号
  142. pageList.unshift({
  143. pageNumber: firstPage - 1,
  144. label: '...',
  145. isActive: false,
  146. type: 'ellipsis'
  147. });
  148. // 添加首页
  149. pageList.unshift({
  150. pageNumber: 1,
  151. label: '1',
  152. isActive: currentPageNum === 1,
  153. type: 'page'
  154. });
  155. }
  156. // 添加右侧省略号和末页
  157. if (lastPage < totalPageCount) {
  158. pageList.pop();
  159. // 添加右侧省略号
  160. pageList.push({
  161. pageNumber: lastPage + 1,
  162. label: '...',
  163. isActive: false,
  164. type: 'ellipsis'
  165. });
  166. // 添加末页
  167. pageList.push({
  168. pageNumber: totalPageCount,
  169. label: `${totalPageCount}`,
  170. isActive: currentPageNum === totalPageCount,
  171. type: 'page'
  172. });
  173. }
  174. }
  175. return pageList;
  176. },
  177. // 项目样式
  178. itemStyle(isItem) {
  179. return (isItem) => {
  180. let style = {
  181. height: this.itemHeight,
  182. borderRadius: this.round
  183. }
  184. if(this.bgColor){
  185. style.backgroundColor = this.bgColor
  186. }
  187. if(this.borderColor){
  188. style.border = '1px solid ' + this.borderColor
  189. }
  190. if(isItem){
  191. style.width = this.itemWidth
  192. }
  193. return style;
  194. }
  195. },
  196. // 激活项目样式
  197. activeItemStyle(isItem) {
  198. return (isItem) => {
  199. let style = {
  200. height: this.itemHeight,
  201. borderRadius: this.round
  202. }
  203. if(this.activeBgColor){
  204. style.backgroundColor = this.activeBgColor
  205. }
  206. if(this.activeColor){
  207. style.color = this.activeColor
  208. }
  209. if(isItem){
  210. style.width = this.itemWidth
  211. }
  212. return style;
  213. }
  214. },
  215. // 文本样式
  216. textStyle() {
  217. return {
  218. color: this.color,
  219. fontSize: this.fontSize
  220. };
  221. },
  222. // 激活文本样式
  223. activeTextStyle() {
  224. return {
  225. color: this.activeColor,
  226. fontSize: this.fontSize
  227. };
  228. }
  229. },
  230. watch: {
  231. // #ifdef VUE3
  232. modelValue: {
  233. immediate: true,
  234. handler(val) {
  235. // 只在初始化或外部主动改变时更新内部状态
  236. if (val && val !== this.innerValue) {
  237. this.innerValue = val;
  238. }
  239. }
  240. },
  241. // #endif
  242. //#ifdef VUE2
  243. value: {
  244. immediate: true,
  245. handler(val) {
  246. // 只在没有modelValue且value有效时更新
  247. if (val && !this.innerValue) {
  248. this.innerValue = val;
  249. }
  250. }
  251. }
  252. // #endif
  253. },
  254. // #ifdef VUE3
  255. emits: ["update:modelValue", "change"],
  256. // #endif
  257. methods: {
  258. // 更新页码
  259. updatePage(page) {
  260. if (this.disabled) return;
  261. const newPage = Math.max(1, Math.min(page, this.totalPages));
  262. if (newPage !== this.innerValue) {
  263. this.innerValue = newPage;
  264. // #ifdef VUE2
  265. this.$emit('input', newPage);
  266. // #endif
  267. // #ifdef VUE3
  268. this.$emit('update:modelValue', newPage);
  269. // #endif
  270. this.$emit('change', newPage);
  271. }
  272. },
  273. // 上一页
  274. prevPage() {
  275. if (this.isFirstPage || this.disabled) return;
  276. this.updatePage(this.innerValue - 1);
  277. },
  278. // 下一页
  279. nextPage() {
  280. if (this.isLastPage || this.disabled) return;
  281. this.updatePage(this.innerValue + 1);
  282. },
  283. // 处理页码点击
  284. handlePageClick(page) {
  285. if (this.disabled || page.isActive) return;
  286. this.updatePage(page.pageNumber);
  287. }
  288. }
  289. }
  290. </script>
  291. <style lang="scss" scoped>
  292. @import "../../libs/css/components.scss";
  293. .u-pagination {
  294. @include flex;
  295. align-items: center;
  296. justify-content: center;
  297. &--simple {
  298. .u-pagination__simple {
  299. margin: 0 12px;
  300. }
  301. }
  302. &--disabled {
  303. opacity: 0.8;
  304. // #ifdef H5
  305. pointer-events: none;
  306. // #endif
  307. }
  308. &__item,
  309. &__prev,
  310. &__next {
  311. @include flex;
  312. align-items: center;
  313. justify-content: center;
  314. // #ifndef APP-NVUE
  315. user-select: none;
  316. // #endif
  317. // #ifdef H5
  318. cursor: pointer;
  319. // #endif
  320. &--active {
  321. border-color: transparent;
  322. }
  323. &--disabled {
  324. opacity: 0.5;
  325. // #ifdef H5
  326. cursor: not-allowed;
  327. // #endif
  328. }
  329. &--ellipsis {
  330. // #ifdef H5
  331. cursor: pointer;
  332. // #endif
  333. }
  334. }
  335. &__prev {
  336. margin-right: 3px;
  337. }
  338. &__next {
  339. margin-left: 3px;
  340. }
  341. &__prev,
  342. &__next {
  343. padding: 0 10px;
  344. &:active {
  345. opacity: 0.8;
  346. }
  347. }
  348. &__item {
  349. margin: 0 3px;
  350. }
  351. }
  352. </style>