为了账号安全,请及时绑定邮箱和手机立即绑定

【饿了么】—— Vue2.0高仿饿了么核心模块&移动端Web App项目爬坑(二)

标签:
Vue.js

 前言:上一篇项目总结介绍了页面骨架的开发、header组件的开发,这一篇主要梳理:商品组件开发、商品详情页实现。项目github地址:https://github.com/66Web/ljq_eleme,欢迎Star。


goods一、商品组件开发

  • App.vue主组件传seller对象给每个路由:

<router-view :seller="seller"></router-view>

      两栏布局-flex布局手机屏幕自适应

  • 设计:无高度滚动条,高度超过视口高度就会隐藏

<div class="goods">
     <div class="menu-wrapper"></div>
     <div class="foods-wrapper"></div></div>

复制代码

.goods     display: flex
     position: absolute
     top: 174px
     bottom: 46px
     width: 100%
     overflow: hidden
     .menu-wrapper            flex: 0 0 80px
            width: 80px
            background: #f3f5f7
            .foods-wrapper                    flex: 1

复制代码

      左侧布局-菜单列表

  • 需求:文字标题可能单行,也可能多行,但都要在列表项中垂直居中

  • 小技巧:使用列表项display:table,文字标题disable:table-cell

复制代码

.menu-item    display: table
    height: 54px
    width: 56px
    line-height: 14px
    .text    display: table-cell
    width: 56px
    vertical-align: middle
    font-size: 12px

