效果:
分页的本质:
分批次的查询数据(基于页码page与每页的条数pagesize),后端接收到分页参数后,会基于这些参数查询数据库,然后基于数据库进行分页;基于sql语句(select * from user limit n,m);
分页组件需要实现的功能:
-
基本组件的结果
-
动态计算中间的页码
-
控制省略号的显示和隐藏,控制页码的切换,点击第4页的时候会 居中显示和前后出现省略号,后2页的时候不显示后面的省略号,
-
控制上一页和下一页的按钮禁用状态,点击第一页时候,前面没有省略号,上一页按钮为禁用状态
全部代码:
父组件:父组件是一个评论列表
<template>
<div class="goods-comment" v-if="conditions">
<div class="head">
<div class="data">
<p>
<span>{{ conditions.salesCount }}</span
><span>人购买</span>
</p>
<p>
<span>{{ conditions.praisePercent }}</span
><span>好评率</span>
</p>
</div>
<div class="tags">
<div class="dt">大家都在说:</div>
<div class="dd">
<a
href="javascript:;"
@click="toggle(i)"
:class="{ active: currentIndex === i }"
v-for="(item, i) in conditions.tags"
:key="i"
>{{ item.title }}({{ item.tagCount }})</a
>
</div>
</div>
</div>
<div class="sort">
<span>排序:</span>
<a href="javascript:;" :class="{ active: reqParams.sortField === null }" @click="changeSort(null)">默认</a>
<a href="javascript:;" @click="changeSort('createTime')" :class="{ active: reqParams.sortField === 'createTime' }"
>最新</a
>
<a
href="javascript:;"
@click="changeSort('praiseCount')"
:class="{ active: reqParams.sortField === 'praiseCount' }"
>最热</a
>
</div>
<!-- 评论列表 -->
<div class="list">
<!-- 列表 -->
<div class="item" v-for="item in list" :key="item.id">
<div class="user">
<img :src="item.member.avatar" alt="" />
<span>{{ item.member.nickname }}</span>
</div>
<div class="body">
<div class="score">
<i class="iconfont icon-wjx01" v-for="star in item.score" :key="star"></i>
<i class="iconfont icon-wjx01" v-for="unstar in 5 - item.score" :key="unstar"></i>
<span class="attr">{{ formatSpec(item.orderInfo.specs) }}</span>
</div>
<div class="text">
{{ item.content }}
<!-- 评论图片的预览 -->
<GoodsCommentImage :pictures="item.pictures"></GoodsCommentImage>
</div>
<div class="time">
<span>{{ item.createTime }}</span>
<span class="zan"><i class="iconfont icon-dianzan"></i>{{ item.praiseCount }}</span>
</div>
</div>
</div>
</div>
<!-- 分页组件 page=1是默认的高亮页码 -->
<XtxPagination :total="total" @changePage="changePage" :page="1" :pagesize="reqParams.pagesize"></XtxPagination>
</div>
</template>
<script>
import GoodsCommentImage from '@/views/goods/goods-comment-image.vue'
import { ref, inject, reactive, watch } from 'vue'
import { findCommentInfoByGoods, findCommentListByGoods } from '@/api/product.js'
export default {
name: 'GoodsComment',
props: {
// id: {
// type: String,
// required: true
// }
},
components: { GoodsCommentImage },
setup() {
const conditions = ref(null)
const goods = inject('goods')
const total = ref(0)
findCommentInfoByGoods(goods.value.id).then(res => {
res.result.tags.unshift({
title: '有图',
type: 'pic',
tagCount: res.result.hasPictureCount
})
res.result.tags.unshift({
title: '全部评价',
type: 'all',
tagCount: res.result.evaluateCount
})
conditions.value = res.result
})
const currentIndex = ref(0)
const toggle = index => {
currentIndex.value = index
// 根据索引获取当前点击的标签的对象信息
const info = conditions.value.tags[index]
if (info.type === 'pic') {
// 点击有图
reqParams.hasPicture = true
reqParams.tag = null
} else if (info.type === 'all') {
// 全部评价
reqParams.hasPicture = null
reqParams.tag = null
} else {
// 其他的四个标签
reqParams.hasPicture = null
reqParams.tag = info.title
}
reqParams.page = 1
}
const list = ref([])
// 筛选条件准备
// 改为响应式的才可以高亮
const reqParams = reactive({
// 当前页码
page: 1,
// 每页的条数
pageSize: 10,
// 是否有图片
hasPicture: null,
// 筛选条件
tag: null,
// 排序的字段
sortField: null
})
findCommentListByGoods(goods.value.id, reqParams).then(res => {
list.value = res.result.items
total.value = res.result.counts
})
// 控制排序条件的切换
const changeSort = type => {
reqParams.sortField = type
reqParams.page = 1
}
// 检测reqParams是否有变化
watch(reqParams, () => {
findCommentListByGoods(goods.value.id, reqParams).then(res => {
total.value = res.result.counts
list.value = res.result.items
})
})
// 格式化规格信息
const formatSpec = spec => {
return spec.reduce((ret, item) => {
return ret + item.name + ':' + item.nameValue
}, '')
}
// 修改分页组件
const changePage = page => {
reqParams.page = page
}
return { changePage, conditions, currentIndex, toggle, list, total, reqParams, changeSort, formatSpec }
}
}
</script>
<style scoped lang="less">
.goods-comment {
.list {
padding: 0 20px;
.item {
display: flex;
padding: 25px 10px;
border-bottom: 1px solid #f5f5f5;
.user {
width: 160px;
img {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
}
span {
padding-left: 10px;
color: #666;
}
}
.body {
flex: 1;
.score {
line-height: 40px;
.iconfont {
color: #ff9240;
padding-right: 3px;
}
.attr {
padding-left: 10px;
color: #666;
}
}
}
.text {
color: #666;
line-height: 24px;
}
.time {
color: #999;
display: flex;
justify-content: space-between;
margin-top: 5px;
}
}
}
.head {
display: flex;
padding: 30px 0;
.data {
width: 340px;
display: flex;
padding: 20px;
p {
flex: 1;
text-align: center;
span {
display: block;
&:first-child {
font-size: 32px;
color: @priceColor;
}
&:last-child {
color: #999;
}
}
}
}
.tags {
flex: 1;
display: flex;
border-left: 1px solid #f5f5f5;
.dt {
font-weight: bold;
width: 100px;
text-align: right;
line-height: 42px;
}
.dd {
flex: 1;
display: flex;
flex-wrap: wrap;
> a {
width: 132px;
height: 42px;
margin-left: 20px;
margin-bottom: 20px;
border-radius: 4px;
border: 1px solid #e4e4e4;
background: #f5f5f5;
color: #999;
text-align: center;
line-height: 40px;
&:hover {
border-color: @xtxColor;
background: lighten(@xtxColor, 50%);
color: @xtxColor;
}
&.active {
border-color: @xtxColor;
background: @xtxColor;
color: #fff;
}
}
}
}
}
.sort {
height: 60px;
line-height: 60px;
border-top: 1px solid #f5f5f5;
border-bottom: 1px solid #f5f5f5;
margin: 0 20px;
color: #666;
> span {
margin-left: 20px;
}
> a {
margin-left: 30px;
&.active,
&:hover {
color: @xtxColor;
}
}
}
}
</style>
分页组件:
分为两种情况:
1.页码小于等于5
2.页码大于5:
又分为三种情况
1).页码小于2时候,页码从1开始到5结束;(左临界值)
2).页码小于最大页码的后两个的时候,页码从pages(总页码)-4开始到pages结束(右临界值)
3).页码始终在中间时,页码从currentPage-2开始到currentPage+2结束(currentPage是当前页面)
<template>
<div class="xtx-pagination">
<a href="javascript:;" @click="changePage(false)" :class="{ disabled: currentPage === 1 }">上一页</a>
<span v-if="currentPage > 3">...</span>
<a
href="javascript:;"
@click="changePage(item)"
:class="{ active: currentPage === item }"
v-for="item in list"
:key="item"
>{{ item }}</a
>
<span v-if="currentPage < pages - 2">...</span>
<a href="javascript:;" @click="changePage(true)" :class="{ disabled: currentPage === pages }">下一页</a>
</div>
</template>
<script>
import { computed, ref } from 'vue'
export default {
name: 'XtxPagination',
props: {
total: {
type: Number,
default: 0
},
pagesize: {
type: Number,
default: 10
}
// // 默认初识页码,父组件给传 但是不这样接收
// page: {
// type: Number,
// default: 1
// }
},
setup(props, { emit, attrs }) {
// attrs表示父组件传得属性,但是props没有接收的属性,这种属性不是强制的 ,或者不是响应式的
// 动态计算中间的页码信息 基于一份数据获得另外一份数据
// 以下两行是约定的每页的条数pagesize = 8
// 总结数 默认值是0 在外边是只能有一次的计算所以放在计算属性中可以是根据传入的值随时变化的
const pages = computed(() => Math.ceil(props.total / props.pagesize))
console.log(attrs.page)
const currentPage = ref(attrs.page || 1)
const list = computed(() => {
// 当父组件传递的total的值发生了变化的时候,计算属性要重新计算
// pages = Math.ceil(props.total / props.pagesize)
const result = []
// 当前的页码
// 页码小于等于5的情况
if (pages.value <= 5) {
for (let i = 1; i <= pages.value; i++) {
result.push(i)
}
} else {
// 大于5页的情况
if (currentPage.value <= 2) {
// 左侧临界值
for (let i = 1; i <= 5; i++) {
result.push(i)
}
} else if (currentPage.value >= pages.value - 1) {
// 右侧临界值
for (let i = pages.value - 4; i <= pages.value; i++) {
result.push(i)
}
} else {
// 中间的临界值
for (let i = currentPage.value - 2; i <= currentPage.value + 2; i++) {
result.push(i)
}
}
}
return result
})
// 控制上一页和下一页的变化
const changePage = type => {
if (type === false) {
// 上一页
// 页码是第一页的时候,上一页为disabled状态
if (currentPage.value === 1) return
if (currentPage.value > 1) {
currentPage.value -= 1
}
} else if (type === true) {
// 下一页
// 如果是最后的页面 下一页为disabled状态
if (currentPage.value === pages.value) return
if (currentPage.value < pages.value) {
currentPage.value += 1
}
} else {
// 点击其他的页面
currentPage.value = type
}
emit('changePage', currentPage.value)
}
return {
list,
currentPage,
pages,
changePage
}
}
}
</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>
注意:attrs的使用是将父组价中给子组件传值,不需要响应式的可以这么写哦~~ See you