欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

vue2.0饿了么学习笔记(15)ratingselect组件的实现

程序员文章站 2023-12-31 15:06:16
...

vue2.0饿了么学习笔记(15)ratingselect组件的实现

之前我们已经完成了图片,产品价格和购物车部分,split组件,之后就是评论选择让ratingselect的实现,先完成部分Dom结构

ratingselect组件的实现

vue2.0饿了么学习笔记(15)ratingselect组件的实现

1)创建ratingselect文件和ratingselect.vue
2)设置ratingselect组件中需要的props接收的数据,数据应从food.vue组件传入<ratingselect></ratingselect>,并由ratingselect.vue的props接收

我们要props的值如下: 首先是有一个变量是否显示只看内容,还有一个控制选择的类型,还有要维护一个所有评价的数据,因为这里有一个评价数量;还要去维护一个描述,是(全部,推荐,吐槽)还是(全部,满意,不满意),按照以上标准设置外部组件传入ratingselect的props值;

 const POSITIVE = 0;
    const NEGATIVE = 1;
    const ALL = 2;
      export default {
        //需要一些评价数据才能完成评价组件
        props: {
           ratings: {
               type: Array,
               default() {
                   return [];
               }
           },
            selectType: { //全部,满意,不满意
                type: Number,
                default: ALL //默认情况时ALL,值等于2
            },
            onlyContent: { //只看有内容的评价还是所有的评价
                type: Boolean,
                default: false //设置为可以看到所有的评价
            },
            desc: { //描述
                type: Object,
                default() { //默认desc是这三种,在商品详情页的时候传入推荐或者吐槽
                    return {
                        all: '全部',
                        positive: '满意',
                        negative: '不满意'
                    };
                }
            }
        },

所以在food.vue(商品详情页)中引入ratingSelect组件的时候,将desc改成全部,推荐和吐槽,接下来去开发DOM

<template>
  <div class="ratingselect">
        <div class="rating-type" border-1px>
            <span>{{desc.all}}</span>
            <span>{{desc.positive}}</span>
            <span>{{desc.negative}}</span>
        </div>
        <div @click="toggleContent($event)"  class="switch" :class="{'on':oContent}">
            <span class="icon-check_circle"></span>
            <span class="text">只看有内容的评价</span>
        </div>
  </div>
</template>

之后在food.vue组件注册并引入这个组件,    

<div class="rating">
                <h1 class="title"> 商品评价</h1>
                <!--  ratings对应被点击的food的ratings-->
                <ratingselect  @increment="incrementTotal" :select-type="selectType" :only-content="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect>

3)在data中挂载对上述对象的跟踪,并对其进行初始化

food.vue中,在foods组件中引入并注册ratingselect.vue组件,并data属性和(:)属性将2)中需要的数据从foods组件中传到ratingselect.vue组件的props中

const POSITIVE = 0;
const NEGATIVE = 1;
const ALL = 2;
data () {
        return {
            showFlag: false,
            selectType: ALL,
            onlyContent: false, //先设置组件一开始显示有内容的评价
            desc: { //desc做了改变
                all: '全部',
                positive: '推荐',
                negative: '吐槽' 
            }
        };
    },

我们对变量做了上述的初始化设置,但是我们希望在切换不同商品的时候能有相同的初始化状态,所以show函数作为goods组件中调用food组件的函数,即点开商品详情的显示函数,将初始化设置传入到show函数中

show() { //可以被父组件调用到,方法前加下划线一般是私有方法
            this.showFlag = true;
            //初始化部分,ratingselect组件是被被不同的商品使用的,所以我们希望在点开不同的商品时,能有一样的初始化状态
            this.selectType = ALL;
            this.onlyContent = false;
            //展示界面时用到BScroll
            this.$nextTick(() => {
                if (!this.scroll) {
                    this.scroll = new BScroll(this.$refs.food, {
                        click: true // 可以被点击
                    });
                } else {
                   this.scroll.refresh();
                }
            });
        }

然后在模板中绑定data属性中的值,将其传入到ratingselect组件中,不要忘了food.ratings,food是由goods组件中传递过来的

<!--  ratings对应被点击的food的ratings-->
 <ratingselect  @increment="incrementTotal" :select-type="selectType" :only-content="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect>

此时,desc已经从默认的满意和不满意变成了推荐和吐槽,接下来在父组件foods.vue中为title和ratingselect部分编写样式

            <div class="rating">
                <h1 class="title"> 商品评价</h1>
                <!--  ratings对应被点击的food的ratings-->
                <ratingselect  @increment="incrementTotal" :select-type="selectType" :only-content="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect>
                <div class="rating-wrapper"> <!-- 评价列表-->
                </div>
            </div>

