2025-08-25 18:06:33 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="pagination-container">
|
|
|
|
|
|
<!-- 上拉加载更多模式 -->
|
|
|
|
|
|
<view v-if="mode === 'loadMore'" class="load-more-container">
|
|
|
|
|
|
<view v-if="loading" class="loading-state">
|
|
|
|
|
|
<view class="loading-spinner"></view>
|
|
|
|
|
|
<text class="loading-text">{{ loadingText }}</text>
|
|
|
|
|
|
</view>
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
<view v-else-if="noMore && list.length > 0" class="no-more-state">
|
|
|
|
|
|
<text class="no-more-text">{{ noMoreText }}</text>
|
|
|
|
|
|
</view>
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
<view v-else-if="list.length === 0 && !loading" class="empty-state">
|
|
|
|
|
|
<view class="empty-icon">{{ emptyIcon }}</view>
|
|
|
|
|
|
<text class="empty-text">{{ emptyText }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 分页器模式 -->
|
|
|
|
|
|
<view v-else-if="mode === 'pager'" class="pager-container">
|
|
|
|
|
|
<view v-if="total > 0" class="pager-info">
|
|
|
|
|
|
<text class="pager-text">
|
|
|
|
|
|
共 {{ total }} 条,第 {{ currentPage }} / {{ totalPages }} 页
|
|
|
|
|
|
</text>
|
|
|
|
|
|
</view>
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
<view class="pager-controls">
|
2025-08-26 09:25:40 +08:00
|
|
|
|
<button
|
|
|
|
|
|
class="pager-btn prev-btn"
|
2025-08-25 18:06:33 +08:00
|
|
|
|
:disabled="currentPage <= 1"
|
|
|
|
|
|
@click="handlePageChange(currentPage - 1)"
|
|
|
|
|
|
>
|
|
|
|
|
|
上一页
|
|
|
|
|
|
</button>
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
<view class="page-numbers">
|
2025-08-26 09:25:40 +08:00
|
|
|
|
<button
|
|
|
|
|
|
v-for="page in visiblePages"
|
2025-08-25 18:06:33 +08:00
|
|
|
|
:key="page"
|
|
|
|
|
|
:class="['page-btn', { active: page === currentPage }]"
|
|
|
|
|
|
@click="handlePageChange(page)"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ page }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</view>
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
|
class="pager-btn next-btn"
|
2025-08-25 18:06:33 +08:00
|
|
|
|
:disabled="currentPage >= totalPages"
|
|
|
|
|
|
@click="handlePageChange(currentPage + 1)"
|
|
|
|
|
|
>
|
|
|
|
|
|
下一页
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
|
export default {
|
|
|
|
|
|
name: 'Pagination',
|
|
|
|
|
|
props: {
|
|
|
|
|
|
// 分页模式:loadMore(上拉加载) 或 pager(分页器)
|
|
|
|
|
|
mode: {
|
|
|
|
|
|
type: String,
|
|
|
|
|
|
default: 'loadMore',
|
2025-08-26 09:25:40 +08:00
|
|
|
|
validator: value => ['loadMore', 'pager'].includes(value),
|
2025-08-25 18:06:33 +08:00
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
// 数据列表
|
|
|
|
|
|
list: {
|
|
|
|
|
|
type: Array,
|
2025-08-26 09:25:40 +08:00
|
|
|
|
default: () => [],
|
2025-08-25 18:06:33 +08:00
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
// 总数据量
|
|
|
|
|
|
total: {
|
|
|
|
|
|
type: Number,
|
2025-08-26 09:25:40 +08:00
|
|
|
|
default: 0,
|
2025-08-25 18:06:33 +08:00
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
// 当前页码
|
|
|
|
|
|
currentPage: {
|
|
|
|
|
|
type: Number,
|
2025-08-26 09:25:40 +08:00
|
|
|
|
default: 1,
|
2025-08-25 18:06:33 +08:00
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
// 每页数量
|
|
|
|
|
|
pageSize: {
|
|
|
|
|
|
type: Number,
|
2025-08-26 09:25:40 +08:00
|
|
|
|
default: 10,
|
2025-08-25 18:06:33 +08:00
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
// 是否正在加载
|
|
|
|
|
|
loading: {
|
|
|
|
|
|
type: Boolean,
|
2025-08-26 09:25:40 +08:00
|
|
|
|
default: false,
|
2025-08-25 18:06:33 +08:00
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
// 是否没有更多数据
|
|
|
|
|
|
noMore: {
|
|
|
|
|
|
type: Boolean,
|
2025-08-26 09:25:40 +08:00
|
|
|
|
default: false,
|
2025-08-25 18:06:33 +08:00
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
// 自定义文本
|
|
|
|
|
|
loadingText: {
|
|
|
|
|
|
type: String,
|
2025-08-26 09:25:40 +08:00
|
|
|
|
default: '正在加载...',
|
2025-08-25 18:06:33 +08:00
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
noMoreText: {
|
|
|
|
|
|
type: String,
|
2025-08-26 09:25:40 +08:00
|
|
|
|
default: '没有更多数据了',
|
2025-08-25 18:06:33 +08:00
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
emptyText: {
|
|
|
|
|
|
type: String,
|
2025-08-26 09:25:40 +08:00
|
|
|
|
default: '暂无数据',
|
2025-08-25 18:06:33 +08:00
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
emptyIcon: {
|
|
|
|
|
|
type: String,
|
2025-08-26 09:25:40 +08:00
|
|
|
|
default: '📋',
|
2025-08-25 18:06:33 +08:00
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
// 分页器显示的页码数量
|
|
|
|
|
|
visiblePageCount: {
|
|
|
|
|
|
type: Number,
|
2025-08-26 09:25:40 +08:00
|
|
|
|
default: 5,
|
|
|
|
|
|
},
|
2025-08-25 18:06:33 +08:00
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
computed: {
|
|
|
|
|
|
// 总页数
|
|
|
|
|
|
totalPages() {
|
|
|
|
|
|
return Math.ceil(this.total / this.pageSize)
|
|
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
// 可见的页码
|
|
|
|
|
|
visiblePages() {
|
|
|
|
|
|
const pages = []
|
|
|
|
|
|
const half = Math.floor(this.visiblePageCount / 2)
|
|
|
|
|
|
let start = Math.max(1, this.currentPage - half)
|
|
|
|
|
|
let end = Math.min(this.totalPages, start + this.visiblePageCount - 1)
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
// 调整起始位置
|
|
|
|
|
|
if (end - start + 1 < this.visiblePageCount) {
|
|
|
|
|
|
start = Math.max(1, end - this.visiblePageCount + 1)
|
|
|
|
|
|
}
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
for (let i = start; i <= end; i++) {
|
|
|
|
|
|
pages.push(i)
|
|
|
|
|
|
}
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
return pages
|
2025-08-26 09:25:40 +08:00
|
|
|
|
},
|
2025-08-25 18:06:33 +08:00
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
methods: {
|
|
|
|
|
|
// 处理页码变化
|
|
|
|
|
|
handlePageChange(page) {
|
|
|
|
|
|
if (page < 1 || page > this.totalPages || page === this.currentPage) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
this.$emit('page-change', page)
|
|
|
|
|
|
},
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
// 重置分页状态
|
|
|
|
|
|
reset() {
|
|
|
|
|
|
this.$emit('reset')
|
2025-08-26 09:25:40 +08:00
|
|
|
|
},
|
|
|
|
|
|
},
|
2025-08-25 18:06:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.pagination-container {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 上拉加载更多样式
|
|
|
|
|
|
.load-more-container {
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.loading-state {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
padding: 20rpx 0;
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
.loading-spinner {
|
|
|
|
|
|
width: 40rpx;
|
|
|
|
|
|
height: 40rpx;
|
|
|
|
|
|
border: 4rpx solid #f3f3f3;
|
|
|
|
|
|
border-top: 4rpx solid #1890ff;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
animation: spin 1s linear infinite;
|
|
|
|
|
|
margin-right: 20rpx;
|
|
|
|
|
|
}
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
.loading-text {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.no-more-state {
|
|
|
|
|
|
padding: 20rpx 0;
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
.no-more-text {
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-state {
|
|
|
|
|
|
padding: 40rpx 0;
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
.empty-icon {
|
|
|
|
|
|
font-size: 80rpx;
|
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
}
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
.empty-text {
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 分页器样式
|
|
|
|
|
|
.pager-container {
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-top: 1rpx solid #f0f0f0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pager-info {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin-bottom: 20rpx;
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
.pager-text {
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pager-controls {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 20rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pager-btn {
|
|
|
|
|
|
min-width: 120rpx;
|
|
|
|
|
|
height: 60rpx;
|
|
|
|
|
|
border: 1rpx solid #d9d9d9;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
font-size: 26rpx;
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
&:disabled {
|
|
|
|
|
|
color: #ccc;
|
|
|
|
|
|
background: #f5f5f5;
|
|
|
|
|
|
border-color: #d9d9d9;
|
|
|
|
|
|
}
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
&:not(:disabled):active {
|
|
|
|
|
|
background: #f0f0f0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-numbers {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 10rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-btn {
|
|
|
|
|
|
min-width: 60rpx;
|
|
|
|
|
|
height: 60rpx;
|
|
|
|
|
|
border: 1rpx solid #d9d9d9;
|
|
|
|
|
|
border-radius: 8rpx;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
font-size: 26rpx;
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
&.active {
|
|
|
|
|
|
background: #1890ff;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
border-color: #1890ff;
|
|
|
|
|
|
}
|
2025-08-26 09:25:40 +08:00
|
|
|
|
|
2025-08-25 18:06:33 +08:00
|
|
|
|
&:not(.active):active {
|
|
|
|
|
|
background: #f0f0f0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes spin {
|
2025-08-26 09:25:40 +08:00
|
|
|
|
0% {
|
|
|
|
|
|
transform: rotate(0deg);
|
|
|
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
|
|
|
transform: rotate(360deg);
|
|
|
|
|
|
}
|
2025-08-25 18:06:33 +08:00
|
|
|
|
}
|
2025-08-26 09:25:40 +08:00
|
|
|
|
</style>
|