父组件:goods-comment
<template>
<div class="goods-comment">
<!-- 评价头部 -->
<div class="head" v-if="commentInfo">
<div class="data">
<p>
<span>{{ commentInfo.salesCount }}</span><span>人购买</span>
</p>
<p>
<span>{{ commentInfo.praisePercent }}</span><span>好评率</span>
</p>
</div>
<div class="tags">
<div class="dt">大家都在说:</div>
<div class="dd">
<a
v-for="(item,i) in commentInfo.tags"
:key="item.title"
href="javascript:;"
:class="{active:currentTageIndex === i }"
@click='changeTag(i)'
>{{item.title}}({{item.tagCount}})</a
>
</div>
</div>
</div>
<div class="sort" v-if='commentInfo'>
<span>排序:</span>
<a @click="changeSort(null)" :class="{active:reqParams.sortField === null}" href="javascript:;">默认</a>
<a @click="changeSort('createTime')" :class="{active:reqParams.sortField === 'createTime'}" href="javascript:;">最新</a>
<a @click="changeSort('praiseCount')" :class="{active:reqParams.sortField === 'praiseCount'}" href="javascript:;">最热</a>
</div>
<!-- 评价列表 -->
<div class="list" v-if="commentList">
<div class="item" v-for="item in commentList" :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="i in item.score" :key="i+'s'" class="iconfont icon-wjx01"></i>
<i v-for="i in 5-item.score" :key="i+'k'" class="iconfont icon-wjx02"></i>
<span class="attr">{{formatSpecs(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
:total='total'
:page-size='reqParams.pageSize'
:current-page='reqParams.page'
@current-change='changePagerFn'
v-if="total"
/>
</div>
</template>
<script>
import { inject, ref, reactive , watch} from "vue";
import { findGoodsCommentInfo ,findGoodsCommentList} from "@/api/product";
import GoodsCommentImage from './goods-comment-image.vue'
export default {
name: "GoodsComment",
components:{GoodsCommentImage},
setup() {
// 获取评价信息
const commentInfo = ref(null);
const goods = inject("goods");
findGoodsCommentInfo(goods.value.id).then((data) => {
data.result.tags.unshift({
title: "有图",
tagCount: data.result.hasPictureCount,
type:'img'
});
data.result.tags.unshift({
title: "全部评价",
tagCount: data.result.evaluateCount,
type:'all'
});
// 设置数据之前,tags数组前追加 有图tag 全部评价tag
commentInfo.value = data.result;
});
// 激活tag
const currentTageIndex = ref(0)
const changeTag = (i) =>{
currentTageIndex.value = i
// 点击tag的时候修改筛选条件
const tag = commentInfo.value.tags[i]
// 情况一:全部评价
// 情况二:有图
// 情况三:正常tag
if(tag.type === 'all'){
reqParams.hasPicture = null
reqParams.tag = null
}else if(tag.type === 'img'){
reqParams.hasPicture = true
reqParams.tag = null
}else{
reqParams.hasPicture = null
reqParams.tag = tag.title
}
// 页码重置到1
reqParams.page = 1
}
// 点击排序
const changeSort = (sortField)=>{
reqParams.sortField = sortField
// 页码重置到1
reqParams.page = 1
}
// 准备筛选条件数据
const reqParams = reactive({
page:1,
pageSize:10,
hasPicture:null,
tag:null,
// 排序方式 praiseCount 热度, createTime最新
sortField:null
})
// 初始化需要发请求 筛选条件发送改变也要发请求
const commentList = ref([])
// 总条数 总数量
const total = ref(0)
watch(reqParams,()=>{
findGoodsCommentList(goods.value.id, reqParams).then(data=>{
commentList.value = data.result.items
// console.log(data.result.items);
total.value = data.result.counts
})
},{immediate:true})
// 定义转换数据的函数(对应vue2.0的过滤器)
const formatSpecs = (specs)=>{
return specs.reduce((p,c)=>`${p} ${c.name}: ${c.nameValue}`,'').trim()
}
const formatNickname = (nickname)=>{
return nickname.substr(0,1) + '****' +nickname.substr(-1)
}
// 实现分页切换 分页组件传递的 newPage当前页码
const changePagerFn = (newPage)=>{
// 导致watch 监听到reqParams变化 从新发请求
reqParams.page = newPage
}
return { commentInfo, currentTageIndex, changeTag, reqParams, commentList,
changeSort, formatSpecs, formatNickname, total, changePagerFn
};
}
};
</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>
子组件:xtx-pagination
<template>
<div class="xtx-pagination">
<a @click="changePager(myCurrentPage - 1)" href="javascript:;" v-if='myCurrentPage > 1' >上一页</a>
<a href="javascript:;" v-else class="disabled">上一页</a>
<span v-if="pager.start > 1">...</span>
<a @click="changePager(i)" href="javascript:;" v-for="i in pager.btnArr" :key="i" :class="{active: i === myCurrentPage}" >{{i}}</a>
<span v-if='pager.end < pager.pageCount'>...</span>
<a @click="changePager(myCurrentPage + 1)" href="javascript:;" v-if="myCurrentPage < pager.pageCount">下一页</a>
<a href="javascript:;" v-else class="disabled">下一页</a>
</div>
</template>
<script>
import { computed, ref, watch } from "vue";
export default {
name: "XtxPagination",
props:{
// 总条数
total:{
type:Number,
default:100
},
// 每页的条数
pageSize:{
type:Number,
default:10
},
// 当前显示的页码
currentPage:{
type:Number,
default:1
}
},
setup(props,{emit}) {
// 需要数据:
// 1.按钮个数 5个
const count = 5;
// 2.当前显示的页码
const myCurrentPage = ref(1);
// 3.总页数 = 总条数 / 每页的条数 向上取整
const myTotal = ref(100);
const myPageSize = ref(10);
// 其他数据(总页数 起始按钮 结束按钮 按钮数组) 都依赖上面四条数据
const pager = computed(() => {
// 总页数
const pageCount = Math.ceil(myTotal.value / myPageSize.value);
//按钮个数和当前页码 ====> 起始按钮 结束按钮 按钮数组
// 1.理想情况下:
const offset = Math.floor(count / 2);
let start = myCurrentPage.value - offset;
let end = start + count - 1;
// 2.如果其实页码小于1 需要处理
if (start < 1) {
start = 1;
end = start + count - 1 > pageCount ? pageCount : start + count - 1;
}
// 3.如果结束页码大于总页数
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, btnArr, start, end };
});
// 监听props的变化 更新组件内的数据
watch(props,()=>{
myTotal.value = props.total
myPageSize.value = props.pageSize
myCurrentPage.value = props.currentPage
},{immediate:true})
// 切换分页的函数
const changePager = (page)=>{
myCurrentPage.value = page
// 将现在的页码通知父组件
emit('current-change',page)
}
return { myCurrentPage, pager, changePager };
},
};
</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>
结果实例: