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