(十三)购物车详情页显示

当我们在goods页面中,将商品添加到购物车的时候,购物车logo上方会显示添加的商品数,如下图所示


当购物车的商品数大于0的时候,点击底部的<shop-cart>部位,会触发显示添加到购物车的商品详情,如下图所示


当点击“清空”按钮的时候,购物车中的商品会删除掉,当点击“-”或者“+”按钮的时候,购物车的右上角的数字会减少或者增加,当点击“去结算”按钮的时候,会跳转到结算的界面。

下面讲讲如何实现上述功能的具体步骤

1、首先在shopCart.vue中书写购物车详情shopCartList的html

  <!--shopCartList的出现与隐藏有个动画的过程-->
      <transition name="fold">
        <!--listShow()在computed中定义-->
        <!--当this.totalCount即购物车中的数量大于0的时候,初始化better-scroll实例-->
        <!--从而当shopCartList中的内容高度大于规定高度时可以滚动-->
        <div class="shopCartList" v-show="listShow">
          <div class="listHeader">
            <h1 class="title">购物车</h1>
            <!--empty()在methods中定义,清空购物车中的商品-->
            <span class="empty" @click="empty">清空</span>
          </div>
          <div class="listContent" ref="listContent">
            <ul>
              <li class="food" v-for="food in selectFoods">
                <span class="name">{{food.name}}</span>
                <div class="price">
                  <span>¥{{food.price * food.count}}</span>
                </div>
                <div class="cartControlWrapper">
                  <!--add是在组件cart-control中通过this.$emit()传递过来的事件名-->
                  <cart-control @add="addFood" :food="food"></cart-control>
                </div>
              </li>
            </ul>
          </div>
        </div>
      </transition>

2、然后,在shopCart.vue中书写购物车详情shopCartList的css

.shopCartList
      position:absolute
      left:0
      top:0
      z-index: -1
      width:100%
      transform: translate3d(0, -100%, 0)
      &.fold-enter-active,&.fold-leave-active
        transiton:all 0.5s
      &.fold-enter,&.fold-leave-active
        transform:translate3d(0,0,0)
      .listHeader
        height:40px
        line-height:40px
        padding:0 18px
        background: #f3f5f7
        border-bottom:1px solid rgba(7,17,27,0.1)
        .title
          float:left
          font-size:14px
          color:rgb(7,17,27)
        .empty
          float:right
          font-size:12px
          color:rgb(0,160,220)
      .listContent
        padding: 0 18px
        max-height:217px
        over-flow:hidden
        background:#fff
        .food
          position:relative
          paddign:12px 0
          box-sizing:border-box
          border-1px(rgba(7,17,27,0.1))
          .name
            line-height:24px
            font-size:14px
            color:rgb(7,17,27)
          .price
            position:absolute
            right:90px
            bottom:1px
            line-height:24px
            font-size:14px
            font-weight:700
            color:rgb(240,20,20)
          .cartControlWrapper
            position: absolute
            right:0
            bottom:-5px

3、接着,在shopCart.vue中书写购物车详情的js

1)当购物车中有商品的时候,点击底部的shopCart.vue中的任意一个地方的时候,会弹出一个shopCartList,如下图所示


那么就需要在这个底部的容器里面定义一个点击事件,即toggleList(),其主要负责将在data()里面定义的变量fold的状态取反,即每次点击.content部分的区域,fold的值是true和false交替变化

fold在data()中的定义

fold:true //默认下该列表是折叠的

<template>中的定义如下

<div class="content" @click="toggleList">

在<script>中定义的toggleList()事件如下:

        //实现.content的展开与折叠,也就是设置fold的值false和true的交替变化
        toggleList(){
          if(!this.totalCount){
            //购物车还没有任何food
            return;
          }
          //更改fold的状态
          this.fold = !this.fold;
        }

然后,在.shopCartList中定义v-show='listShow'        

        <!--listShow()在computed中定义-->
        <!--当this.totalCount即购物车中的数量大于0的时候,初始化better-scroll实例-->
        <!--从而当shopCartList中的内容高度大于规定高度时可以滚动-->
        <div class="shopCartList" v-show="listShow">

