分页组件的封装
分页的本质:分批次查询数据(基于页码page 和每页条数pagesize ),后端接收到分页参数后,会基于这些参数查询数据库,然后基于数据库进行分页,基于SQL 语句
分页组件实现的步骤
- 分页基础的布局,依赖数据分析。
- 分页内容逻辑,完成切换效果。
- 接收外部数据,提供分页事件。
落地的代码
- 分页的基础布局,在
src/component/libray 中新建一个pagination.vue 的文件,基本结构
<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>
然后从父组件中传入数据每页的总数据,或者先给一个假的数据
export default {
name:'XtxPagination',
props:{
total: {
type: Number,
default: 80
},
pagesize: {
type: Number,
default: 10
}
}
}
然后再setup(){} 中导入props ,在计算中页数
const pages=computed(()=>Math.ceil(props.total/props.pagesize))
然后再动态计算一下页码的列表
const currentPage = ref(attrs.page || 1)
const list =component(()=>{
const result=[]
if(pages.value<=5){
for(let i=1;i<pages.value;i++){
result.push(i)
}
}else{
if(currentPage.value<=2){
for(let i=1;i<=5;i++){
result.push(i)
}
}else if(currentPage.value>=page.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
})
然后在页面中循环出来,就可以了,代码如下
<template>
<div class="xtx-pagination">
<a @click='changePage(false)' href="javascript:;" :class="{disabled: currentPage===1}">上一页</a>
<span v-if='currentPage > 3'>...</span>
<a @click='changePage(item)' href="javascript:;" :class='{active: currentPage===item}' v-for='item in list' :key='item'>{{item}}</a>
<span v-if='currentPage < pages - 2'>...</span>
<a @click='changePage(true)' href="javascript:;" :class='{disabled: currentPage===pages}'>下一页</a>
</div>
</template>
点击左右的按钮切换当前的页面
const changePage=(type)=>{
if(type===false){
if(currentPage.value===1) return
if(currentPage.value>1){
currentPage.value-=1
}
}else if(type===true){
if(currentPAge.value===pages.value)return
if(currentPage.value<pages.value){
currentPage.value+=1
}
}else{
currentPage.value=type
}
}
然后把currentPage 传给父组件,并改变page的值
<script>
emit('change-page', currentPage.value)
</script>
<XtxPagination @change-page="changePage" />
<script>
const changePage = page => {
reqParams.page = page
}
</script>
详细的代码,再最下面
<template>
<div class="xtx-pagination">
<a @click="changePage(false)" href="javascript:;" :class="{ disabled: currentPage === 1 }">上一页</a>
<span v-if="currentPage > 3">...</span>
<a
@click="changePage(item)"
:class="{ active: currentPage === item }"
v-for="item in list"
:key="item"
href="javascript:;"
>{{ item }}</a
>
<span v-if="currentPage < pages - 1">...</span>
<a @click="changePage(true)" href="javascript:;" :class="{ disabled: currentPage === pages }">下一页</a>
</div>
</template>
<script>
import { computed, ref } from 'vue'
export default {
name: 'XtxPagination',
props: {
total: {
type: Number,
default: 80
}
},
setup(props, { emit }) {
const pageSize = 8
const pages = Math.ceil(props.total / pageSize)
const currentPage = ref(1)
const list = computed(() => {
const result = []
if (pages <= 5) {
for (let i = 1; i <= pages; i++) {
result.push(i)
}
} else {
if (currentPage.value <= 2) {
for (let i = 1; i <= 5; i++) {
result.push(i)
}
} else if (currentPage.value >= pages - 1) {
for (let i = pages - 4; i <= pages; 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) {
if (currentPage.value === 1) return
if (currentPage.value > 1) {
currentPage.value -= 1
}
} else if (type === true) {
if (currentPage.value === pages) return
if (currentPage.value < pages) {
currentPage.value += 1
}
} else {
currentPage.value = type
}
emit('change-page', currentPage.value)
}
return { list, currentPage, changePage, pages }
}
}
</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>
父组件
<template>
<div class="goods-comment">
<div class="head" v-if="conditions">
<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
@click="toggle(index)"
:class="{ active: currentIndex === index }"
v-for="(item, index) in conditions.tags"
:key="index"
href="javascript:;"
>{{ item.title }}({{ item.tagCount }})</a
>
</div>
</div>
</div>
<div class="sort">
<span>排序:</span>
<a :class="{ active: reqParams.sortField === null }" @click="changeSort(null)" href="javascript:;">默认</a>
<a :class="{ active: reqParams.sortField === 'createTime' }" @click="changeSort('createTime')" href="javascript:;"
>最新</a
>
<a
:class="{ active: reqParams.sortField === 'praiseCount' }"
@click="changeSort('praiseCount')"
href="javascript:;"
>最热</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>{{ formatNickname(item.member.nickname) }}</span>
</div>
<div class="body">
<div class="score">
<i v-for="star in item.score" :key="star" class="iconfont icon-wjx01"></i>
<i v-for="unstar in 5 - item.score" :key="unstar" class="iconfont icon-wjx02"></i>
<span class="attr">{{ formatSpec(item.orderInfo.specs) }}</span>
</div>
<div class="text">{{ item.content }}</div>
<GoodsCommentImage v-if="item.pictures.length" :pictures="item.pictures" />
<div class="time">
<span>{{ item.createTime }}</span>
<span class="zan"><i class="iconfont icon-dianzan"></i>{{ item.praiseCount }}</span>
</div>
</div>
</div>
</div>
<XtxPagination @change-page="changePage" />
</div>
</template>
<script>
import { ref, inject, reactive, watch } from 'vue'
import { findCommentInfoByGoods, findCommentListByGoods } from '@/api/product.js'
import GoodsCommentImage from './goods-comment-image.vue'
export default {
name: 'GoodsComment',
components: { GoodsCommentImage },
setup () {
const conditions = ref(null)
const goods = inject('goods')
findCommentInfoByGoods(goods.value.id).then(ret => {
ret.result.tags.unshift({
title: '有图',
type: 'pic',
tagCount: ret.result.hasPictureCount
})
ret.result.tags.unshift({
title: '全部评价',
type: 'all',
tagCount: ret.result.evaluateCount
})
conditions.value = ret.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 total = ref(0)
const reqParams = reactive({
page: 1,
pageSize: 10,
hasPicture: null,
tag: null,
sortField: null
})
watch(
reqParams,
() => {
findCommentListByGoods(goods.value.id, reqParams).then(ret => {
total.value = ret.result.counts
list.value = ret.result.items
})
},
{
immediate: true
}
)
const changeSort = type => {
reqParams.sortField = type
reqParams.page = 1
}
const formatSpec = specs => {
return specs.reduce((ret, item) => ret + item.name + ':' + item.nameValue + ' ', '')
}
const formatNickname = nickname => {
return nickname.substr(0, 1) + '****' + nickname.substr(-1)
}
const changePage = page => {
reqParams.page = page
}
return {
changePage,
total,
formatNickname,
formatSpec,
list,
conditions,
currentIndex,
toggle,
reqParams,
changeSort
}
}
}
</script>
<style scoped lang="less">
.goods-comment {
.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;
}
}
}
.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;
}
}
}
}
</style>
|