先来看一下最终的效果:
说下, 封装分页组件的大致逻辑是怎样的:
1. 首先, 完成分页组件的基础布局
2. 在组件内部实现, 分页组件的基本逻辑交互
3. 父组件传入对应的数据, 用户点击分页组件的按钮时; 通知父组件发送请求更新数据
1. 分页组件的基本布局:
<template>
<div class="xtx-pagination">
<a href="javascript:;" class="disabled">上一页</a>
<span>...</span>
<a href="javascript:;" class="active">3</a>
<a href="javascript:;">4</a>
<a href="javascript:;">5</a>
<a href="javascript:;">6</a>
<a href="javascript:;">7</a>
<span>...</span>
<a href="javascript:;">下一页</a>
</div>
</template>
<script>
export default {
name: 'XtxPagination'
}
</script>
<style scoped lang="less">
.xtx-pagination {
display: flex;
justify-content: center;
padding: 30px;
> a {
display: inline-block;
padding: 5px 10px;
border: 1px solid #e4e4e4;
border-radius: 4px;
margin-right: 10px;
&:hover {
color: @xtxColor;
}
&.active {
background: @xtxColor;
color: #fff;
border-color: @xtxColor;
}
&.disabled {
cursor: not-allowed;
opacity: 0.4;
&:hover {
color: #333
}
}
}
> span {
margin-right: 10px;
}
}
</style>
2. 完成分页组件的基本逻辑交互
首先我们得知道分页组件有哪些逻辑交互:
1. 上一页和下一页按钮点击时, 切换被高亮的按钮
2. 用户点击单个按钮时, 切换对应的高亮效果
思路分析:
1. 定义分页组件最主要的三条数据(当前页 myCurrent , 一页多少条数据 pageSize , 总共有多少条数据 total)
2. 定义页面显示按钮的个数(count), 其他不显示的按钮由省略号代替
3. 我们需要通过计算属性的动态的计算, 所以定义一个计算属性(pager)
4. 计算属性里面我们需要得到, 总共有多少页(pageCount), 起始页码值(start), 结尾页码值(end)和按钮区间数组(btnArr)
5. 我们分为三种情况去计算 start 和 end 值, 第一种(当前页正中间), 第二种(当前页在起始的位置), 第三种(当前页在最后的位置)
6. 用页面显示按钮的个数(count) / 2, 得到偏移量, start 的值就是当前页码值减去偏移量; end 值就是当前页码值加上偏移量
7. 因为起始的页码值默认为1, 如果当前页码值减去偏移量的值小于了1; 就说明超出了限制, 那么就让 start 的值为1即可
8. end 值可以用 start 值加上 count 减1
9. 如果当前页码值加上偏移量大于了总页数, 和上面计算 start 值超出的算法反过来就行
10. 最后定义一个记录按钮区间的数组(btnArr), 循环的将每一个页码值 push 进去
11. 计算属性最后返回, btnArr , start , end , pageCount 数据
<template>
<div class="xtx-pagination" v-if="total > 0">
<a v-if="myCurrentPage > 1" href="javascript:;" @click="myCurrentPage--">上一页</a>
<a v-else href="javascript:;" class="disabled">上一页</a>
<span v-if="pager.start > 1">...</span>
<a href="javascript:;" @click="myCurrentPage = item" v-for="item in pager.btnArr" :key="item" :class="{active: item === myCurrentPage}">{{ item }}</a>
<span v-if="pager.end < pager.pageCount">...</span>
<a v-if="myCurrentPage < pager.pageCount" href="javascript:;" @click="myCurrentPage++)">下一页</a>
<a v-else href="javascript:;" class="disabled">下一页</a>
</div>
</template>
<script>
import { ref, computed, watch } from 'vue'
export default {
name: 'XtxPagination',
props: {
currentPage: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 10
},
total: {
type: Number,
default: 100
}
},
setup (props, { emit }) {
// 按钮的个数, 如果想动态的显示按钮的个数就需要将此变量设置成一个响应式数据
const count = 5
// 当前页
const myCurrentPage = ref(1)
// 一页显示多少条数据
const pageSize = ref(10)
// 总共有多少条数据
const total = ref(100)
// 根据按钮的个数和当前页码, 计算出起始页码值, 结尾页码值和总页数
const pager = computed(() => {
// 总页数
const pageCount = Math.ceil(total.value / pageSize.value)
// 最理想的值(当前页在中间)
// 计算偏移量
const offset = Math.floor(count / 2)
// 起始值
let start = myCurrentPage.value - offset
// 结束值
let end = (count % 2) === 0 ? myCurrentPage.value + offset - 1 : myCurrentPage.value + offset
// 计算得到start值超出范围的情况
if (start < 1) {
start = 1
end = (start + count - 1) > pageCount ? pageCount : (start + count - 1)
}
// 计算得到end值超出范围的情况
if (end > pageCount) {
end = pageCount
start = (end - count + 1) < 1 ? 1 : (end - count + 1)
}
// 得到按钮的区间
const btnArr = []
for (let i = start; i <= end; i++) {
btnArr.push(i)
}
return {
pageCount,
start,
end,
btnArr
}
})
return { pager, myCurrentPage}
}
}
</script>
<style scoped lang="less">
.xtx-pagination {
display: flex;
justify-content: center;
padding: 30px;
> a {
display: inline-block;
padding: 5px 10px;
border: 1px solid #e4e4e4;
border-radius: 4px;
margin-right: 10px;
&:hover {
color: @xtxColor;
}
&.active {
background: @xtxColor;
color: #fff;
border-color: @xtxColor;
}
&.disabled {
cursor: not-allowed;
opacity: 0.4;
&:hover {
color: #333
}
}
}
> span {
margin-right: 10px;
}
}
</style>
3. 父组件与子组件的交互
主要交互的是, 父组件将 "当前页码", "一页显示多少条", "总共数据的条数" 传给子组件
子组件使用 watch 监听父组件传入数据的变化, 一旦发生改变就修改掉子组件内部定义的数据
(这样可以不去改动先前子组件定义好的死数据, watch 监听到直接使用 props 改变的数据替换掉子组件内定死的数据即可)
当用户修改 "当前页码" 的值的时候, 将修改的值 emit 出去; 父组件拿到调用接口, 获取最新的数据
<template>
<div class="xtx-pagination" v-if="total > 0">
<a v-if="myCurrentPage > 1" href="javascript:;" @click="changePage(myCurrentPage - 1)">上一页</a>
<a v-else href="javascript:;" class="disabled">上一页</a>
<span v-if="pager.start > 1">...</span>
<a href="javascript:;" @click="changePage(item)" v-for="item in pager.btnArr" :key="item" :class="{active: item === myCurrentPage}">{{ item }}</a>
<span v-if="pager.end < pager.pageCount">...</span>
<a v-if="myCurrentPage < pager.pageCount" href="javascript:;" @click="changePage(myCurrentPage + 1)">下一页</a>
<a v-else href="javascript:;" class="disabled">下一页</a>
</div>
</template>
<script>
import { ref, computed, watch } from 'vue'
export default {
name: 'XtxPagination',
props: {
currentPage: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 10
},
total: {
type: Number,
default: 100
}
},
setup (props, { emit }) {
......
// 用户触发点击事件, 将当前的页码值传给父组件
const changePage = (page) => {
if (myCurrentPage.value !== page) {
myCurrentPage.value = page
}
emit('current-change', page)
}
watch(props, () => {
myCurrentPage.value = props.currentPage
pageSize.value = props.pageSize
total.value = props.total
})
return { pager, myCurrentPage, changePage }
}
}
</script>
<style scoped lang="less">
......
</style>
父组件里面有一个技巧
就是使用 watch 监听后端需要的数据 reqParams , 因为子组件传过来的值会去修改 reqParams 中的数据
所以, 只要 reqParams 发生改变; 就会立即调用请求获取数据
<!-- 分页组件 -->
<XtxPagination :total="total" :pageSize="reqParams.pageSize" :currentPage="reqParams.page" @current-change="changePage" />
<script>
export default {
setup () {
// 后端需要的筛选条件
const reqParams = reactive({
page: 4,
pageSize: 10,
hisPicture: null,
tag: null,
// sortField的值有praiseCount(最热), createTime(最新)
sortField: null
})
// 用户点击分页组件按钮, 切换当前页
const changePage = (page) => {
reqParams.page = page
}
const commentList = ref([])
const total = ref(0)
watch(reqParams, async () => {
const { result } = await findCommentListByGoods(goods.id, reqParams)
total.value = result.counts
commentList.value = result.items
}, { immediate: true })
}
}
</script>