函数listShow()是在computed中定义的,当this.fold的值发生变化的时候,就会触发函数listShow()的执行

        //是否展开.shopCartList的内容
        //主要有两个功能:设置该区域v-show的值
        //以及初始化better-scroll实例(.listContent),从而当内容高度大于规定高度的时候,该区域内容可以滚动
        listShow(){
          if(!this.totalCount>0){
              //购物车还没有添加任何商品
              this.fold = true;
              return false;
          }
          let show = !this.fold;
          if(show){
            //show为true的时候,才去初始化content-list的better-scroll
            //异步执行,需要等到DOM更新完在初始化better-scroll
            this.$nextTick(()=>{
              //由于.listContent的高度是不断变化的,所以需要进行判断处理
              // this.scroll不存在,则需要创建
              //this.scroll存在了,但是.listContent的高度发生变化
              //需要重新计算better-scroll,当DOM结构发生变化的时候务必要调用refresh(),确保滚动效果
              //正常
              if(!this.scroll){
                this.scroll = new BScroll(this.$refs.listContent,{
                  click:true
                });
              }else{
                //存在,则直接调用better-scroll中的接口函数refresh()
                this.scroll.refresh();
              }

            });
          }
          return show;
        }
      },

2)点击“清空”按钮的时候,.shopCartList会消失

在“清空”按钮的DOM上定义点击事件,如下面所示:

            <!--empty()在methods中定义,清空购物车中的商品-->
            <span class="empty" @click="empty">清空</span>

点击事件empty()在methods中定义:

      //清空.foodList里面选中的food,就是把food.count设为0
        empty(){
          //对selectFoods中的每一个food进行遍历
          this.selectFoods.forEach((food)=>{
            food.count = 0;
          });
        },

3)当点击<cart-control>中的“+”或者“-”的时候,商品数量会自加或者会自减

                  <!--add是在组件cart-control中通过this.$emit()传递过来的事件名-->
                  <!--addFood()在methods中定义,主要是将子组件传过来的对象-->
                  <!--传给drop(target)-->
                  <cart-control @add="addFood" :food="food"></cart-control>

在methods中addFood(target)定义如下

        addFood(target) {
          //将子组件传过来的对象target,传给在shopCart.vue中定义的函数drop(target)
          this.drop(target);
        }

4)点击“去结算”按钮会跳转到结算界面,这里这是写了一个弹窗

在<template>中的定义 如下,注意智力定义的点击事件pay(),要防止冒泡

      <!--修饰符.stop/.prevent阻止冒泡事件,防止触发toggleList()-->
        <!--.stop调用event.stopPropagation()-->
        <!--.prevent调用event.preventDefault()-->
        <div class="content-right" @click.stop.prevent="pay">
          <!--payClass在computed里面定义,当价格总数>最小价格的时候,最右边的背景呈高亮状态-->
          <div class="pay" :class="payClass">
            {{payDesc}}
          </div>
        </div>

pay()在methods中的定义 如下:

      //去结算
        pay(){
          if(this.totalPrice < this.minPrice){
            return;
          }
          window.alert(`支付${this.totalPrice}元`);
        }

5)然后,在弹出的.shopCartList中,有一个半透明的背景层,点击他的时候,会隐藏 掉.shopCartList和.listMask,当购物车有商品的时候,这个半透明层才会出现,所以用了v-show='listShow',同上面的定义

在<template>中的定义如下:

    <transition name="fade">
      <div class="listMask" v-show="listShow" @click="hideList"></div>
    </transition>

在<style>中的定义如下:

.listMask
    position:fixed
    left:0
    top:0
    height:100%
    width:100%
    z-index:40
    opacity:1
    background: rgba(7, 17, 27, 0.6)
    &.fade-enter-active,&.fade-leave-active
      transition:all 0.4s
    &.fade-enter,&.fade-leave-active
      opacity:0
      background:rgba(7,17,27,0)

事件hideList()在methods中定义 如下:

        //当.shopCartList展开的时候,点击半透明的背景层的时候,可以将.shopCartList隐藏掉
        hideList(){
          this.fold = true;
        }