因为rating下有一条border,所以在rating下不可以设置四周的padding值,如果设置了border就撑不开整个屏幕了

    .rating //因为要在rating title下方画一条横线,所以不能用padding-left,改用title的margin代替
        padding-top: 18px
        .title
            line-height 14px
            margin-left 18px
            font-size 14px
            color rgb(7,17,27)

之后是ratingselect自身样式的编写(ratingType和switch的普通样式和点击时的样式变化,若要改变初始状态就在food.vue中的show()函数中改变,show()函数是用来被goods.vue调用并展开详情界面的)

CSS样式:

.ratingselect
    .rating-type
        padding 18px 0
        margin 0 18px //保证横线的长度
        border-1px(rgba(7,17,27,0.1))
        font-size 0
        .block //没有写文字的时候是没有被撑开的
            display inline-block
            padding 8px 12px
            margin-right 8px
            border-radius 1px
            line-height 16px
            font-size 12px
            color rgb(77,85,93)
            &.active  // block的active要设置一下
                 color #ffffff
            .count
                margin-left 2px
                font-size 8px
            &.positive
                background rgba(0,160,220,.2)
                &.active
                    background rgb(0,160,220)
            &.negative
                background  rgba(77,85,93,0.2)
                &.active
                    background  rgb(77,85,93)
    .switch
        padding 12px 18px
        line-height 24px
        border-bottom 1px solid rgba(7,17,27,0.1)
        color rgb(147,153,159)
        font-size 0
        &.on
            .icon-check_circle
                color #00c850
        .icon-check_circle
            display inline-block
            vertical-align top 
            margin-right 4px
            font-size 24px
        .text
            display inline-block
            vertical-align top
            font-size 12px       

我们这里只是定义了样式,将其应用到DOM中,我们为其添加了active类和点击按钮,这里的sType对应的是

    this.selectType = ALL;
    this.onlyContent = false;

默认为全部选中并且看所有评价

<template>
  <div class="ratingselect">
        <div class="rating-type" border-1px>
            <span class="block positive" @click="select(2,$event)" :class="{'active':sType === 2}">{{desc.all}}<span class="count">{{ratings.length}}</span> </span>
            <span class="block positive" @click="select(0,$event)" :class="{'active':sType === 0}">{{desc.positive}}<span class="count">{{positives.length}}</span></span>
            <span class="block negative" @click="select(1,$event)" :class="{'active':sType === 1}">{{desc.negative}}<span class="count">{{negatives.length}}</span></span>
        </div>
        <div @click="toggleContent($event)"  class="switch" :class="{'on':oContent}">
            <span class="icon-check_circle"></span>
            <span class="text">只看有内容的评价</span>
        </div>
  </div>
</template>

4)为ratingType(是否满意)和switch(是否查看全部评估)编写点击事件,ratingselect.vue在select()中改变了selectType的值,要将改变的值通知到父组件food.vue的show()方法,可以通过派发事件完成

父组件food.vue 将selectType和onlyContent传入子组件,与之前的方式不变

<ratingselect  @increment="incrementTotal" :select-type="selectType" :only-content="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect>

在props中接收到父组件传过来的selectType和onlyContent的值之后,在data中重新定义变量接收,以便观测值的变化(因为子组件将改变data中的值,子组件要将这些变化的值传递个父组件)

         data() {
            return {
                    sType: this.selectType,
                    oContent: this.onlyContent
                };
            }

之后,sType就替代了this.selectType,所以DOM就变成了

<template>
  <div class="ratingselect">
        <div class="rating-type" border-1px>
            <span class="block positive" @click="select(2,$event)" :class="{'active':sType === 2}">{{desc.all}}<span class="count">{{ratings.length}}</span> </span>
            <span class="block positive" @click="select(0,$event)" :class="{'active':sType === 0}">{{desc.positive}}<span class="count">{{positives.length}}</span></span>
            <span class="block negative" @click="select(1,$event)" :class="{'active':sType === 1}">{{desc.negative}}<span class="count">{{negatives.length}}</span></span>
        </div>
        <div @click="toggleContent($event)"  class="switch" :class="{'on':oContent}">
            <span class="icon-check_circle"></span>
            <span class="text">只看有内容的评价</span>
        </div>
  </div>
</template>

