vue2.0 饿了么学习笔记(13)商品的详情页-----子组件改变父组件的值

开发food.vue组件实现商品的详情页展示,首先写布局,组件正常情况下是隐藏的,要添加一个v-show的条件,food组件要维护的是goods组件中循环的每一个food的详情页,所以我们用props接收父组件goods传过来的food数据

<transition name="fade"> <!--  为food界面的展开添加动画-->
    <div v-show="showFlag" class="food" ref="food">
    </div>
  </transition>
 props: { //接收传入的food,此时的food是被选中点开详情页的那个food
        food: {
            type: Object
        }
    }

food首先是根据屏幕做定位的,他的position被设置为fixed

  .food
    position fixed
    left: 0
    top: 0
    bottom 48px // 底部留出购物车的空间
    z-index 30 // 小于底部的购物车,也要小于购物车弹出的mask图层
    width 100%
    background #ffffff

写好food组件的基本样式,便可以回到food组件的父组件goods组件中,在循环foods的时候添加一个点击事件,维护一个selectedFood值,点击了其中某一个food就将food的值传入selectedFood中,在通过selectedFood传到food组件中,selectedFood在data中进行维护

首先在模板中添加food组件

 <shopcart ref="shopcart" :select-foods="selectFoods" :delivery-price="seller.deliveryPrice" :min-price="seller.minPrice"></shopcart>
    <food :food="selectedFood" ref="food"></food> <!-- data中的selectedFood传递进来-->

但是我没现在还不知道选中的selectedFood是哪一个,首先在data中定义这个被选中的food为空

data() {
  return {
    selectedFood: {} //一开始是空对象,点击food的时候再存入,在li(food)中添加点击事件
  };
}

在循环food的时候添加点击事件,将选中的food传递到selectedFood中

 <li v-for="food in item.foods" :key="food.id" class="food-item"  border-1px @click="selectFood(food,$event)">

编写selectFood事件,将food传过去,不要忘了将$event传递进去

  selectFood(food, event) { //@click函数
    if (!event._constructed) { //浏览器直接return掉,去掉自带click事件的点击
        return;
     }
      this.selectedFood = food;
      //调用子组件的show()方法展开food组件
      this.$refs.food.show();
  }
 }

这样我们就取到了被选中的selectedFood,将其传达组件food中, :food = selectedFood;

这样,food组件就拿到了被点选的这个food的相关数据,之后,我们回到子组件food中为其添加show方法,其中showFlag是表示商品详情页图层是否会出现的布尔值,它是一直在变化的量,不要忘记将其放入到data中进行观测

 data () {
        return {
            showFlag: false, // 默认商品详情页不展开
    }

 

 show() { //可以被父组件调用到,方法前加下划线一般是私有方法
            this.showFlag = true;
        }

在子组件food中写好了show方法,那么我们在父组件goods中food的点击函数中就可以调用子组件food的show方法将其展开,俺么怎么在父组件中调用子组件的方法呢,在子组件food中添加一个ref即可

selectFood(food, event) { //@click函数
    if (!event._constructed) { //浏览器直接return掉,去掉自带click事件的点击
        return;
     }
      this.selectedFood = food;
      //调用子组件的show()方法展开food组件
      this.$refs.food.show();
  }
 }

这样点击food就可出现food的详情页了,之后,我们在为详情页的展开设置一个从右向左飞入的动画

 &.fade-enter-active, &.fade-leave-active
        transition all .3s linear //为动画过程添加缓动和最终状态
        transform translate3d(0,0,0)
    &.fade-enter, &.fade-leave-active
        opacity 0
        transform translate3d(100%, 0, 0) //动画x轴方向移动

到这里我们实现了点击,动画并且拿到了数据,现在我们为food组件填充内容,DOM结构如下

       <div class="food-content">
            <div class="image-header">
                <img :src="food.image" alt="">
                <div class="back" @click="hide">
                    <i class="icon-arrow_lift"></i>
                </div>
            </div>
            <div class="content">
                ...
        </div>

图片的高度是要跟屏幕的宽度一样的,屏幕的宽度不定,但是如果不设定图片的高度,页面的加载会出现抖动的过程,因为页面在一开始加载的时候是没有高度的,怎样优化这个过程(padding-top 100%)

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