以上大概就是实现购物车详情页面的显示的操作

完整代码如下:

<template>

<template>
  <div>
    <div class="shopCart">
      <div class="content" @click="toggleList">
        <div class="content-left">
          <div class="logo-wrapper">
            <!--totalCount在computed中定义,购物车里面的数量totalCount大于0,则-->
            <!--购物车背景以及logo呈高亮状态-->
            <div class="logo" :class="{'highlight':totalCount>0}">
              <span class="icon-shopping_cart" :class="{'highlight':totalCount>0}"></span>
            </div>
            <!--logo右上角的数字,当totalCount<=0,则不显示-->
            <div class="num" v-show="totalCount>0">{{totalCount}}</div>
          </div>
          <!--totalPrice在computed里面计算,totalPrice>0则价格总数高亮-->
          <div class="price" :class="{'highlight':totalPrice>0}">¥{{totalPrice}}</div>
          <div class="desc">另需配送费¥{{deliveryPrice}}元</div>
        </div>
        <!--修饰符.stop/.prevent阻止冒泡事件,防止触发toggleList()-->
        <!--.stop调用event.stopPropagation()-->
        <!--.prevent调用event.preventDefault()-->
        <div class="content-right" @click.stop.prevent="pay">
          <!--payClass在computed里面定义,当价格总数>最小价格的时候,最右边的背景呈高亮状态-->
          <div class="pay" :class="payClass">
            {{payDesc}}
          </div>
        </div>
      </div>
      <!--定义小小球-->
      <div class="ball-container">
        <div v-for="ball in balls">
          <transition name="drop" @before-enter="beforeDrop" @enter="dropping" @after-enter="afterDrop">
            <div class="ball" v-show="ball.show">
              <!--.inner是一个小球-->
              <div class="inner inner-hook"></div>
            </div>
          </transition>
        </div>
      </div>
      <!--shopCartList的出现与隐藏有个动画的过程-->
      <transition name="fold">
        <!--listShow()在computed中定义-->
        <!--当this.totalCount即购物车中的数量大于0的时候,初始化better-scroll实例-->
        <!--从而当shopCartList中的内容高度大于规定高度时可以滚动-->
        <div class="shopCartList" v-show="listShow">
          <div class="listHeader">
            <h1 class="title">购物车</h1>
            <!--empty()在methods中定义,清空购物车中的商品-->
            <span class="empty" @click="empty">清空</span>
          </div>
          <div class="listContent" ref="listContent">
            <ul>
              <li class="food" v-for="food in selectFoods">
                <span class="name">{{food.name}}</span>
                <div class="price">
                  <span>¥{{food.price * food.count}}</span>
                </div>
                <div class="cartControlWrapper">
                  <!--add是在组件cart-control中通过this.$emit()传递过来的事件名-->
                  <!--addFood()在methods中定义,主要是将子组件传过来的对象-->
                  <!--传给drop(target)-->
                  <cart-control @add="addFood" :food="food"></cart-control>
                </div>
              </li>
            </ul>
          </div>
        </div>
      </transition>
    </div>
    <transition name="fade">
      <div class="listMask" v-show="listShow" @click="hideList"></div>
    </transition>
  </div>
</template>

<script>

