本节内容:
- 添加商品数量的购物车控件按钮的实现
- 页面底部购物车组件部分的实现
- 通过购物车控件按钮增加商品数量,更改数据后,更新购物车控件的各个状态和各个数据

主组件good.vue模块代码

  • 购物车控件按钮
<cartcontrol @cartAdd ='_drop' :food ='list'></cartcontrol>
  • 购物车组件部分
<cartshop ref='shopcart' :selectFoods = 'selectFoods' :minPrice = 'seller.minPrice' :deliveryPrice = 'seller.deliveryPrice'></cartshop>
  • selectFoods 为计算属性
  • 已在购物车控件按钮组件中点击了增加功能,会添加了商品到购物车,实现代码在cartcontrol.vue中:
addCount(event){
             if(!event._constructed) return;
             if(!this.food.count){
              this.$set(this.food, 'count', 1)
              }else{
                this.food.count++
              }
              this.$emit('cartAdd',event.target);
          }
  • 遍历所有的商品foods,如果其数量count存在,
selectFoods:function(){
            let foods =[];
            this.goods.forEach((item)=>{
                item.foods.forEach((fd)=>{
                    if(fd.count){
                        foods.push(fd)
                    }
                })
            })
            return foods;
        }
  • good.vue的完整代码如下:
<template>
 <div class="goods">
    <div ref='menuWrapper'>
        <div class='goodsMenu'>
            <div v-for='(item,index) in goods' class='Menu-item' :class='{currentMenu:currentIndex===index}' @click='menuClick(index,$event)'>
                <span class='itemText'><span v-show='item.type>0' class='h-iconJ' :class='classMap[item.type]'></span>{{item.name}}</span>
            </div> 
        </div> 
    </div>
    <div ref='foodsWrapper'>
        <div class='goodsLists'>
           <div class='listOne food-list-hook' v-for='item in goods'>
               <h1 class='listOneTitle'>{{item.name}}</h1>
               <div class='foodsOne' :class='{boderBottom:item.foods.length>1}' v-for='list in item.foods'>
                    <cartcontrol @cartAdd ='_drop' :food ='list'></cartcontrol>
                    <div class='listImg'>
                        <img :src="list.image">
                    </div>
                    <div class='goodInfo'>
                        <div class='goodsName'>{{list.name}}</div>
                        <div class='goodsDesc'>{{list.description}}</div>
                        <div class='goodsxs'><span>月售{{list.sellCount}}份</span><span>好评率{{list.rating}}%</span></div>
                        <div class='foodsPrice'><span>¥{{list.price}}</span><span class='old' v-show='list.oldPrice!=""'>¥{{list.oldPrice}}</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <cartshop ref='shopcart' :selectFoods = 'selectFoods' :minPrice = 'seller.minPrice' :deliveryPrice = 'seller.deliveryPrice'></cartshop>
</div>
</template>