接下来添加返回按钮:

  <div class="back" @click="hide">
      <i class="icon-arrow_lift"></i>
  </div>

CSS代码如下:

        .back
            position absolute
            top 10px
            left 0
            .icon-arrow_lift
                display block 
                padding 10px //图片不要变大,但是点击区域会扩大
                font-size 20px
                color #ffffff

实现返回按钮,添加点击事件

        hide() {
            this.showFlag = false;
        }

接下来实现

先写一下布局和样式

            <div class="content">
                <h1 class="title">{{food.name}}</h1>
                <div class="detail">
                    <span class="sell-count">月售{{food.sellCount}}份</span>
                    <span class="rating">好评率{{food.rating}}%</span>
                </div>
                <div class="price"> 
                    <span class="now">¥{{food.price}}</span>
                    <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                </div>
                购物车样式
             </div>
.content
        padding 18px
        position relative
        .title
            line-height 14px
            margin-bottom 8px
            font-size 14px
            font-weight 700px
            color rgb(7,17,27)
        .detail
            margin-bottom 18px
            height 10px
            line-height 10px
            font-size 0 // 因为后面两个span是inline-block布局
            .sell-count, .rating
                font-size 10px
                color rgb(147,153,159)
            .sell-count
                margin-right 12px
        .price
            line-height 24px
            font-weight: 700
            .now
              margin-right 8px
              font-size 14px
              color rgb(240,20,20)
            .old 
               text-decoration line-through
               color rgb(147,153,159)
               font-size 10px
        .cartcontrol-wrapper
            position absolute
            right 12px
            bottom 12px

 

页面有可能超过手机高度,这里需要引入一个betterScroll,父层绑定一个betterScroll,有一个固定的高度,内层海底一个内容层,当内层的高度大于外层的固定高度时,就会出现滚动

<transition name="fade"> <!--  为food界面的展开添加动画-->
    <div v-show="showFlag" class="food" ref="food">
    </div>
</transiton>

所以在界面展示的时候,对betterScroll进行初始化,即在show函数中进行初始化

    show() { //可以被父组件调用到,方法前加下划线一般是私有方法
            this.showFlag = true;
            //展示界面时用到BScroll
            this.$nextTick(() => {
                if (!this.scroll) {
                    this.scroll = new BScroll(this.$refs.food, {
                        click: true // 可以被点击
                    });
                } else {
                   this.scroll.refresh();
                }
            });
        }

接下来实现右侧的购物车按钮:原来是一个加入购物车的按钮,点击之后,会出现加减号组件,别忘了引入 food

 <div class="cartcontrol-wrapper"> <!-- 加减号组件cartcontrol -->
                    <cartcontrol :food="food"></cartcontrol>
                </div>
                <transition name="fade"> 
                    <div @click.stop.prevent="addFirst" class="buy" v-show="food.count === 0 || !food.count">
                        加入购物车    
                    </div> <!-- 这两种情况下加入购物车都会显示,否则就会隐藏-->
                </transition>

添加css样式

        .cartcontrol-wrapper
            position absolute
            right 12px
            bottom 12px
        .buy
            position absolute
            right 18px
            bottom 18px
            z-index 10 // 要盖住加减号组件
            height 24px
            line-height 24px
            padding 0 12px
            box-sizing border-box
            font-size 10px
            border-radius 10px
            color #ffffff
            background rgb(0,160,220)
            &.fade-enter-active, &.fade-leave-active
                transition all 0.2s 
                opacity 1
            &.fade-enter, &fade-leave-active
                opacity 0

设计好样式之后,为添加购物车按钮

  <div @click.stop.prevent="addFirst" class="buy" v-show="food.count === 0 || !food.count">
                        加入购物车    
                    </div> <!-- 这两种情况下加入购物车都会显示,否则就会隐藏-->
  addFirst(event) { // 默认的参数是event,点击按钮的时候添加的是第一个商品
            if (!event._constructed) { //浏览器直接return掉,去掉自带click事件的点击
                return;
            }
            //第一次的时候el消失,找不到小球发出的其实位置,所以第一次小球动画消失
            //解决办法就是将购物车的消失做成一个动画,而不是立马display:none
            //添加的时候添加小球动画,派发事件,之后goods.vue就可以监听到cart-add,并传递给shopcart.vue
            this.$emit('cart-add', event.target);
            Vue.set(this.food, 'count', 1); //初始化this.food.count = 1;
        }

