313 lines
5.7 KiB
Vue
313 lines
5.7 KiB
Vue
<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>
|
||
|
||
<view v-else-if="noMore && list.length > 0" class="no-more-state">
|
||
<text class="no-more-text">{{ noMoreText }}</text>
|
||
</view>
|
||
|
||
<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>
|
||
|
||
<view class="pager-controls">
|
||
<button
|
||
class="pager-btn prev-btn"
|
||
:disabled="currentPage <= 1"
|
||
@click="handlePageChange(currentPage - 1)"
|
||
>
|
||
上一页
|
||
</button>
|
||
|
||
<view class="page-numbers">
|
||
<button
|
||
v-for="page in visiblePages"
|
||
:key="page"
|
||
:class="['page-btn', { active: page === currentPage }]"
|
||
@click="handlePageChange(page)"
|
||
>
|
||
{{ page }}
|
||
</button>
|
||
</view>
|
||
|
||
<button
|
||
class="pager-btn next-btn"
|
||
: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',
|
||
validator: value => ['loadMore', 'pager'].includes(value),
|
||
},
|
||
|
||
// 数据列表
|
||
list: {
|
||
type: Array,
|
||
default: () => [],
|
||
},
|
||
|
||
// 总数据量
|
||
total: {
|
||
type: Number,
|
||
default: 0,
|
||
},
|
||
|
||
// 当前页码
|
||
currentPage: {
|
||
type: Number,
|
||
default: 1,
|
||
},
|
||
|
||
// 每页数量
|
||
pageSize: {
|
||
type: Number,
|
||
default: 10,
|
||
},
|
||
|
||
// 是否正在加载
|
||
loading: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
|
||
// 是否没有更多数据
|
||
noMore: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
|
||
// 自定义文本
|
||
loadingText: {
|
||
type: String,
|
||
default: '正在加载...',
|
||
},
|
||
|
||
noMoreText: {
|
||
type: String,
|
||
default: '没有更多数据了',
|
||
},
|
||
|
||
emptyText: {
|
||
type: String,
|
||
default: '暂无数据',
|
||
},
|
||
|
||
emptyIcon: {
|
||
type: String,
|
||
default: '📋',
|
||
},
|
||
|
||
// 分页器显示的页码数量
|
||
visiblePageCount: {
|
||
type: Number,
|
||
default: 5,
|
||
},
|
||
},
|
||
|
||
computed: {
|
||
// 总页数
|
||
totalPages() {
|
||
return Math.ceil(this.total / this.pageSize)
|
||
},
|
||
|
||
// 可见的页码
|
||
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)
|
||
|
||
// 调整起始位置
|
||
if (end - start + 1 < this.visiblePageCount) {
|
||
start = Math.max(1, end - this.visiblePageCount + 1)
|
||
}
|
||
|
||
for (let i = start; i <= end; i++) {
|
||
pages.push(i)
|
||
}
|
||
|
||
return pages
|
||
},
|
||
},
|
||
|
||
methods: {
|
||
// 处理页码变化
|
||
handlePageChange(page) {
|
||
if (page < 1 || page > this.totalPages || page === this.currentPage) {
|
||
return
|
||
}
|
||
|
||
this.$emit('page-change', page)
|
||
},
|
||
|
||
// 重置分页状态
|
||
reset() {
|
||
this.$emit('reset')
|
||
},
|
||
},
|
||
}
|
||
</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;
|
||
|
||
.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;
|
||
}
|
||
|
||
.loading-text {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
}
|
||
}
|
||
|
||
.no-more-state {
|
||
padding: 20rpx 0;
|
||
|
||
.no-more-text {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
}
|
||
|
||
.empty-state {
|
||
padding: 40rpx 0;
|
||
|
||
.empty-icon {
|
||
font-size: 80rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.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;
|
||
|
||
.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;
|
||
|
||
&:disabled {
|
||
color: #ccc;
|
||
background: #f5f5f5;
|
||
border-color: #d9d9d9;
|
||
}
|
||
|
||
&: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;
|
||
|
||
&.active {
|
||
background: #1890ff;
|
||
color: #fff;
|
||
border-color: #1890ff;
|
||
}
|
||
|
||
&:not(.active):active {
|
||
background: #f0f0f0;
|
||
}
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% {
|
||
transform: rotate(0deg);
|
||
}
|
||
100% {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
</style>
|