<script>
import BScroll from 'better-scroll';
import cartcontrol from '../cartcontrol/cartcontrol.vue';
import cartshop from '../cartcontrol/cartshop.vue';
export default {
    name: 'goods',
    components:{cartcontrol,cartshop},
    props: {
        seller:{
            type:Object
        }
    },
    data(){
        return{
       goods:[],//商品信息
       listHeight:[],//每个菜单将要滑动的height
       scrollY:0
        }
    },
    computed:{ 
        //选中到购物车中的商品数据,由每一个food对象组成
        selectFoods:function(){
            let foods =[];
            this.goods.forEach((item)=>{
                item.foods.forEach((fd)=>{
                    if(fd.count){
                        foods.push(fd)
                    }
                })
            })
            return foods;
        },
        currentIndex:function(){
            for(let i=0;i<this.listHeight.length;i++){
                let height1 = this.listHeight[i];
                let height2 = this.listHeight[i+1];
                if(!height2||(this.scrollY>=height1&&this.scrollY<height2)){
                    return i
                }
            }
        }
    },
    created(){
        //左侧菜单图标信息
        this.classMap=['decrease_3','discount_3','guarantee_3','invoice_3','special_3'],
        //goods商品数据请求
        this.$http.get('/api/goods').then((response)=>{
          response = response.body;
          if(response.errno){
            this.goods =response.data;
            this.$nextTick(()=>{
              this._initScroll();
              this._calculateHeight();
            })
           }
        },(response)=>{
          console.log(response)
        });

    },
    methods:{
        //获取子组件的数据
        _drop(el){
                this.$refs.shopcart.drop(el)
        },
        //点击菜单显示右侧相应菜单
        menuClick(index,event){
            if(!event._constructed) return false;
            let foodLists = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
            let el =foodLists[index];

            this.foodsScroll.scrollToElement(el, 500)
        },
        //初始化better-scroll
        _initScroll() {
            //获取better-scroll实例
            this.meunScroll = new BScroll(this.$refs.menuWrapper, {
                click:true
            });
            this.foodsScroll = new BScroll(this.$refs.foodsWrapper, {
                click:true,
                probeType:3
            });
            this.foodsScroll.on('scroll',(pos)=>{
                this.scrollY =Math.abs(Math.round(pos.y));
            })
        },
          //获取右侧商品每个列表的y坐标,组成数组listHeight
        _calculateHeight() {
            let foodLists = this.$refs.foodsWrapper.getElementsByClassName('food-list-hook');
            //第一个列表坐标
            let height=0;
            this.listHeight.push(height);
            for(let i=0,len=foodLists.length;i<len;i++){
                let item =foodLists[i];
                height += item.clientHeight;
                this.listHeight.push(height);
            }
        }
    }
}
</script>
<style scope>
    .goods{
        position:absolute;
        display: flex;
        width:100%;
        bottom:46px;
        top:176.4px;
        overflow: hidden;
    }
    .goodsMenu{
        flex: 0,0,80px;
        width:80px;
        padding:0 12px;
        background-color:#f3f5f7;
        box-sizing:border-box;
    }
    .Menu-item{
        position:relative;
        display: table;
        width:100%;
        height:54px;
        font-size:0;
    }
    .Menu-item:after,
    .boderBottom:after{
        position:absolute;
        content: '';
        width:100%;
        bottom:0;
        left:0;
        border-top: 1px solid rgba(7,17,27,0.1); 
        -webkit-transform: scaleY(0.5);
        transform: scaleY(0.5);
    }
    .Menu-item:last-child:after,
    .boderBottom:last-child:after{
        border-top:none;
    }
    .currentMenu{
        background-color:#fff;
        color:#000;
        padding: 0 12px;
        margin: -1px 0 0 -12px;
        border:none;

    }
    .itemText{
        display: table-cell;
        font-size:12px;
        color:#07111B;
        font-weight: 200;
        line-height: 14px;
        vertical-align: middle;
    }
    .Menu-item .h-iconJ{
        display: inline-block;
        width:14px;
        height:14px;
        margin-right:4px;
        background-size:14px 14px;
    }
    .decrease_3{
        background-image: url('decrease_3@2x.png');
    }
    .discount_3{
       background-image: url('discount_3@2x.png');
   }
   .guarantee_3{
       background-image: url('guarantee_3@2x.png');
   }
   .invoice_3{
       background-image: url('invoice_3@2x.png');
   }
   .special_3{
       background-image: url('special_3@2x.png');
   }
   /*右侧商品区*/
   .goodsLists{
       flex: 1;
   }
   /*一个菜单分类*/
   .listOne{
    width:100%;
}
/*分类名*/
.listOneTitle{
    width:100%;
    height:26px;
    line-height: 26px;
    border-left:2px solid #d9dde1;
    background-color:#f3f5f7;
    padding-left:14px;
    font-size:12px;
    color:rgb(147,153,159);
    box-sizing:border-box;
}
/*商品介绍*/
.foodsOne{
    position: relative;
    display:flex;
    margin:18px;
    font-size:0px;
    box-sizing:border-box;
}
.boderBottom {
    /*border-bottom:1px solid rgba(7,17,27,0.1);*/
    padding-bottom: 18px;
}
.boderBottom:last-child{
    /*border-bottom:none;*/
    padding-bottom: 0;
}
.cartcontrol{
    position: absolute;
    bottom:18px;
    right:0;
}
.listImg{
    flex:0,0,58px;
    display:inline-block;
    width:58px;
    height:58px;
    vertical-align: top;
}
.listImg img{
    width:100%;
    height:100%;
}
.goodInfo{
    flex:1;
    display:inline-block;
    margin:2px 0 0 10px;
}
.goodInfo .goodsName{
    font-size:14px;
    color:rgb(7,17,27);
    line-height: 14px;
}
.goodInfo .goodsDesc,
.goodInfo .goodsxs{
    font-size:10px;
    color:rgb(147,153,159);
    line-height: 12px;
    margin-top:8px;
}
.goodInfo .goodsxs span:first-child{
    margin-right:12px;
}
.goodInfo .foodsPrice{
    font-size:10px;
    color:rgb(240,20,20);
    font-weight: normal;
    line-height: 14px;
    margin-top:8px;
}
.goodInfo .foodsPrice .old{
    text-decoration:line-through;
    margin-left:8px;
    color:rgb(147,153,159);
}
        /*.goodCarts{
            width:100%;
            height:46px;
        }*/
    </style>