点击购物车按钮立马消失的话会使getBoundingReact函数找不到位置,所以为其添加一个动画transition

<transition name="fade"> 
                    <div @click.stop.prevent="addFirst" class="buy" v-show="food.count === 0 || !food.count">
                        加入购物车    
                    </div> <!-- 这两种情况下加入购物车都会显示,否则就会隐藏-->
                </transition>

点击主页面的加减号组件时,加减号所在的good的详情页也会展开,所以要对cartcontrol组件进行阻止冒泡的限定,所以回到cartcontrol组件

    <div class="cart-decrease" v-show="food.count>0" @click.stop.prevent="decreaseCart">
   <div class="cart-add icon-add_circle" @click.stop.prevent="addCart"></div> <!-- 加号图标 -->

将split分割写成一个组件

有上下的一个boder,有高度和背景色:

<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>

将split引入到food组件,并注册即可

<split v-show="food.info"></split>

之后添加商品详情,但是有的food是没有商品详情的,所以要在这里做一个v-show的判断,包括split组件

            <split v-show="food.info"></split>
            <div class="info" v-show="food.info"> <!-- 并不是所有的商品都有info的 -->
                <h1 class="title">商品信息</h1>
                <p class="text">{{food.info}}</p>
            </div>
            <split></split>

接下来写商品介绍,即info部分的样式

    .info
        padding 18px
        .title
            line-height 14px
            margin-bottom 16px
            font-size 14px
            color rgb(7,17,27)
        .text
            line-height 24px
            padding 0 8px
            font-size 12px
            color rgb(77,85,93)

之后就是商品的评价部分了,ratings

 

 

 

 

 

 

 

 

Vue1.0升级至2.0之后 prop的.sync被去除 因此直接在子组件修改父组件的值是会报错的 目的是为了阻止子组件影响父组件的数据

1)首先将父组件的数据传递到子组件中,data :绑定

food.vue(父组件中)

     data () {
        return {
            showFlag: false,
            selectType: ALL,
            onlyContent: true, //先设置组件一开始显示有内容的评价
            desc: { //desc做了改变
                all: '全部',
                positive: '推荐',
                negative: '吐槽' 
            }
        };
    },
<ratingselect :select-type="selectType" :only-content="onlyContent" :desc="desc" :ratings="food.ratings"></ratingselect>

2)子组件接收数据,修改数据,然后设置派发事件将修改后的数据派发的父组件

ratingSelect.vue(子组件中),在子组件中除了用props接收父子间的数据之外,还要在data中定义两个变量,将props中接收到的值赋值给这两个新变量,之后在子组件中改变的都是这两个新的变量

    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: '不满意'
                    };
                }
            }
        },
        data() {
            return {
                    sType: this.selectType,
                    oContent: this.onlyContent
                }
            },
 
 <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">47</span> </span>
            <span class="block positive" @click="select(0,$event)" :class="{'active':sType === 0}">{{desc.positive}}<span class="count">40</span></span>
            <span class="block negative" @click="select(1,$event)" :class="{'active':sType === 1}">{{desc.negative}}<span class="count">47</span></span>
        </div>
        <div @click="toggleContent($event)"  class="switch" :class="{'on':oContent}">
            <span class="icon-check_circle"></span>
            <span class="text">只看有内容的评价</span>
        </div>
  </div>

 

 

在click事件中改变自定义变量的值,并利用$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); //selectType要跟food.vue中data定义的同名
              },
            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);//onlyContent要跟food.vue中data定义的同名
            }
        },  

3)父组件food.vue监听函数,要写在methods中

        incrementTotal(type, data) {
            this[type] = data;
            this.$nextTick(() => {
                this.scroll.refresh();
        });

 

在子组件中绑定函数 @increment="incrementTotal"

 

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

4)实现点击不同选项时评价列表的刷新,在父组件food.vue中添加

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

并将needShow()函数添加在DOM中li中的v-show条件下

                <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>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值