但是之前定义的rating-type和switch的active的条件是不变的。

之后编写rating-type和swicth切换有内容评价部分的绑定函数:

select函数中,在点击的时候就把类型123传进去,传入event是因为外层是一个betterScroll,要进行点击事件的判断,将sType的值更新之后通过emit将函数派发出去;

 methods: {
            select (type, event) { //点击的时候外层是有一个BScroll的,所以要传递event阻止默认点击
                if (!event._constructed) { //浏览器直接return掉,去掉自带click事件的点击
                    return;
                }
                //将this.selectType设置成传入的参数,而不是food传过来的初始化的值,之后样式就可以随着点击改变了
                this.sType = type;
                //派发事件通知父组件food.vue selectType的改变,将type值传出去
                 console.log('ratingselect.vue ' + type);
                //this.$emit('se-type', type); 
                this.$emit('increment', 'selectType', this.sType);
            },
            toggleContent (event) {
                if (!event._constructed) { //浏览器直接return掉,去掉自带click事件的点击
                        return;
                }
                this.oContent = !this.oContent;
                console.log('ratingselect.vue ' + this.oContent);
               // this.$emit('toggle-content', this.oContent);
                this.$emit('increment', 'onlyContent', this.oContent);
            }
        },  

回到父组件food.vue中编写监听函数,并关联组件

<ratingselect  :select-type="selectType" :only-content="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect>

 

5)统计不同评价的数量(过滤评价类型),添加positives和negitives数组,长度即为评价数量

<div class="rating-type" border-1px>
   <span class="block positive" @click="select(2,$event)" :class="{'active':sType === 2}">{{desc.all}}<span class="count">{{ratings.length}}</span> </span>
   <span class="block positive" @click="select(0,$event)" :class="{'active':sType === 0}">{{desc.positive}}<span class="count">{{positives.length}}</span></span>
   <span class="block negative" @click="select(1,$event)" :class="{'active':sType === 1}">{{desc.negative}}<span class="count">{{negatives.length}}</span></span>
 </div>
     computed: {
            positives() { //对应所有正向评价的数组
                return this.ratings.filter((rating) => {
                    return rating.rateType === POSITIVE;
                });
            },
            negatives() {
                return this.ratings.filter((rating) => {
                    return rating.rateType === NEGATIVE;
                });
            }
        },

至此,ratingselect组件开发完成,我们将其与下方的评价列表连接起来,实现联动

回到food组件在ratingselect组件的下方添加评价列表的DOM结构

vue2.0饿了么学习笔记(15)ratingselect组件的实现

 <div class="rating-wrapper"> <!-- 评价列表-->
                    <ul v-show="food.ratings && food.ratings.length">
                        <li v-show="needShow(rating.rateType, rating.text)" v-for="rating in food.ratings" :key="rating.id" class="rating-item border-1px">
                            <div class="user">
                                <span class="name">{{rating.username}}</span>
                                <img class="avatar" height="12" width="12"  :src="rating.avatar" alt="">
                            </div>
                            <div class="time">{{rating.rateTime | formatDate}}</div>
                            <p class="text">
                                <span :class="{'icon-thumb_up':rating.rateType === 0, 'icon-thumb_down':rating.rateType === 1}"></span>{{rating.text}}
                            </p>
                        </li>
                    </ul>
                    <div class="no-ratings" v-show="!food.ratings || !food.ratings.length">暂无评价</div>
                </div>

添加css样式:

 .rating-wrapper
            padding 0 18px
            .rating-item
                position relative
                padding 16px 0
                border-1px(rgba(7, 17, 27, 0.1))
                .user
                    position absolute
                    right 0
                    top 16px
                    line-height 12px
                    font-size 0
                    .name
                        display inline-block
                        vertical-align top
                        font-size 10px
                        color rgb(147,153,159)
                        margin-right 6px
                    .avatar
                        border-radius 50%
                .time
                    margin-bottom 6px
                    line-height 12px
                    font-size 10px
                    color rgb(147,153,159)
                .text
                    line-height 16px
                    font-size 12px
                    color rgb(7,17,27)
                    .icon-thumb_up, .icon-thumb_down
                        margin-right 4px
                        line-height 16px
                        font-size 12px
                    .icon-thumb_up
                        color rgb(0,160,220)
                    .icon-thumb_down
                        color rgb(147,153,159)
            .no-ratings
                padding 16px 0
                font-size 12px
                color rgb(147,153,159)

