一、购物车基础
1)购物车状态设计cartFoods+mutation
store/state.js
// 所有要管理的状态数据:从页面需求分析出来,最好和api/index.js里的命名相同
export default{
latitude: 40.10038, // 纬度
longitude: 116.36867, // 经度
address: {}, //地址相关信息对象
categorys: [], // 食品分类数组
shops: [], // 商家数组
userInfo: {}, // 用户信息
goods: [], // 商品列表
ratings: [], // 商家评价列表
info: {}, // 商家信息
cartFoods: [], // 【1】购物车中食物的列表
}
1.用于存储加入到了购物车中的food对象,即food.count加了此属性的food,放入cartFoods中
2.重点:【1-2】处
src/store/mutations.js
// 加购物车数量
[INCREMENT_FOOD_COUNT](state,{food}){
if(!food.count){//如果不存在数量属性则加一个
// food.count=1//此操作虽能在food中加一个count属性,但视图无法更新
/*可更新视图的设置新属性:Vue.set()参数:
对象
属性名
属性值
*/
Vue.set(food,'count',1)
// 【1】把加入了数量的食物,都放入cartFoods的状态中,用于购物车结算
state.cartFoods.push(food)
}else{//如果存在则直接加1
food.count++
}
},
// 减购物车数量
[DECREMENT_FOOD_COUNT](state,{food}){
if(food.count){//如果购物车数量>0才进行减操作,防止数量为负
food.count--
// 如果food.count数量减少至0了则从state.cartFoods中删除相应的food
// 【2】splice(删除下标,删除数量);indexOf(food)返回对应对象的下标
if(food.count===0){
state.cartFoods.splice(state.cartFoods.indexOf(food),1)
}
}
}
2)设计一个新状态用于计算加入购物车食物总数量(totalCount)、总价格(totalPrice)
getters类似computed:处理vuex中现有的状态,生成返回一个所有地方都可调用的新状态
知识点:getters的生成
reduce()函数两种返回累加值写法=>和return
store/getters.js
// getters类似computed:处理vuex中现有的状态,生成返回一个所有地方都可调用的新状态
export default{
//【1】返回根据cartFoods中的food.count生成:购物车中食物的总数量,用于结算时使用
totalCount(state){
return state.cartFoods.reduce((total,food)=>{
//【1.1】此处是用非箭头写法,需要返回一下值外面才能收到
return total+food.count
},0)
},
//【2】返回根据cartFoods中的food.count生成:购物车中食物总价格
totalPrice(state){
//【2.1】此处内部直接用=>total+food...代替return
return state.cartFoods.reduce((total,food)=>total+food.count*food.price,0)
},
//或: 【2.2】以上也可用totalCount生成的getter来计算购物车总价:
totalPrice2(state,getters){
return state.cartFoods.reduce((total,food)=>total+getters.totalCount*food.price,0)
}
}
3)购物车组件src/components/shopCart/shopCart.vue
<template>
<div>
<div class="shopcart">
<div class="content">
<div class="content-left">
<div class="logo-wrapper">
<!-- 【5】如果购物车里商品有数量则加高亮显示类名highlight -->
<div class="logo " :class="{highlight:totalCount}">
<!-- 【12】点击显示购物车列表 -->
<i class="iconfont icon-shopping_cart" :class="{highlight:totalCount}"
@click="showCarts"></i>
</div>
<div class="num">{{totalCount}}</div>
</div>
<div class="price" :class="{highlight:totalCount}">¥{{totalPrice}}</div>
<div class="desc">另需配送费¥{{info.deliveryPrice}} 元</div>
</div>
<div class="content-right">
<!-- 【6】结合计算属性控制结算按钮颜色(灰/绿),显示文字(还差xx元起送/去结算) -->
<div class="pay" :class="payClass">
{{payText}}
</div>
</div>
</div>
<!-- 【7】显隐购物车列表 需满足2个条件:1.isShow为true;且2.购物车总数不能为0;
它们有一个为false就不显示列表,因此要用到计算属性showList对是否显示重新计算 -->
<div class="shopcart-list" v-show="showList">
<div class="list-header">
<h1 class="title">购物车</h1>
<span class="empty">清空</span>
</div>
<div class="list-content">
<ul>
<li class="food" v-for="(food,index) in cartFoods" :key="index">
<span class="name">{{food.name}}</span>
<div class="price"><span>¥{{food.price}}</span></div>
<div class="cartcontrol-wrapper">
<div class="cartcontrol">
<CartControl :food="food"/>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
<!-- 【8】购物车列表蒙版:用计算属性原因同上 -->
<div class="list-mask" v-show="showList" @click="showCarts"></div>
</div>
</template>
<script>
//【1】引入状态读取,getters读取助手函数
import {mapState,mapGetters} from 'vuex'
import CartControl from '../CartControl/CartControl' //购物车加减数量组件
export default{
data(){
return{
isShow:false, //【9】显隐购物车列表
}
},
computed:{
// 【2】引入需要的state、getters
...mapState(['cartFoods','info']), //购物车里的食物对象,商家相关信息如:食物的起送金等其它信息
...mapGetters(['totalCount','totalPrice']), //购物车食物总数量,购物车食物总价
// 【3】购物车付款结算按钮:购物车总额>起送时显示样式:enough;
// 小于时显示样式:not-enough;
payClass(){
const {totalPrice}=this
const {minPrice}=this.info
return totalPrice>=minPrice ? 'enough' : 'not-enough'
},
// 【4】购物车付款结算按钮:购物车没东西时显示:xx元起送;
// 未达金额时显示:还差xx元起送;达金额则显示:去结算;
payText(){
const {totalPrice}=this
const {minPrice}=this.info
if(totalPrice===0){
return `${minPrice}元起送`
}else if(totalPrice<minPrice){
return `还差${minPrice-totalPrice}元起送`
}else{
return '去结算'
}
},
// 【11】重新计算isShow,控制是否显示隐藏购物车列表
showList(){
/* 显示购物车列表条件:列表总数不为0 且 isShow也为true,才会显示购物车列表*/
if(this.totalCount===0){
/*isShow置否,防止在商家详情列表里点加时满足:count不为0 且 isShow也为true
自动显示购物车列表*/
this.isShow=false
return false //直接返回false让
}
// 不为0则原样返回
return this.isShow
},
},
methods:{
//【10】显隐购物车列表
showCarts(){
// 大于0才显示:防止显示空列表
if(this.totalCount>0){
this.isShow=!this.isShow
}
}
},
components:{
CartControl
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus" scoped>
@import "../../common/stylus/mixins.styl"
.shopcart
position fixed
left 0
bottom 0
z-index 50
width 100%
height 48px
.content
display flex
background #141d27
font-size 0
color rgba(255, 255, 255, 0.4)
.content-left
flex 1
.logo-wrapper
display inline-block
vertical-align top
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%
text-align center
background #2b343c
&.highlight
background $green
.icon-shopping_cart
line-height 44px
font-size 24px
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
display inline-block
vertical-align top
margin-top 5px
line-height 24px
padding-right 12px
box-sizing border-box
font-size 16px
font-weight 700
color #fff
&.highlight
color #fff
.desc
display inline-block
vertical-align bottom
margin-bottom 15px
margin-left -45px
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
color #fff
&.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 $green
transition all 0.4s linear
.shopcart-list
position absolute
left 0
top 0
z-index -1
width 100%
transform translateY(-100%)
&.move-enter-active, &.move-leave-active
transition transform .3s
&.move-enter, &.move-leave-to
transform translateY(0)
.list-header
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)
.list-content
padding 0 18px
max-height 217px
overflow hidden
background #fff
.food
position relative
padding 12px 0
box-sizing border-box
bottom-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 12px
line-height 24px
font-size 14px
font-weight 700
color rgb(240, 20, 20)
.cartcontrol-wrapper
position absolute
right 0
bottom 6px
.list-mask
position fixed
top 0
left 0
width 100%
height 100%
z-index 40
backdrop-filter blur(10px)
opacity 1
background rgba(7, 17, 27, 0.6)
&.fade-enter-active, &.fade-leave-active
transition all 0.5s
&.fade-enter, &.fade-leave-to
opacity 0
background rgba(7, 17, 27, 0)
</style>
4)商家详情调用购物车组件src/pages/shop/goods/goods.vue
...略过
<!-- 底部购物车组件 -->
<ShopCart />
</div>
<!-- ref是标识此子组件:用于调用其内部的toggleShow()展示隐藏食物详情;
:food向子组件传当前food对象 -->
<Food :food='food' ref="food" />
</div>
</template>
import ShopCart from '../../../components/ShopCart/ShopCart.vue' //底购物车按钮
export default{
...
components:{
CartControl,
Food,
ShopCart,
}
}
5)效果:http://localhost:8080/#/shop/goods
0.点购物车图标显隐购物车列表
并能自动计算相关数值:
- 右结算按钮:满xx元配送 / 差xx配送 / 去结算
- 总费用计算
- 食品总数量计算 左侧红点
- 点蒙板也可关闭列表
二、清空购物车+滚动购物车
0.store/state.js
// 所有要管理的状态数据:从页面需求分析出来,最好和api/index.js里的命名相同
export default{
cartFoods: [], // 购物车中食物的列表
}
1.store/mutation-types.js
export const CLEAR_CART = 'clear_cart' // 清空购物车
2.mutations.js
import {
//【1】 引入类型
CLEAR_CART,
} from './mutation-types.js'
import Vue from 'vue' //用于新增一个状态的属性,并能自动更新视图
export default{
...略过
//【2】清空购物车
[CLEAR_CART](state){
// 把购物车状态中的count全部置为0
state.cartFoods.forEach(food=>food.count=0)
// 把购物车清空
state.cartFoods=[]
}
}
3.actions.js
// 控制mutations
import {
略过...
CLEAR_CART //【1】
} from './mutation-types.js'
import {
略过... //ajax请求
} from '../api/index.js'
export default{
略过...
// 【2】清空购物车
clearCart({commit}){
commit(CLEAR_CART)
}
}
4.调用清空购物车components/shopCart/shopCart.vue
//【1】
<span class="empty" @click="clearCart">清空</span>
import { MessageBox } from 'mint-ui' //确认提示框组件
methods:{
略过...
//【1】调用vuex的action触发:mutation清空cartFoods[]和food.count数量[实现清空购物车]
clearCart(){
/*min-ui组件MessageBox.confirm('确定吗')
.then(fn1,fn2)fn1是点确定时执行的操作,fn2:取消时操作*/
MessageBox.confirm('确认清空购物车吗').then(action=>{
this.$store.dispatch('clearCart')
},()=>{})
}
5. 购物车列表的滚动better-scroll components/shopCart/shopCart.vue
import BScroll from '@better-scroll/core' //滑动库
methods:{
// 【11】重新计算isShow,控制是否显示隐藏购物车列表
showList(){
/* 显示购物车列表条件:列表总数不为0 且 isShow也为true,才会显示购物车列表*/
if(this.totalCount===0){
/*isShow置否,防止在商家详情列表里点加时满足:count不为0 且 isShow也为true
自动显示购物车列表*/
this.isShow=false
return false //直接返回false让
}
//【重点】如果显示了就创建一个Bscroll滑动对象
if(this.isShow) {
this.$nextTick(() => {
// 实现BScroll的实例是一个单例,
// 如果创建多个就会导致里面的加减按钮一次加减多个食物数量
if(!this.scroll) { //如果滚动对象不存在执行:
this.scroll = new BScroll('.list-content', {
click: true
})
} else {
// 让滚动条刷新一下: 重新统计内容的高度解决第一次无法滚动问题
this.scroll.refresh()
}
})
}
// 不为0则原样返回
return this.isShow
}
}
效果:购物车列表即可滚动