- 基于 UniApp 官方扩展组件库 uni-ui 中的 uni-pagination 分页器组件,针对大数据量场景进行优化

- 新增输入框跳转功能:在原有分页器基础上,新增了一个输入框区域,允许用户直接输入目标页码进行跳转
- 双向页码绑定优化:实现了输入框与当前页码的双向绑定机制。当用户通过其他方式(如点击上一页、下一页、页码按钮)切换页面时,输入框会自动更新显示当前页码。同时,当用户在输入框中输入页码并确认跳转时,页面也会相应更新
- 类型安全和用户体验优化:对输入处理进行了严格的类型安全检查,且支持空值输入

输入框跳转区域
- 允许用户直接输入页码进行跳转
- 输入限制: 只能输入数字,最大长度根据总页数确定
<view class="uni-pagination__jump"><text class="uni-pagination__jump-text">前往</text><input class="uni-pagination__jump-input" type="number" :value="jumpPage" @input="onJumpInput"@blur="onJumpBlur"@confirm="onJumpConfirm":placeholder="'1-' + maxPage":maxlength="String(maxPage).length" /><text class="uni-pagination__jump-text">页</text>
</view>
输入框跳转方法
onJumpInput(e) {const value = e.detail.value;this.jumpPage = value === '' ? '' : Number(value) || 1;
},onJumpBlur() {if (this.jumpPage === '') {return;}const targetPage = Number(this.jumpPage);if (isNaN(targetPage) || targetPage < 1 || targetPage > this.maxPage) {this.jumpPage = 1;} else {this.jumpPage = targetPage;}
},onJumpConfirm() {this.jumpToPage();
},jumpToPage() {if (this.jumpPage === '') {return;}const targetPage = Number(this.jumpPage);if (isNaN(targetPage) || targetPage < 1 || targetPage > this.maxPage) {return;}this.currentIndex = targetPage;this.change("current");
}
事件触发方法
change(e) {this.$emit("input", this.currentIndex);this.$emit("update:modelValue", this.currentIndex);this.$emit("change", {type: e,current: this.currentIndex});
}
完整代码
<template><view class="uni-pagination"><!-- #ifndef MP --><pickerv-if="showPageSize === true || showPageSize === 'true'"class="select-picker"mode="selector":value="pageSizeIndex":range="pageSizeRange"@change="pickerChange"@cancel="pickerClick"@click.native="pickerClick"><button type="default" size="mini" :plain="true"><text>{{ pageSizeRange[pageSizeIndex] }} {{ piecePerPage }}</text><uni-iconsclass="select-picker-icon"type="arrowdown"size="12"color="#999"></uni-icons></button></picker><!-- #endif --><!-- #ifndef APP-NVUE --><view class="uni-pagination__total is-phone-hide">共 {{ total }} 条</view><!-- #endif --><viewclass="uni-pagination__btn":class="currentIndex === 1? 'uni-pagination--disabled': 'uni-pagination--enabled'":hover-class="currentIndex === 1 ? '' : 'uni-pagination--hover'":hover-start-time="20":hover-stay-time="70"@click="clickLeft"><template v-if="showIcon === true || showIcon === 'true'"><uni-icons color="#666" size="16" type="left" /></template><template v-else><text class="uni-pagination__child-btn">{{ prevPageText }}</text></template></view><view class="uni-pagination__num uni-pagination__num-flex-none"><view class="uni-pagination__num-current"><textclass="uni-pagination__num-current-text is-pc-hide current-index-text">{{ currentIndex }}</text><text class="uni-pagination__num-current-text is-pc-hide">/{{ maxPage || 0 }}</text><!-- #ifndef APP-NVUE --><viewv-for="(item, index) in paper":key="index":class="{ 'page--active': item === currentIndex }"class="uni-pagination__num-tag tag--active is-phone-hide"@click.top="selectPage(item, index)"><text>{{ item }}</text></view><!-- #endif --></view></view><view class="uni-pagination__jump"><text class="uni-pagination__jump-text">前往</text><inputclass="uni-pagination__jump-input"type="number":value="jumpPage"@input="onJumpInput"@blur="onJumpBlur"@confirm="onJumpConfirm":placeholder="'1-' + maxPage":maxlength="String(maxPage).length"/><text class="uni-pagination__jump-text">页</text><!-- <button class="uni-pagination__jump-btn" size="mini" type="primary" @click="jumpToPage">确定</button> --></view><viewclass="uni-pagination__btn":class="currentIndex >= maxPage? 'uni-pagination--disabled': 'uni-pagination--enabled'":hover-class="currentIndex === maxPage ? '' : 'uni-pagination--hover'":hover-start-time="20":hover-stay-time="70"@click="clickRight"><template v-if="showIcon === true || showIcon === 'true'"><uni-icons color="#666" size="16" type="right" /></template><template v-else><text class="uni-pagination__child-btn">{{ nextPageText }}</text></template></view></view>
</template><script>
import { initVueI18n } from "@dcloudio/uni-i18n";
import messages from "./i18n/index.js";
const { t } = initVueI18n(messages);
export default {name: "UniPagination",emits: ["update:modelValue", "input", "change", "pageSizeChange"],props: {value: {type: [Number, String],default: 1,},modelValue: {type: [Number, String],default: 1,},prevText: {type: String,},nextText: {type: String,},piecePerPageText: {type: String,},current: {type: [Number, String],default: 1,},total: {type: [Number, String],default: 0,},pageSize: {type: [Number, String],default: 10,},showIcon: {type: [Boolean, String],default: false,},showPageSize: {type: [Boolean, String],default: false,},pagerCount: {type: Number,default: 7,},pageSizeRange: {type: Array,default: () => [20, 50, 100, 500],},},data() {return {pageSizeIndex: 0,currentIndex: 1,paperData: [],pickerShow: false,jumpPage: 1,};},computed: {piecePerPage() {return this.piecePerPageText || t("uni-pagination.piecePerPage");},prevPageText() {return this.prevText || t("uni-pagination.prevText");},nextPageText() {return this.nextText || t("uni-pagination.nextText");},maxPage() {let maxPage = 1;let total = Number(this.total);let pageSize = Number(this.pageSize);if (total && pageSize) {maxPage = Math.ceil(total / pageSize);}return maxPage;},paper() {const num = this.currentIndex;const pagerCount = this.pagerCount;const total = this.total;const pageSize = this.pageSize;let totalArr = [];let showPagerArr = [];let pagerNum = Math.ceil(total / pageSize);for (let i = 0; i < pagerNum; i++) {totalArr.push(i + 1);}showPagerArr.push(1);const totalNum = totalArr[totalArr.length - (pagerCount + 1) / 2];totalArr.forEach((item, index) => {if ((pagerCount + 1) / 2 >= num) {if (item < pagerCount + 1 && item > 1) {showPagerArr.push(item);}} else if (num + 2 <= totalNum) {if (item > num - (pagerCount + 1) / 2 &&item < num + (pagerCount + 1) / 2) {showPagerArr.push(item);}} else {if ((item > num - (pagerCount + 1) / 2 ||pagerNum - pagerCount < item) &&item < totalArr[totalArr.length - 1]) {showPagerArr.push(item);}}});if (pagerNum > pagerCount) {if ((pagerCount + 1) / 2 >= num) {showPagerArr[showPagerArr.length - 1] = "...";} else if (num + 2 <= totalNum) {showPagerArr[1] = "...";showPagerArr[showPagerArr.length - 1] = "...";} else {showPagerArr[1] = "...";}showPagerArr.push(totalArr[totalArr.length - 1]);} else {if ((pagerCount + 1) / 2 >= num) {} else if (num + 2 <= totalNum) {} else {showPagerArr.shift();showPagerArr.push(totalArr[totalArr.length - 1]);}}return showPagerArr;},},watch: {current: {immediate: true,handler(val, old) {if (val < 1) {this.currentIndex = 1;} else {this.currentIndex = val;}},},value: {immediate: true,handler(val) {if (Number(this.current) !== 1) return;if (val < 1) {this.currentIndex = 1;} else {this.currentIndex = val;}},},pageSizeIndex(val) {this.$emit("pageSizeChange", this.pageSizeRange[val]);},currentIndex: {handler(val) {if (this.jumpPage !== val && this.jumpPage !== '') {this.jumpPage = val;}},immediate: true,},},methods: {pickerChange(e) {this.pageSizeIndex = e.detail.value;this.pickerClick();},pickerClick() {const body = document.querySelector("body");if (!body) return;const className = "uni-pagination-picker-show";this.pickerShow = !this.pickerShow;if (this.pickerShow) {body.classList.add(className);} else {setTimeout(() => body.classList.remove(className), 300);}},selectPage(e, index) {if (parseInt(e)) {this.currentIndex = e;this.change("current");} else {let pagerNum = Math.ceil(this.total / this.pageSize);if (index <= 1) {if (this.currentIndex - 5 > 1) {this.currentIndex -= 5;} else {this.currentIndex = 1;}return;}if (index >= 6) {if (this.currentIndex + 5 > pagerNum) {this.currentIndex = pagerNum;} else {this.currentIndex += 5;}return;}}},clickLeft() {if (Number(this.currentIndex) === 1) {return;}this.currentIndex -= 1;this.change("prev");},clickRight() {if (Number(this.currentIndex) >= this.maxPage) {return;}this.currentIndex += 1;this.change("next");},change(e) {this.$emit("input", this.currentIndex);this.$emit("update:modelValue", this.currentIndex);this.$emit("change", {type: e,current: this.currentIndex,});},onJumpInput(e) {const value = e.detail.value;this.jumpPage = value === '' ? '' : Number(value) || 1;},onJumpBlur() {if (this.jumpPage === '') {return;}const targetPage = Number(this.jumpPage);if (isNaN(targetPage) || targetPage < 1 || targetPage > this.maxPage) {this.jumpPage = 1;} else {this.jumpPage = targetPage;}},onJumpConfirm() {this.jumpToPage();},jumpToPage() {if (this.jumpPage === '') {return;}const targetPage = Number(this.jumpPage);if (isNaN(targetPage) || targetPage < 1 || targetPage > this.maxPage) {return;}this.currentIndex = targetPage;this.change("current");},},
};
</script><style lang="scss" scoped>
$uni-primary: #2979ff !default;
.uni-pagination {margin: 40rpx 0;display: flex;position: relative;overflow: hidden;flex-direction: row;justify-content: center;align-items: center;
}.uni-pagination__total {font-size: 14px;color: #999;margin-right: 15px;
}.uni-pagination__btn {display: flex;cursor: pointer;padding: 0 8px;line-height: 30px;font-size: 12px;position: relative;background-color: #f0f0f0;flex-direction: row;justify-content: center;align-items: center;text-align: center;border-radius: 5px;
}.uni-pagination__child-btn {display: flex;font-size: 12px;position: relative;flex-direction: row;justify-content: center;align-items: center;text-align: center;color: #666;font-size: 12px;
}.uni-pagination__num {display: flex;flex: 1;flex-direction: row;justify-content: center;align-items: center;height: 30px;line-height: 30px;font-size: 12px;color: #666;margin: 0 5px;
}.uni-pagination__num-tag {cursor: pointer;min-width: 30px;margin: 0 5px;height: 30px;text-align: center;line-height: 30px;color: #999;border-radius: 4px;
}.uni-pagination__num-current {display: flex;flex-direction: row;
}.uni-pagination__num-current-text {font-size: 15px;
}.current-index-text {color: $uni-primary;
}.uni-pagination--enabled {color: #333333;opacity: 1;
}.uni-pagination--disabled {opacity: 0.5;cursor: default;
}.uni-pagination--hover {color: rgba(0, 0, 0, 0.6);background-color: #eee;
}.tag--active:hover {color: $uni-primary;
}.page--active {color: #fff;background-color: $uni-primary;
}.page--active:hover {color: #fff;
}
.uni-pagination__jump {display: flex;flex-direction: row;justify-content: center;align-items: center;margin: 0 10px;
}.uni-pagination__jump-text {font-size: 12px;color: #666;margin: 0 5px;
}.uni-pagination__jump-input {width: 50px;height: 30px;min-height: fit-content;text-align: center;border: 1px solid #ddd;border-radius: 4px;font-size: 12px;color: #333;background-color: #fff;margin: 0 5px;
}.uni-pagination__jump-btn {margin-left: 5px;height: 30px;line-height: 30px;font-size: 12px;padding: 0 8px;
}
.is-pc-hide {display: block;
}.is-phone-hide {display: none;
}@media screen and (min-width: 450px) {.is-pc-hide {display: none;}.is-phone-hide {display: block;}.uni-pagination__num-flex-none {flex: none;}
}
</style>