复制代码

  • 关于box-sizing:border-box; 规定两个并排的带边框的框

      右侧布局-食品列表

  • 列表嵌套:第一层遍历商品项item in goods, 第二层遍历单个商品的信息项food in item.foods

      列表滚动-better-scroll 第三方JS库

  1. goods.vue 引入:

    import BScroll from 'better-scroll';
  2. ref 属性获取dom元素:驼峰命名法 

    <div class="menu-wrapper" ref="menuWrapper"><div class="foods-wrapper" ref="foodsWrapper">
  3. better-scroll初始化:

    methods: {
        _initScroll(){          this.meunScroll=new BScroll(this.$refs.menuWrapper,{});          this.foodsScroll=new BScroll(this.$refs.foodsWrapper,{});
        }
    }
  4. 成功回调函数中调用_initScroll方法:

    this.$nextTick(()=>{     this._initScroll();
    })
  • this.$nextTick()这个方法作用是当数据被修改后使用这个方法会回调获取异步更新后的dom再render出来 

  • 如果不在下面的this.$nextTick()方法里回调这个方法,数据改变后再来计算滚动轴就会出错

      左右联动

  • 需求:滚动右侧,左侧跟着变化;点击左侧,右侧滚动到相应位置

  • 原理:依赖右侧滚动列表实时变化的Y值(纵坐标),移动到哪个区间,左侧列表就要显示哪个区间

  • 【滚动右侧时左侧相应滚动】思路&实现:

  1. 在data中定义数组用来存储不同区间的高度

    data () {    return {
          goods:[],      listHeight: []
        }
    }
  2. 为了获取高度,给food-list定义一个class--food-list-hook,不用来编写css,专门用来获取DOM元素,没有实际的效果,只是用来被js选择的

    <li v-for="item in goods" :key="item.id" class="food-list food-list-hook">
  3. 定义foodList拿到每个li,每个li是包括包括标题在内的每一类food的高度,不是单独的一种good,将_calculateHeight放在nextTick中初始化_initScroll的后面,保证其能正确计算到高度

    复制代码

    _calculateHeight() {     //food-list-hook类的添加知识为了能拿到food列表,例如,拿到的是多个类似整个粥品的区块
        let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
        let height = 0;    this.listHeight.push(height); //listHeight是一个递增的区间数组,是每个专区高度的累加
        for (let i = 0; i < foodList.length; i++) {
          let item = foodList[i];
          height += item.clientHeight;      this.listHeight.push(height);
        }
     }

    复制代码

  4. 在data中定义一个scrollY对象,用来跟踪滚动的高度 scrollY:0;在初始化betterScroll时,为右侧添加probeType--可以检测到右侧实时滚动的位置,监听scroll,将其实时滚动的位置暴露出来

    复制代码

    data () {    return {
          goods:[],
          listHeight: [],      scrollY: 0
        }
    }

    复制代码

    复制代码

    _initScroll() {          this.meunScroll=new BScroll(this.$refs.menuWrapper,{
                click: true //使better-scroll可点击,默认派发一个点击事件          });          this.foodsScroll=new BScroll(this.$refs.foodsWrapper,{
                click: true,
                probeType: 3 //BScroll滚动时,能实时告诉我们滚动的位置,类似探针的效果          });           //foodsScroll监听事件,在scroll滚动时能见位置实时暴露出来
              this.foodsScroll.on('scroll', (pos) => {            this.scrollY = Math.abs(Math.round(pos.y));//本身是个负值,取正值          })
     }

    复制代码

  5. 拿到滚动的高度和内容区的固定高度之后, 查看scrollY落在哪个区间,并返回那个区间的索引(!height2是测试最后一个区间的)其中,>= 向下的是一个闭区间,这样第一个就会高亮了

    复制代码

    computed: {
      currentIndex() { //currentIndex对应菜单栏的下标
        for (let i = 0; i < this.listHeight.length; i++) { //不要忘了加this引用
          let height1 = this.listHeight[i];
          let height2 = this.listHeight[i + 1];      //获得了一个区间的上下范围,判断scrollY落到这个区间,!height2是判断最后一个区间
          //避免i溢出,>= 向下的是一个闭区间,这样第一个就会高亮了
          if (!height2 || (this.scrollY >= height1 && this.scrollY < height2)) {        return i; //映射到第5行menu的变化      }
        }    return 0;
    }

    复制代码

  6. 拿到index之后,回到左侧的menu区,当我们遍历menu的时候,如果$index等于我们计算得到的currentIndex时,就为当前的li添加一个current样式

    <!-- 如果index等于currentIndex,就为这个li添加一个current类,改变左侧导航栏的背景颜色--><li v-for="(item,index) in goods" :key="item.id"  
        class="menu-item" :class="{'current': currentIndex === index}" 
        @click = "selectMenu($index, $event)">

    复制代码

    &.current
         position: relative
         z-index: 10
         margin-top: -1px
         background: #ffffff
         font-weight: 700
         .text
              border-none()

    复制代码

  • 【点击左侧右侧滚动】思路&实现:

  1. 在左侧菜单栏添加点击事件selectMenu, @click = "selectMenu($index, $event)",将index传进去,就可以知道点选的是哪个区域,然后利用原生DOM操作将高度滚动到相应的位置

  2. 点击左侧菜单栏的时候没有反应,因为BScroll默认阻止点击事件,所以在 _initScroll()中获取DOM对象时添加click: true,并解决PC端双点击问题,event是点击时的event

    复制代码

    selectMenu (index, event) {    if (!event._constructed) { //浏览器直接return掉,去掉自带click事件的点击
           return;
        }
        let foodList = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
        let ref = foodList[index]; //取到index对应的DOM
        this.foodsScroll.scrollToElement(ref, 300);//滚动到DOM所在位置
        //console.log(index);}

    复制代码

      购物车组件

  • 定位在视口底部: fixed布局——右侧宽度固定,左侧自适应

    <div class="content">
          <div class="content-left"></div>
          <div class="content-right"></div></div>

    复制代码

    .content
         display: flex
         background: #141d27
         .content-left          flex: 1  /*让所有弹性盒模型对象的子元素都有相同的长度,忽略它们内部的内容*/
         .content-right          flex: 0 0 105px /*flex三个参数依次表示:等分、内容缩放情况、站位空间*/
              width: 105p

    复制代码

  • display:inline-block有一个默认间隙的问题 —— 解决:父级font-size:0

  • 三种状态转换: 数据驱动Dom变化

      cartcontrol组件

  • 在设计尺寸基础上增加点击区域: padding

  • 检测数据的变化:Vue.set

  1. 引入:

    Vue import Vue from 'vue';
  2. 使用Vue.set接口:

    Vue.set(this.food, 'count', 1);
  • 按钮平移+渐隐渐现+滚动动画:

    <transition name="move">...</transition>

    复制代码

    .cart-decrease
         display: inline-block
         padding: 6px
         transform: translate3d(0, 0, 0)
         transform: rotate(0)
         &.move-enter-active, &.move-leave-active
              transition: all 0.4s linear
              transform: translate3d(0, 0, 0)
              transform: rotate(0)
         &.move-enter, &.move-leave-active
              opacity: 0
              transform: translate3d(24px, 0, 0)/*开启硬件加速,让动画更流畅*/
              transform: rotate(180deg)
         .inner
              display: inline-block
              line-height: 24px
              font-size: 24px
              color: rgb(0, 160, 220)

    复制代码

    View Code

      购物车抛物线小球动画

  • data数据中定义一个数组,存放5个小球,这5个小球可以满足的动画的运行

    复制代码

     data() {         return {
                    balls: [{ //每一个成员都用来维护当前小球的状态,初始状态都是隐藏的
                        show: false
                    },
                    { 
                        show: false
                    },
                    {  
                        show: false
                    },
                    {  
                        show: false
                    },
                    {  
                        show: false
                    }],                //添加一个变量,用来存贮已经下落的小球                dropBalls: [],
                    fold: true //购物车详情列表默认折叠          };
    }

    复制代码

  • 布局

    复制代码

    <div class="ball-container">
        <div v-for="(ball, index) in balls" :key="index">
            <div class="ball-container">
                 <div v-for="(ball, index) in balls" :key="index">
                       <transition name="drop" >
                              <div v-show="ball.show" class="ball">
                                     <div class="inner inner-hook"></div>
                               </div>
                       </transition>
                 </div> 
             </div>
        </div> </div>

    复制代码

  • :key报错问题:key值是必须唯一的,如果重复就会报错。可以把key值改为index,就可以避免这个情况

    <div v-for="(ball, index) in balls" :key="index" v-show="ball.show"></div>
  • 动画需求:小球有抛物线轨迹运动的过渡,而且发射出去就不会再回来了

  1. 动画属性只用enter,不用leave,并且小球起始点需要动态计算

    复制代码

    .ball-container
        position: fixed
        left: 32px
        bottom: 22px
        z-index: 200
        .inner
            width: 15px
            height: 15px
            border-radius: 50%
            background-color: #00A0DC
            transition: all 1s linear        &.drop-enter-active
                  transition: all 1s cubic-bezier(0.49, -0.29, 0.75, 0.41)

    复制代码

  2. CSS3 三次贝塞尔曲线(cubic-bezier):

       贝塞尔曲线通过控制曲线上的四个点(起始点、终止点以及两个相互分离的中间点)来创造、编辑图形,  绘制出一条光滑曲线并以曲线的状态来反映动画过程中速度的变化。  在线调试地址: http://cubic-bezier.com/#.17,.67,.83,.67  

  3. 动画分为两层,外层控制小球y轴方向和运动的轨道,内层控制x轴方向的运动

  4. 使用js动画钩子,vue在实现动画的时候提供了几个javascript钩子,可配合css动画一起使用,也可单独使用

    复制代码

     methods: {
            dropMove(el) {        //    console.log(el)
               for(let i=0; i<this.balls.length; i++) {
                   let ball = this.balls[i];               if(!ball.show) {
                      ball.show = true;
                      ball.el = el;                  this.dropBalls.push(ball);                  return;
                   }
               }
            },
            beforeEnter(el, done) {
                let count = this.balls.length;            while (count--) {
                    let ball = this.balls[count];                if(ball.show) {                    
                        let rect = ball.el.getBoundingClientRect();//返回元素的大小及其相对于视口的位置
                        let x = rect.left - 32  //ball-container left:32
                        let y = -(window.innerHeight - rect.top -22);
                        el.style.display = '';
                        el.style.transform = `translate3d(0,${y}px,0)`;//外层元素纵向移动
                        el.style.webkitTransform = `translate3d(0,${y}px,0)`;
                        let inner = el.getElementsByClassName('inner-hook')[0];//内层元素横向移动
                        inner.style.webkitTransform = `translate3d(${x}px, 0, 0)`;
                        inner.style.transform = `translate3d(${x}px, 0, 0)`;                    // console.log(el);                }
                }
            },
            dropEnter(el, done) {            /*手动取到offsetHeight, 触发浏览器重绘*/
                let rf = el.offsetHeight;            this.$nextTick(() => {  //样式重置回来
                    el.style.webkitTransform = 'translate3d(0, 0, 0)'// 设置小球掉落后最终的位置
                    el.style.transform = 'translate3d(0, 0, 0)'
                    let inner = el.getElementsByClassName('inner-hook')[0]
                    inner.style.webkitTransform = 'translate3d(0, 0, 0)'
                    inner.style.transform = 'translate3d(0, 0, 0)'
                    el.addEventListener('transitionend', done) // Vue为了知道过渡的完成,必须设置相应的事件监听器。它可以是transitionend或 animationend            })            // console.log(el);        },
            afterEnter(el) {
                let ball = this.dropBalls.shift();            if(ball) {
                    ball.show = false;
                    el.style.display = 'none';
                } 
                // console.log(el);
            }

    复制代码

    View Code

    每个钩子都有一个参数el: 当前执行transition动画的DOM对象当我们点击触发一个过渡的时候,我们在beforeEnter里先拿到当前元素的偏移位置,然后给过渡元素设置其起始位置,在enter里需要重新触发下浏览器的重绘,然后在下一帧重新设置元素的结束位置,这时就会产生过渡效果,在过渡完成后我们将当前元素隐藏即可。关于tansition实践详解博客:【重点突破】—— Vue2.0 transition 动画Demo实践填坑

 

二、food商品详情页实现

 

food

 

       商品详情页实现-food.vue组件

  1. 设计时:父组件可以调用子组件方法,子组件不能调用父组件方法

  2. 常见命名习惯:如果是父组件调用的方法,命名如show();如果是组件私有方法,命名会在前面加_, 如_show()

  3. 详情页从右往左飞入动画:

    复制代码

    .food
        position: fixed
        left: 0
        top: 0
        bottom: 48px
        z-index: 30
        width: 100%
        background: #ffffff
        transform: translate3d(0, 0, 0)    &.move-enter-active, &.move-leave-active
              transition: all 0.2s linear
              transform: translate3d(0, 0, 0)    &.move-enter, &.move-leave-active
              opacity: 0
              transform: translate3d(100%, 0, 0)

    复制代码

  • 坑:头图显示是异步加载的,不能写死宽高,因为视口是自适应的,但不设置高度,页面内容会有图片撑开高度的抖动过程。

  • 解决:一开始给<img>限制宽高,设为容器的100%

    复制代码

    .image-header
        position: relative    width: 100%
        height: 0    padding-top: 100% // 百分比是相对于盒子的宽度来计算的,看起来就像是一个正方形
        img 
           position: absolute
           top: 0
           left: 0       width: 100%
           height: 100%

    复制代码

  • 坑:“加入购物车”一点击就会display:none,这样执行better-scroll动画的时候,找ball.el.getBoundingClientRect()会找不到,小球就不能找到正确的初始位置。

  • 解决:给消失的过程加一个opcity的transition动画,时长0.2s,这样就不会立刻消失

    &.buy-enter-active, &.buy-leave-active 
         transition: all 0.2s
         opacity: 1 
    &.buy-enter, &.buy-leave-active 
         opacity: 0
  • 坑:菜单列表的“+”“-”按钮,每次点击都会触发详情页显示,这是因为点击事件被穿透了。

  • 解决:给cart-control.vue组件中的“+”“-”按钮的点击事件,都添加阻止事件冒泡

    @click.stop.prevent="decreaseCart($event)"
    @click.stop.prevent="addCart($event)"
  • 同理,详情页的“加入购物车”按钮,最好也加上阻止事件冒泡

    @click.stop.prevent="addFirst($event)"

       split组件实现 

  • 一个很简单的样式模板组件,分隔区

    复制代码

    <template>
      <div class="split"></div></template>
     <script type="text/ecmascript-6">
      export default {};</script>
     <style lang="stylus" rel="stylesheet/stylus">
        .split
            width 100%
            height 16px
            border-top: 1px solid rgba(1, 17, 27, 0.1);
            border-bottom: 1px solid rgba(1, 17, 27, 0.1);
            background: #f3f5f7</style>

    复制代码

    View Code

       商品评价 - ratingselect 组件

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

    <v-ratingselect :select-type="selectType" :only-content="onlyContent"
                    :desc="desc" :ratings="food.ratings" 
                    @increment="incrementTotal"></v-ratingselect>
  2. props的值如下:首先是有一个变量【only-content】是否显示只看内容,还有一个【select-type】控制选择的类型,还有要维护一个【ratings】所有评价的数据,因为这里有一个评价数量;还要去维护一个【desc】描述,是(全部,推荐,吐槽)还是(全部,满意,不满意),按照以上标准设置外部组件传入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: '不满意'
                        };
                    }
                }
    },

    复制代码

  3. 在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>

    复制代码

    View Code

  4. 在food.vue(商品详情页)的data中挂载对上述对象的跟踪,并对其进行初始化

    复制代码

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

    复制代码

  5. 需求:在切换不同商品的时候能有相同的初始化状态 —— 定义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();
                    }
           });
    }

    复制代码

  • 两种样式:公用样式、特殊样式

    复制代码

    .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

    复制代码

    View Code

  1. 被选中 

    :class="{'active':selectType===2}"
  2. 居中对齐:

    display: inline-block
    vertical-align: top
  3. 因为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)

    复制代码

  • 坑:[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop’s value. Prop being mutated: “gems” (found in component: )

  • 这是因为在vue2.0中,直接修改prop是被视作反模式的。由于在新的渲染机制中,每当父组件重新渲染时,子组件都会被覆盖,所以应该把props看做是不可变对象。

  • 解决:在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>

    复制代码

    View Code

  • 编写rating-type和swicth切换有内容评价部分的绑定函数:select(type, event) —— 在点击的时候就把类型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('increment', 'selectType', this.sType);
          },
          toggleContent (event) {          if (!event._constructed) { //浏览器直接return掉,去掉自带click事件的点击
                    return;
              }          this.oContent = !this.oContent;
              console.log('ratingselect.vue ' + this.oContent);          
              this.$emit('increment', 'onlyContent', this.oContent);
          }
    }

    复制代码

  • 统计不同评价的数量(过滤评价类型),添加计算属性 -- 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;
                });
        }
    }

    复制代码

       评价列表

  • 切换子组件的按钮之后,父组件就可以根据子组件的选择进行内容的切换

  1. 为列表的显示添加选择

     <li v-show="needShow(rating.rateType, rating.text)" //v-show特殊用法:绑定函数返回值
         v-for="rating in food.ratings" 
         :key="rating.id" 
         class="rating-item border-1px">
  2. 定义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;
         }
    }

    复制代码

  3. ratingselect.vue  中进行rating.rateType的切换,变量更改后的结果要传递到父组件中,这时用到了incrementTotal() 

    incrementTotal(type, data) {  // 对子组件更改的数值进行监听
           this[type] = data;       this.$nextTick(() => { // 当我们改变数据的时候,DOM的更新是异步的
                    this.scroll.refresh();
           });
    }
  4. 触发事件increment:  在子组件ratingselect中使用select和toggleContent中进行emit派发

  • 时间的显示添加过滤器,将时间戳转化为时间字符串

    <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);
    }

    复制代码

    View Code

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

原文出处:https://www.cnblogs.com/ljq66/p/10006577.html  

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消