<script>
    import cartControl from '../cartControl/cartControl';
    import BScroll from 'better-scroll';
    export default {
      components:{
        'cart-control':cartControl
      },
      props:{
        //从父组件中传递过来的数据是一个数组,每个元素是一个对象,对象属性有price,count
        //分别对应每种食品的单价以及这种商品加进购物车的数量
        selectFoods:{//矩阵里面存放着每一个被选中的food
          type:Array,
          default(){  //矩阵类型的默认值是返回一个空的数组
            return [

            ];
          }
        },
        deliveryPrice:{
          type:Number,
          default:0
        },
        minPrice:{
          type:Number,
          default:0
        }
      },
      data(){
        return {
          //维护每个小球的状态,因为transition只对v-if  v-show  v-for有过渡效果
          //所以这里定义了show,在<template>中采用v-show指令
          balls:[
            {
              show: false
            },
            {
              show:false
            },
            {
              show:false
            },
            {
              show:false
            },
            {
              show:false
            }
          ],
          dropBalls:[],  //存放下落的小球
          fold:true    //表示选中的food的列表是折叠还是打开
        };
      },
      computed:{
        //计算加入购物车所有商品的价格
        totalPrice(){
          let total = 0;
          this.selectFoods.forEach((food) => {
            total += food.price * food.count;
          });
          return total;
        },
        // 计算所有选择商品的数量,用于在logo上面显示所选择商品数量,并且数量>0的时候购物车高亮
        // 并且购物车logo高亮
        totalCount(){
          let count = 0;
          this.selectFoods.forEach((food) => {
            count += food.count;
          });
          return count;
        },
        //最右边的20元起送的一个描述,要有一个逻辑的思维
        payDesc(){
          if( this.totalPrice === 0 ){
            return `¥${this.minPrice}元起送`;
          }else if( this.totalPrice < this.minPrice ){
            let diff = this.minPrice - this.totalPrice;
            return `还差¥${diff}元起送`;
          }else{
            return '去结算';
          }
        },
        //右边文字的样式类
        payClass(){
          if( this.totalPrice < this.minPrice ){
            return 'not-enough';
          }else{
            return 'enough';
          }
        },
        //是否展开选中food的list
        //主要有两个功能:设置该区域v-show的值
        //以及初始化better-scroll实例,从而当内容高度大于规定高度的时候,该区域内容可以滚动
        listShow(){
          if(!this.totalCount>0){
              //购物车还没有添加任何商品
              this.fold = true;
              return false;
          }
          let show = !this.fold;
          if(show){
            /ow为true的时候,才去初始化content-list的better-scroll
            //异步执行,需要等到DOM更新完在初始化better-scroll
            this.$nextTick(()=>{
              //由于.listContent的高度是不断变化的,所以需要进行判断处理
              // this.scroll不存在,则需要创建
              //this.scroll存在了,但是.listContent的高度发生变化
              //需要重新计算better-scroll,当DOM结构发生变化的时候务必要调用refresh(),确保滚动效果
              //正常
              if(!this.scroll){
                this.scroll = new BScroll(this.$refs.listContent,{
                  click:true
                });
              }else{
                //存在,则直接调用better-scroll中的接口函数refresh()
                this.scroll.refresh();
              }

            });
          }
          return show;
        }
      },
            methods:{
        drop(el){
          //对球进行遍历
          let len = this.balls.length;
          for( let i=0;i<len;i++ ){
            let ball = this.balls[i];
            if( !ball.show ){
              ball.show = true;
              ball.el = el;
              this.dropBalls.push(ball); //将下落的小球放进来
              return;
            }
          }
        },
        addFood(target) {
          //将子组件传过来的对象target,传给在shopCart.vue中定义的函数drop(target)
          this.drop(target);
        },
        beforeDrop(el){
           //将所有设为true的小球找到
          // 遍历所有的小球
          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; //左下角购物车和右侧点击的“+”的水平距离
              let y = -(window.innerHeight - rect.top -22);//竖直方向的距离差
              //外层做一个纵向的变化
              el.style.display = '';
              el.style.webkitTransform = `translate3d(0,${y}px,0)`;
              el.style.transform = `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)`;
            }
          }
        },
        dropping(el,done){
          let rf = el.offsetHeight;
          this.$nextTick(()=>{
            //当下降的时候,重写外部和内部的translate3d()
            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)
          });
        },
        afterDrop(el){
          //当降落完一个ball,就将该ball从balls数组中取出来
          let ball = this.dropBalls.shift();
          if(ball){
            //又可以利用该ball
            ball.show = false;
            el.style.display = 'none';
          }
        },
        //实现.content的展开与折叠
        toggleList(){
          if(!this.totalCount){
            //购物车还没有任何food
            return;
          }
          //更改fold的状态
          this.fold = !this.fold;
        },
        //清空.foodList里面选中的food,就是把food.count设为0
        empty(){
          //对selectFoods中的每一个food进行遍历
          this.selectFoods.forEach((food)=>{
            food.count = 0;
          });
        },
        //当.shopCartList展开的时候,点击半透明的背景层的时候,可以将.shopCartList隐藏掉
        hideList(){
          this.fold = true;
        },
        //去结算
        pay(){
          if(this.totalPrice < this.minPrice){
            return;
          }
          window.alert(`支付${this.totalPrice}元`);
        }
      }
    }
