- 基于 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 -->
<picker
v-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-icons
class="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 -->
<view
class="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">
<text
class="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 -->
<view
v-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>
<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>
<!-- <button
class="uni-pagination__jump-btn"
size="mini"
type="primary"
@click="jumpToPage"
>
确定
</button> -->
</view>
<view
class="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>