添加商品控件代码

  • 增加商品到购物车:addCount方法
  • 删除商品到购物车:decreaseCount方法
  • 不能直接更改props进来的数据 ,使用vue.$set()更改,使vue能够响应数据的变化,然后更新 ,具体原理请看vue官方关于响应式原理的介绍
  • 父组件good.vue中响应数据变更
<template>
    <div class='cartcontrol'>
      <transition name='move'>
        <div v-show='food.count>0' class='cart-decrease' @click.stop.prevent='decreaseCount($event)'>
          <span class='icon-remove_circle_outline'></span>
        </div>
      </transition>
      <div v-show='food.count>0' class='cart-count'>{{food.count}}</div>
      <div class='cart-add' @click.stop.prevent='addCount($event)'>
        <span class='icon-add_circle'></span>
      </div>
    </div>
</template>
<script>
    export default {
        name:'cartcontrol',
        props:{
          food:{
            type:Object
          }
        },
        methods:{
          addCount(event){
             if(!event._constructed) return;
             if(!this.food.count){
              this.$set(this.food, 'count', 1)
              }else{
                this.food.count++
              }
              this.$emit('cartAdd',event.target);
          },
          decreaseCount(event){
             if(!event._constructed) return;
             if(this.food.count<=1){
              this.$set(this.food, 'count', 0)
              }else{
                this.food.count--
              }
          }
        }

    }
</script>
<style lang="stylus" rel="stylesheet/stylus" scope>
.cartcontrol
  font-size:0
  .cart-decrease,
  .cart-add,
  .cart-count
    display:inline-block
    width:24px
    height:24px
    text-align:center
    line-height: 24px
    font-size:24px
    color:rgb(0,160,220)
  .cart-count
    vertical-align:top
    font-size:10px
    color:rgb(147,153,159)
  .move-enter-active, .move-leave-active 
    transition:all .8s ease
    transform:rotate3d(0,0,1,180deg) 
  .move-enter,.move-leave-to
    opacity:0
    transform:translate3d(24px,0,0)
</style>

购物车控件代码

  • cartcontrl控件中更改了商品数据,在父组件中selectFoods响应后更新
  • 父组件中传递selectFoods到子组件中,子组件获取到数据selectfoods后通过添加需要的计算属性,及时更新数据的变化
<template>
    <div class='cartshop'>
        <div class='content'>
            <div class='content-left'>
                <div class='cartWrap'>
                    <div class='icon-shopping_cart' :class='{highColor:foodCount>0}'></div>
                </div>
                <div v-if='foodCount>0' class='foodcount'>{{foodCount}}</div>
                <div class='allPrice' :class='{highColor:foodCount>0}'>¥{{AllPrice}}</div>
                <div class='peisong'>另需配送费¥{{deliveryPrice}}元</div>
            </div>
            <div class='content-right' :class='{highColor:AllPrice>minPrice}'>
                <p>{{DescPric}}</p><!-- ¥{{minPrice}}(22)起送 -->
            </div>
        </div>
        <div class='ball-container'>
          <div v-for='ball in balls'>
            <transition name='drop' @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter">
              <div v-show='ball.show' class='ball'>
                <div class="inner inner-hook"></div>
              </div>
            </transition>
          </div>
        </div>
    </div>