</script>

<style>

<style lang="stylus">
  @import '../../common/stylus/mixin.styl'
  .shopCart
    position: fixed
    left:0
    bottom:0
    height:48px
    z-index:50
    width: 100%
    .content
      display:flex
      background: #141d27
      font-size:0
      color:rgba(255,255,255,0.4)
      .content-left
        flex:1
        .logo-wrapper,.price,.desc
          display: inline-block
          vertical-align: top
        .logo-wrapper
          position: relative
          top: -10px
          margin:0 12px
          padding:6px
          width:56px
          height:56px
          box-sizing:border-box
          border-radius: 50%
          background: #141d27
          .logo
            width: 100%
            height: 100%
            border-radius: 50%
            background: #2b343c
            text-align:center
            &.highlight
              background:rgb(0,160,220)
            .icon-shopping_cart
              font-size:24px
              line-height:44px
              color:#80858a
              &.highlight
                color:#fff
          .num
            position: absolute
            top:0
            right:0
            width:24px
            height:16px
            line-height:16px
            text-align:center
            border-radius: 16px
            font-size:9px
            font-weight:700
            color: #ffffff
            background:rgb(240,20,20)
            box-shadow:0 4px 8px 0 rgba(0,0,0,0.4)
        .price,.desc
          line-height:24px
        .price
          margin-top:12px
          padding-right:12px
          box-sizing:border-box
          border-right:1px solid rgba(255,255,255,0.1)
          font-size:16px
          font-weight:700
          &.highlight
            color:#fff
        .desc
          margin:12px 0 0 12px
          font-size:10px
      .content-right
        flex:0 0 105px
        width:105px
        .pay
          height:48px
          line-height:48px
          text-align:center
          font-size:12px
          font-weight:700
          background:#2b333b
          &.not-enough
            background:#2b333b
          &.enough
            background: #00b43c
            color:#fff
    .ball-container
      .ball
        position:fixed
        left:32px
        bottom:22px
        z-index:200
        transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41)
        .inner
          width:16px
          height:16px
          border-radius:50%
          background:rgb(0, 160, 220)
          transition:all 0.4s linear
    .shopCartList
      position:absolute
      left:0
      top:0
      z-index: -1
      width:100%
      transform: translate3d(0, -100%, 0)     /*有一个向上变化的动作*/
      &.fold-enter-active,&.fold-leave-active
        transiton:all 0.5s
      &.fold-enter,&.fold-leave-active
        transform:translate3d(0,0,0)
      .listHeader
        height:40px
        line-height:40px
        padding:0 18px
        background: #f3f5f7
        border-bottom:1px solid rgba(7,17,27,0.1)
        .title
          float:left
          font-size:14px
          color:rgb(7,17,27)
        .empty
          float:right
          font-size:12px
          color:rgb(0,160,220)
      .listContent
        padding: 0 18px
        max-height:217px
        over-flow:hidden
        background:#fff
        .food
          position:relative
          paddign:12px 0
          box-sizing:border-box
          border-1px(rgba(7,17,27,0.1))
          .name
            line-height:24px
            font-size:14px
            color:rgb(7,17,27)
          .price
            position:absolute
            right:90px
            bottom:1px
            line-height:24px
            font-size:14px
            font-weight:700
            color:rgb(240,20,20)
          .cartControlWrapper
            position: absolute
            right:0
            bottom:-5px
  .listMask
    position:fixed
    left:0
    top:0
    height:100%
    width:100%
    z-index:40
    opacity:1
    background: rgba(7, 17, 27, 0.6)
    &.fade-enter-active,&.fade-leave-active
      transition:all 0.4s
    &.fade-enter,&.fade-leave-active
      opacity:0
      background:rgba(7,17,27,0)
</style>

















































评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值