底部购物车(美团外卖效果)
1、实现列表数据和底部购物车数据共享:
(1)vuex的state添加一个属性,用来存储购物车列表的数据,购物车列表的每一项都是商品对象
(2)当点击增加按钮时,往当前商品对象中添加count数据:
利用vuex,当点击增加/减少按钮时,将当前物品对象中添加一个count属性,
因为是对象的引用关系,如果vuex之前的state状态中已经存在商品列表的数据,无需再添加额外的state状态,
当前对象改变属性(利用Vue.set,将对象的新属性交给vue来动态绑定数据),商品列表中的该元素也会相应改变
添加count属性后,即可根据加减来改变count属性的值,从而实现获得该商品的数量
(3)vuex创建对应的方法来进行第(2)步
actions.js中创建方法来调用mutations.js内的方法来使得状态增/减,方法的传入当前对象
调用actions.js:this.$store.dispatch('xx',{对象:xx});
actions.js: xx({commit},{对象})
{
commit(RECEIVE_XXX,{对象});
},
mutations.js:
[RECEIVE_XXX](state,{对象})
{
if(对象.count)
{
food.count++;
}else{
state.购物车列表.push(对象); //增加时往购物车列表添加该商品对象,相同商品只改变了count属性,所以每个商品只需要添加一次
Vue.set(对象,'count',1); //让对象新增的数据能够被vue数据绑定,动态更新
}
}
(4)通过state中的购物车列表的内容,实现底部购物车的数据展示
因为需要实时更新,且建立在已有数据state上
在getters.js中定义计算属性方法,通过遍历购物车列表的每一个对象中的count,来计算数量和总价
totalCount(state)
{
return state.购物车列表.reduce((preTotal,food)=>{return preTotal+food.count},0)
},
totalPrice(state)
{
return state.购物车列表.reduce((preTotal,food)=>{return preTotal+food.count*food.price},0)
}
(5)将vuex状态引入到组件,实现对应功能
(6)清除购物车
遍历购物车列表,将所有对象的count属性值置为0,然后将整个列表置空(=[ ])
对象的引用:
多个对象引用同一个对象,其中一个改变,都会改变
多个对象引用同一个对象,其中一个指向另一个对象,其他对象不变
代码示例:
state.js:
export default{
address:"", //地址
categorys:[],//食品分类
shops:[],//商家
userInfo:{ }, //用户信息
goods:[], //商品列表
ratings:[], //商家评价
info:[] ,//商家信息
cartFoods:[] //购物车中的商品列表
}
actions.js
import {
RECEIVE_ADDRESS,
RECEIVE_CATEGORYS,
RECEIVE_SHOPS,
RECEIVE_USERINFO,
RECEIVE_RESET,
RECEIVE_GOODS,
RECEIVE_RATINGS,
RECEIVE_INFO,
RECEIVE_ADD,
RECEIVE_DELETE
} from './mutation-types'
import axios from 'axios';
export default{
//同步更新food中的count数量,增加
updateFoodCount({ commit},{ food})
{
commit(RECEIVE_ADD,{ food});
},
//同步更新food中的counte数量,减少
updateFoodCountDelete({ commit},{ food})
{
commit(RECEIVE_DELETE,{ food});
}
}
mutations.js:
import {
RECEIVE_ADDRESS,
RECEIVE_CATEGORYS,
RECEIVE_SHOPS,
RECEIVE_USERINFO,
RECEIVE_RESET,
RECEIVE_GOODS,
RECEIVE_RATINGS,
RECEIVE_INFO,
RECEIVE_ADD,
RECEIVE_DELETE
} from './mutation-types'
import Vue from 'vue'
export default{
[RECEIVE_ADD](state,{ food})
{
if(food.count)
{
food.count++;
}else{
state.cartFoods.push(food); //增加时往购物车列表添加该商品对象,相同商品只改变了count属性,所以每个商品只需要添加一次
Vue.set(food,'count',1); //让对象新增的数据能够被vue数据绑定,动态更新
}
},
[RECEIVE_DELETE](state,{ food})
{
if(food.count)
{
food.count--; //对象引用,使得属性值减1
if(food.count==0)
{
state.cartFoods.splice(state.cartFoods.indexOf(food),1);
}
}
}
}
getters.js:
export default{
totalCount(state) //返回数量
{
return state.cartFoods.reduce((preTotal,food)=>{ return preTotal+food.count},0)
},
totalPrice(state)//返回总价
{
return state.cartFoods.reduce((preTotal,food)=>{ return preTotal+food.count*food.price},0)
}
}
页面:
<template>
<div class='bf'>
<transition name='wave'>
<div class='blist' v-show='show'> //展开列表
<div class='b1'>
<span>购物车</span>
<span>清空</span>
</div>
//展开列表,根据购物车列表的状态中是否有数据来渲染
<ul class='b2' v-for='(item,index) in cartFoods' :key='index' >
<li class='b3'>
<span>{ { item.name}}</span>
<div class='b4'>
<span>¥{ { item.price}}</span>
<div class='lz'>
<div class='cir' @click='del(item)'>
-
</div>
<span class='cp'>{ { item.count}}</span>
<div class='cir' @click='add(item)'>
+
</div>
</div>
</div>
</li>
</ul>
</div>
</transition>
<div class='bs'>
<div class='bl'>
<div class='bimg' @click='change'>
<img src="./imgs/1.png" alt="">
</div>
<div class='binfo'>
<p>¥{ { totalPrice}}元</p> //根据vuex的计算属性返回总价
<p>另需配送费¥{ { info.deliveryPrice}}元</p>
</div>
</div>
<div class='br' :class='{on:totalPrice>=info.minPrice}'>//根据vuex的计算属性计算价钱
{ { totalPrice?totalPrice>=info.minPrice?'去结算':'还差'+(info.minPrice-totalPrice)+'元起送':`¥${ info.minPrice}元起送`}}
</div>
<!-- 购物车图标上的数量提示,根据vuex的购物车列表是否有值显示 -->
<div class='balert' v-show='totalCount'>
{ { totalCount}}
</div>
</div>
</div>
</template>
<script>
import { mapState,mapGetters} from 'vuex';
export default {
data()
{
return{
show:false
}
},
methods: {
change() {
this.show=!this.show;
},
del(food) //减少count
{
this.$store.dispatch('updateFoodCountDelete',{ food})
},
add(food) //增加count
{
this.$store.dispatch('updateFoodCount',{ food})
}
},
computed: {
...mapState(['cartFoods','info']),
...mapGetters(['totalCount','totalPrice'])
}
}
</script>
<style lang='less'>
.on{
background-color: #44BB00!important;
}
.bf{
width: 100%;
position: fixed;
bottom: 0px;
.blist{
background-color:#F3F5F7;
.b1{
display: flex;
justify-content: space-between;
padding: 0 20px;
height: 40px;
line-height: 40px;
border: solid 1px #ccc;
span:last-child{
padding: 0 10px;
border-left: solid 1px #ccc;
border-right: solid 1px #ccc;
}
}
.b2{
padding-bottom: 10px;
background-color: white;
.b3{
height: 30px;
line-height: 30px;
border-bottom:solid 1px #ccc;
padding:0 20px ;
display: flex;
justify-content: space-between;
.b4{
display: flex;
>span{
color:red
}
.lz{
display: flex;
width: 70px;
justify-content: space-evenly;
align-items: center;
.cir{
text-align: center;
line-height: 20px;
width: 20px;
height: 20px;
color: white;
background-color: #008000;
border-radius: 100px;
}
.cp{
font-size: 10px;
}
}
}
}
}
}
.bs{
color: white;
background-color: #131D29;
height: 50px;
width: 100%;
display: flex;
justify-content: space-between;
position: relative;
.bl{
display: flex;
.bimg{
height: 45px;
width: 45px;
border-radius: 100px;
margin-top: -10px;
background-color: #131D29;
text-align: center;
font-size: 0px;
line-height: 45px;
margin-left: 20px;
img{
height: 40px;
width: 40px;
vertical-align: middle;
}
}
.binfo{
display: flex;
flex-direction: column;
justify-content: space-around;
margin-left: 10px;
p:last-child{
color:#ccc;
font-size: 12px;
}
}
}
.br{
height: 50px;
line-height: 50px;
background-color: #2B333D;
padding:0 10px;
color: white;
}
.balert{
background-color: red;
height: 20px;
width: 20px;
line-height: 20px;
border-radius: 100px;
text-align: center;
position: absolute;
left: 45px;
top:-10px;
}
}
}
.wave-enter-to,.wave-leave{
transform:translateY(0)
}
.wave-enter,.wave-leave-to{
transform:translateY(100%)
}
.wave-enter-active,.wave-leave-active{
transition: transform 1s;
}
</style>
效果图:
还没有评论,来说两句吧...