</template>
<script>
    export default {
        name:'cartshop',
        props: {
            //选中商品
            selectFoods:{
                type:Array
            },
            //起送价
            minPrice:{
                type:Number
            },
            //配送费
            deliveryPrice:{
                type:Number
            }
        },
        data(){
            return{
              balls:[{
                show:false
              },{
                show:false
              },{
                show:false
              },{
                show:false
              },{
                show:false
              }],
              dropBalls:[]
           }
        },
        computed:{
            AllPrice:function(){
               let ap = 0;
               this.selectFoods.forEach((item)=>{
                    ap +=item.price*item.count;
                  }) 
               return ap
            },
            foodCount:function(){
                let count = 0;
                this.selectFoods.forEach((item)=>{
                    count +=item.count;
               })
               return count; 
            },
            DescPric:function(){
                if(this.AllPrice==0){
                    return '¥'+this.minPrice+'起送';
                }else if(this.AllPrice<this.minPrice){
                    let price =this.minPrice -this.AllPrice;
                    return '还差¥'+price+'起送';
                }else if(this.AllPrice>this.minPrice){
                    return '结算';
                }
            }
        },
        methods:{
          drop(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){
            let count = this.balls.length;
            while (count--) {
              let ball = this.balls[count];
              if (ball.show===true) {
                //cartcontrol组件的加号DOM对象相对视窗的距离
                let rect = ball.el.getBoundingClientRect();
                let x =rect.left - 32;
                let y = -(window.innerHeight - rect.top - 23);
                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)`;
              }
            }
          },
          enter(el){
            //触发浏览器重绘
            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)';
            });

          },
          afterEnter(el){
             let ball = this.dropBalls.shift();
              if (ball) {
                el.style.display = 'none';
                ball.show = false;
              }
          }
        }
    }
</script>
<style lang="stylus" rel="stylesheet/stylus" scope>
.cartshop
    position:fixed
    left:0
    bottom:0
    width:100%
    height:48px
    .content
        display:flex
        width:100%
        background-color:#141d27
        .content-left
            position:relative
            display:inline-block
            flex:1
            .foodcount
               position:absolute
               top:-8px
               left:44px
               width:24px
               border-radius:6px
               box-shadow:0 4px 8px 0 rgba(0,0,0,0.4)
               font-size:9px
               font-weight:700
               text-align:center
               line-height:16px
               color:#fff
               background-color:rgb(240,20,20)
            .cartWrap
               position:absolute
               top:-10px
               left:12px
               width:56px
               height:56px
               padding:6px
               box-sizing:border-box
               border-radius:50%
               background-color:#141d27
               font-size:0
               .icon-shopping_cart
                    width: 100%
                    height: 100%
                    line-height: 44px
                    border-radius: 50%
                    font-size: 28px
                    text-align: center
                    color: rgba(255,255,255,0.4)
                    background-color: #2b343c
                    &.highColor
                        background-color:rgb(0,160,220)
                        color:#fff

            .allPrice
                 display:inline-block
                 line-height:24px
                 margin: 12px 0 0 80px
                 padding-right:12px
                 border-right:1px solid rgba(255,255,255,0.1)
                 font-size: 16px
                 font-weight:700
                 color:rgba(255,255,255,0.4)
                 &.highColor
                   color:rgb(255,255,255)  
            .peisong
                display:inline-block
                padding-left:12px
                line-height:48px
                font-size:12px
                font-weight:400
                color:rgba(255,255,255,0.4)

        .content-right  
            display:inline-block 
            height:48px
            line-height: 48px
            flex:0 0 105px
            padding:0 8px
            box-sizing:border-box 
            background-color:#2B333B
            &.highColor
               background-color:#1f9324 
            &.highColor p
                color:#fff
            p
                font-size:12px
                font-weight:700
                text-align:center
                color:rgba(255,255,255,0.4)
    .ball-container
      .ball
        position: fixed
        left: 32px
        bottom: 22px
        z-index: 200
        transition: all 0.6s cubic-bezier(.76,-0.75,.36,.94)
        .inner
          width: 16px
          height: 16px
          border-radius: 50%
          background: rgb(0, 160, 220)
          transition: all 0.4s linear        
</style>