一、内容介绍
1、内容
本篇文章主要介绍的是评价列表的实现。由于评价列表中包含的内容在前面的学习过程中我们都开发过其中的组件,例如星星组件和评价筛选组件。因此在这一部分我们只需要对组件进行复用,然后稍作改动即可。
2、效果
二、具体实现
1、数据的获取
在评价列表展示页实现过程中我们要从服务器端获取评价信息,并且评价列表展示页是可以滚动的,因此使用better-scroll
created () {
axios.get('/api/ratings').then((response) => {
response = response.data
// console.log(response)
if (response.errno === ERR_OK) {
this.ratings = response.data
// console.log(this.ratings)
this.$nextTick(() => {
this.scroll = new BScroll(this.$refs.ratings, {
click: true
})
})
}
})
},
2、具体布局
template>
<div class="ratings" ref="ratings">
<div class="ratings-content">
<div class="overview">
<div class="overview-left">
<h1 class="score">{{seller.score}}</h1>
<div class="title">综合评价</div>
<div class="rank">高于周边商家{{seller.rankRate}}</div>
</div>
<div class="overview-right">
<div class="score-wrapper">
<span class="title">服务态度</span>
<Star :size="36" :score="seller.serviceScore" class="star"></Star>
<span class="score">{{seller.serviceScore}}</span>
</div>
<div class="score-wrapper">
<span class="title">商品评分</span>
<Star :size="36" :score="seller.foodScore"></Star>
<span class="score">{{seller.foodScore}}</span>
</div>
<div class="delivery-wrapper">
<span class="title">送达时间</span>
<span class="time">{{seller.deliveryTime}}分钟</span>
</div>
</div>
</div>
<Split></Split>
<RatingSelect
:select-type="selectType"
:only-content="onlyContent"
:ratings="ratings"
@type-select="typeSelect"
@content-toggle="conToggle">
</RatingSelect>
<div class="ratings-wapper">
<ul>
<li
v-for="(rating, index) in ratings" :key="index"
class="rating-item"
v-show="needShow(rating.rateType,rating.text)">
<div class="avatar">
<img :src="rating.avatar" width="28" height="28" alt="">
</div>
<div class="content">
<h1 class="name">{{rating.username}}</h1>
<div class="star-wrapper">
<Star :score="rating.score" class="star"></Star>
<span class="time" v-show="rating.deliveryTime">{{rating.deliveryTime}}</span>
</div>
<p class="text">{{rating.text}}</p>
<div class="recommend" v-show="rating.recommend">
<span class="iconfont icon-dianzan"></span>
<span class="item" v-for="(item, index) in rating.recommend" :key="index">{{item}}</span>
</div>
<div class="time">{{rating.rateTime | formatDate}}</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</template>
3、star组件
<template>
<div class="star">
<span
v-for="(item,index) in item"
:key="index"
:class="item"
class="star-item"></span>
</div>
</template>
定义总的星星的数量和状态
// 定义总的星星的数量和状态
const LENGTH = 5
const CLS_ON = 'on'
const CLS_OFF = 'off'
const CLS_HALF = 'half'
通过计算属性根据获取的分数来计算出星星的数量和状态
computed: {
item () {
let result = []
let score = Math.floor(this.score * 2) / 2
let hasDecimal = score % 1 !== 0
let integer = Math.floor(score)
for (let i = 0; i < integer; i++) {
result.push(CLS_ON)
}
if (hasDecimal) {
result.push(CLS_HALF)
}
while (result.length < LENGTH) {
result.push(CLS_OFF)
}
return result
}
}
4、ratingSelect组件
<RatingSelect
:select-type="selectType"
:only-content="onlyContent"
:ratings="ratings"
@type-select="typeSelect"
@content-toggle="conToggle">
</RatingSelect>
其中的方法和函数跟(4)的实现一样,在这里不详细介绍
methods: {
needShow (type, text) {
// 判断是否要显示内容
if (this.onlyContent && !text) {
return false
}
// 判断选择的类型
if (this.selectType === ALL) {
return true
} else {
return (type === this.selectType)
}
},
typeSelect (type) {
this.selectType = type
// 由于改变selectType的时候DOM是没有更新的,因此还是需要异步更新
this.$nextTick(() => {
this.scroll.refresh()
})
},
// 有无内容展示
conToggle (onlyContent) {
this.onlyContent = onlyContent
this.$nextTick(() => {
this.scroll.refresh()
})
}
},
filters: {
formatDate (time) {
let date = new Date(time)
return formatDates(date, 'yyyy-MM-dd hh:mm')
}
}
三、源码
ratings.vue
<template>
<div class="ratings" ref="ratings">
<div class="ratings-content">
<div class="overview">
<div class="overview-left">
<h1 class="score">{{seller.score}}</h1>
<div class="title">综合评价</div>
<div class="rank">高于周边商家{{seller.rankRate}}</div>
</div>
<div class="overview-right">
<div class="score-wrapper">
<span class="title">服务态度</span>
<Star :size="36" :score="seller.serviceScore" class="star"></Star>
<span class="score">{{seller.serviceScore}}</span>
</div>
<div class="score-wrapper">
<span class="title">商品评分</span>
<Star :size="36" :score="seller.foodScore"></Star>
<span class="score">{{seller.foodScore}}</span>
</div>
<div class="delivery-wrapper">
<span class="title">送达时间</span>
<span class="time">{{seller.deliveryTime}}分钟</span>
</div>
</div>
</div>
<Split></Split>
<RatingSelect
:select-type="selectType"
:only-content="onlyContent"
:ratings="ratings"
@type-select="typeSelect"
@content-toggle="conToggle">
</RatingSelect>
<div class="ratings-wapper">
<ul>
<li
v-for="(rating, index) in ratings" :key="index"
class="rating-item"
v-show="needShow(rating.rateType,rating.text)">
<div class="avatar">
<img :src="rating.avatar" width="28" height="28" alt="">
</div>
<div class="content">
<h1 class="name">{{rating.username}}</h1>
<div class="star-wrapper">
<Star :score="rating.score" class="star"></Star>
<span class="time" v-show="rating.deliveryTime">{{rating.deliveryTime}}</span>
</div>
<p class="text">{{rating.text}}</p>
<div class="recommend" v-show="rating.recommend">
<span class="iconfont icon-dianzan"></span>
<span class="item" v-for="(item, index) in rating.recommend" :key="index">{{item}}</span>
</div>
<div class="time">{{rating.rateTime | formatDate}}</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import BScroll from 'better-scroll'
import Star from '@/components/Star/star'
import Split from '@/components/split/split'
import RatingSelect from '@/components/ratingSelect/ratingSelect'
import axios from 'axios'
import {formatDates} from '@/common/js/date.js'
const ALL = 2
const ERR_OK = 0
export default {
props: {
seller: {
type: Object
}
},
components: {
Star,
Split,
RatingSelect
},
data () {
return {
showFlag: false,
selectType: ALL,
ratings: [],
onlyContent: true
}
},
created () {
axios.get('/api/ratings').then((response) => {
response = response.data
// console.log(response)
if (response.errno === ERR_OK) {
this.ratings = response.data
// console.log(this.ratings)
this.$nextTick(() => {
this.scroll = new BScroll(this.$refs.ratings, {
click: true
})
})
}
})
},
methods: {
needShow (type, text) {
// 判断是否要显示内容
if (this.onlyContent && !text) {
return false
}
// 判断选择的类型
if (this.selectType === ALL) {
return true
} else {
return (type === this.selectType)
}
},
typeSelect (type) {
this.selectType = type
// 由于改变selectType的时候DOM是没有更新的,因此还是需要异步更新
this.$nextTick(() => {
this.scroll.refresh()
})
},
// 有无内容展示
conToggle (onlyContent) {
this.onlyContent = onlyContent
this.$nextTick(() => {
this.scroll.refresh()
})
}
},
filters: {
formatDate (time) {
let date = new Date(time)
return formatDates(date, 'yyyy-MM-dd hh:mm')
}
}
}
</script>
<style>
.ratings{
position: absolute;
top: 174px;
bottom: 0;
left: 0;
width: 100%;
overflow: hidden;
}
.ratings .overview{
display: flex;
padding: 18px 0;
}
.ratings .overview .overview-left{
flex: 0 0 137px;
border-right: 1px solid rgba(7, 17, 27, 0.1);
text-align: center;
padding: 6px 0;
}
.ratings .overview .overview-left .score{
line-height: 28px;
font-size: 24px;
color: rgb(255,153,0);
margin-bottom: 6px;
}
.ratings .overview .overview-left .title{
font-size: 12px;
line-height: 12px;
color: rgb(7, 17, 27);
margin-bottom: 8px;
}
.ratings .overview .overview-left .rank{
font-size: 10px;
line-height: 10px;
color: rgb(147,153,159);
}
.ratings .overview .overview-right{
flex: 1;
padding: 6px 0 6px 24px;
}
.ratings .overview .overview-right .score-wrapper{
margin-bottom: 8px;
font-size: 0;
}
.ratings .overview .overview-right .score-wrapper .title{
display: inline-block;
vertical-align: top;
font-size: 12px;
line-height: 18px;
color: rgb(7, 17, 27);
}
.ratings .overview .overview-right .score-wrapper .star{
display: inline-block;
vertical-align: top;
margin: 0 12px;
}
.ratings .overview .overview-right .score-wrapper .score{
display: inline-block;
line-height: 18px;
vertical-align: top;
font-size: 12px;
color: rgb(255,153,0);
}
.ratings .overview .overview-right .delivery-wrapper{
font-size: 0;
}
.ratings .overview .overview-right .delivery-wrapper .title{
font-size: 12px;
line-height: 18px;
color: rgb(7, 17, 27);
}
.ratings .overview .overview-right .delivery-wrapper .time{
font-size: 12px;
margin-left: 12px;
color: rgb(147,153,159);
}
.ratings .ratings-wapper{
padding: 0 18px;
}
.ratings .ratings-wapper .rating-item{
display: flex;
padding: 18px 0;
border-bottom: 1px solid rgba(7, 17, 27, 0.1);
}
.ratings .ratings-wapper .rating-item .avatar{
flex: 0 0 28px;
width: 28px;
margin-right: 12px;
}
.ratings .ratings-wapper .rating-item .avatar img{
border-radius: 50%;
}
.ratings .ratings-wapper .rating-item .content{
position: relative;
flex: 1;
}
.ratings .ratings-wapper .rating-item .content .name{
margin-bottom: 4px;
line-height: 12px;
font-size: 10px;
color: rgb(7, 17, 27);
}
.ratings .ratings-wapper .rating-item .content .star-wrapper{
margin-bottom: 6px;
font-size: 0;
}
.ratings .ratings-wapper .rating-item .content .star-wrapper .star{
display: inline-block;
margin-right: 6px;
vertical-align: top;
font-size: 10px;
}
.ratings .ratings-wapper .rating-item .content .star-wrapper .time{
position: relative;
display: inline-block;
vertical-align: top;
color: rgb(147,153,159);
font-size: 12px;
margin-top: 5px;
line-height: 12px;
}
.ratings .ratings-wapper .rating-item .content .text{
line-height: 18px;
color: rgb(7, 17, 27);
font-size: 12px;
margin-bottom: 8px;
}
.ratings .ratings-wapper .rating-item .content .recommend{
line-height: 16px;
font-size: 0;
}
.ratings .ratings-wapper .rating-item .content .recommend .iconfont,
.ratings .ratings-wapper .rating-item .content .recommend .item{
display: inline-block;
margin: 0 8px 4px 0;
font-size: 9px;
}
.ratings .ratings-wapper .rating-item .content .recommend .iconfont{
color: rgb(0,160,220);
}
.ratings .ratings-wapper .rating-item .content .recommend .item{
padding: 0 6px;
border: 1px solid rgba(7, 17, 27, 0.1);
border-radius: 1px;
color: rgb(147,153,159);
background: #fff;
}
.ratings .ratings-wapper .rating-item .content .time{
position: absolute;
top: 0;
right: 0;
line-height: 12px;
font-size: 10px;
color: rgb(147,153,159);
}
</style>
star.vue
<template>
<div class="star">
<span
v-for="(item,index) in item"
:key="index"
:class="item"
class="star-item"></span>
</div>
</template>
<script>
// 定义总的星星的数量和状态
const LENGTH = 5
const CLS_ON = 'on'
const CLS_OFF = 'off'
const CLS_HALF = 'half'
export default {
props: {
seller: {
type: Object
},
score: {
type: Number
}
},
computed: {
item () {
let result = []
let score = Math.floor(this.score * 2) / 2
let hasDecimal = score % 1 !== 0
let integer = Math.floor(score)
for (let i = 0; i < integer; i++) {
result.push(CLS_ON)
}
if (hasDecimal) {
result.push(CLS_HALF)
}
while (result.length < LENGTH) {
result.push(CLS_OFF)
}
return result
}
}
}
</script>
<style scoped>
.star{
font-size: 0;
}
.star .star-item{
display: inline-block;
width:15px;
height: 15px;
margin-right: 6px;
background-size: 15px 15px;
background-repeat: no-repeat;
}
.star .on{
background-image: url('./star36_on@2x.png');
}
.star .half{
background-image: url('./star36_half@2x.png');
}
.star .off{
background-image: url('./star36_off@2x.png');
}
</style>