添加好样式之后,为列表的显示添加选择:

 <li v-show="needShow(rating.rateType, rating.text)" v-for="rating in food.ratings" :key="rating.id" class="rating-item border-1px">

添加needshow函数,因为我们是在子组件ratingselect中进行rating.rateType的切换,变量更改后的结果要传递到父组件中,这时用到了incrementTotal方法,对子组件更改的数值进行监听(在子组件ratingselect中使用select和toggleContent中进行emit派发),所以在切换了子组件的按钮之后,父组件就可以根据子组件的选择进行内容的切换

 select (type, event) { //点击的时候外层是有一个BScroll的,所以要传递event阻止默认点击
                if (!event._constructed) { //浏览器直接return掉,去掉自带click事件的点击
                    return;
                }
                //将this.selectType(this.sType)设置成传入的参数,而不是food传过来的初始化的值,之后样式就可以随着点击改变了
                this.sType = type;
                //派发事件通知父组件food.vue selectType的改变,将type值传出去
                 console.log('ratingselect.vue ' + type);
                //this.$emit('se-type', type); 
                this.$emit('increment', 'selectType', this.sType);
            },
            toggleContent (event) {
                if (!event._constructed) { //浏览器直接return掉,去掉自带click事件的点击
                        return;
                }
                this.oContent = !this.oContent;
                console.log('ratingselect.vue ' + this.oContent);
               // this.$emit('toggle-content', this.oContent);
                this.$emit('increment', 'onlyContent', this.oContent);
            }

  

<ratingselect  @increment="incrementTotal" :select-type="selectType" :only-content="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect>
        needShow(type, text) {
             // console.log('this.selectType: ' + this.selectType + '  type: ' + type + ' out  ' + text);
            if (this.onlyContent && !text) {
                return false;
            }
            if (this.selectType === ALL) {
                return true;
            } else {
                //console.log('this.selectType: ' + this.selectType + 'type: ' + type + ' in ' + text);
                return type === this.selectType;
            }
        },
        incrementTotal(type, data) {
            this[type] = data;
            this.$nextTick(() => { // 当我们改变数据的时候,DOM的更新是异步的
                this.scroll.refresh();
        });
      }

 

6)为其中时间的显示添加过滤器,将时间戳转化为时间字符串
 

<div class="time">{{rating.rateTime | formatDate}}</div>
 import {formatDate} from 'common/js/date.js';
export function formatDate(date, fmt) {
   if (/(y+)/.test(fmt)) {
    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
   }
  let o = {
    'M+': date.getMonth() + 1,
    'd+': date.getDate(),
    'h+': date.getHours(),
    'm+': date.getMinutes(),
    's+': date.getSeconds()
  };
  for (let k in o) {
    if (new RegExp(`(${k})`).test(fmt)) {
      let str = o[k] + '';
      fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
    }
  }
  return fmt;
}

function padLeftZero(str) {
  return ('00' + str).substr(str.length);
}

 

filters: {
      formatDate(time) {
          let date = new Date(time);
          return formatDate(date, 'yyyy-MM-dd hh:mm');
      }  
    }


8)点击是否满意和只看有评价内容时,评价列表的显示,不是ratingselect组件的内容,写在food.vue中

 

             <div class="rating-wrapper"> <!-- 评价列表-->
                    <ul v-show="food.ratings && food.ratings.length">
                        <li v-show="needShow(rating.rateType, rating.text)" v-for="rating in food.ratings" :key="rating.id" class="rating-item border-1px">
                            <div class="user">
                                <span class="name">{{rating.username}}</span>
                                <img class="avatar" height="12" width="12"  :src="rating.avatar" alt="">
                            </div>
                            <div class="time">{{rating.rateTime | formatDate}}</div>
                            <p class="text">
                                <span :class="{'icon-thumb_up':rating.rateType === 0, 'icon-thumb_down':rating.rateType === 1}"></span>{{rating.text}}
                            </p>
                        </li>
                    </ul>
                    <div class="no-ratings" v-show="!food.ratings || !food.ratings.length">暂无评价</div>
                </div>

 

利用needshow进行切换

 

     needShow(type, text) {
             // console.log('this.selectType: ' + this.selectType + '  type: ' + type + ' out  ' + text);
            if (this.onlyContent && !text) {
                return false;
            }
            if (this.selectType === ALL) {
                return true;
            } else {
                //console.log('this.selectType: ' + this.selectType + 'type: ' + type + ' in ' + text);
                return type === this.selectType;
            }
        },

 

相关标签: vue2.0饿了么

上一篇:

下一篇: