01. 购物车 - 静态布局
基本结构
< template> < div class = " cart" > < van-nav-bar title = " 购物车" fixed /> < div class = " cart-title" > < span class = " all" > 共< i> 4</ i> 件商品</ span> < span class = " edit" > < van-icon name = " edit" /> 编辑</ span> </ div> < div class = " cart-list" > < div class = " cart-item" v-for = " item in 10" :key = " item" > < van-checkbox> </ van-checkbox> < div class = " show" > < img src = " http://cba.itlike.com/public/uploads/10001/20230321/a072ef0eef1648a5c4eae81fad1b7583.jpg" alt = " " > </ div> < div class = " info" > < span class = " tit text-ellipsis-2" > 新Pad 14英寸 12+128 远峰蓝 M6平板电脑 智能安卓娱乐十核游戏学习二合一 低蓝光护眼超清4K全面三星屏5GWIFI全网通 蓝魔快本平板</ span> < span class = " bottom" > < div class = " price" > ¥ < span> 1247.04</ span> </ div> < div class = " count-box" > < button class = " minus" > -</ button> < input class = " inp" :value = " 4" type = " text" readonly > < button class = " add" > +</ button> </ div> </ span> </ div> </ div> </ div> < div class = " footer-fixed" > < div class = " all-check" > < van-checkbox icon-size = " 18" > </ van-checkbox> 全选</ div> < div class = " all-total" > < div class = " price" > < span> 合计:</ span> < span> ¥ < i class = " totalPrice" > 99.99</ i> </ span> </ div> < div v-if = " true" class = " goPay" > 结算(5)</ div> < div v-else class = " delete" > 删除</ div> </ div> </ div> </ div>
</ template> < script>
export default { name : 'CartPage'
}
</ script> < style lang = " less" scoped >
// 主题 padding
.cart { padding-top : 46px; padding-bottom : 100px; background-color : #f5f5f5; min-height : 100vh; .cart-title { height : 40px; display : flex; justify-content : space-between; align-items : center; padding : 0 10px; font-size : 14px; .all { i { font-style : normal; margin : 0 2px; color : #fa2209; font-size : 16px; } } .edit { .van-icon { font-size : 18px; } } } .cart-item { margin : 0 10px 10px 10px; padding : 10px; display : flex; justify-content : space-between; background-color : #ffffff; border-radius : 5px; .show img { width : 100px; height : 100px; } .info { width : 210px; padding : 10px 5px; font-size : 14px; display : flex; flex-direction : column; justify-content : space-between; .bottom { display : flex; justify-content : space-between; .price { display : flex; align-items : flex-end; color : #fa2209; font-size : 12px; span { font-size : 16px; } } .count-box { display : flex; width : 110px; .add,.minus { width : 30px; height : 30px; outline : none; border : none; } .inp { width : 40px; height : 30px; outline : none; border : none; background-color : #efefef; text-align : center; margin : 0 5px; } } } } }
} .footer-fixed { position : fixed; left : 0; bottom : 50px; height : 50px; width : 100%; border-bottom : 1px solid #ccc; background-color : #fff; display : flex; justify-content : space-between; align-items : center; padding : 0 10px; .all-check { display : flex; align-items : center; .van-checkbox { margin-right : 5px; } } .all-total { display : flex; line-height : 36px; .price { font-size : 14px; margin-right : 10px; .totalPrice { color : #fa2209; font-size : 18px; font-style : normal; } } .goPay, .delete { min-width : 100px; height : 36px; line-height : 36px; text-align : center; background-color : #fa2f21; color : #fff; border-radius : 18px; &.disabled { background-color : #ff9779; } } } }
</ style>
按需导入组件
import { Checkbox } from 'vant'
Vue. use ( Checkbox)
02. 购物车 - 构建 vuex 模块 - 获取数据存储
新建 modules/cart.js
模块
export default { namespaced : true , state ( ) { return { cartList : [ ] } } , mutations : { } , actions : { } , getters : { }
}
挂载到 store 上面
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import cart from './modules/cart' Vue. use ( Vuex) export default new Vuex. Store ( { getters : { token : state => state. user. userInfo. token} , modules : { user, cart}
} )
封装 API 接口 api/cart.js
export const getCartList = ( ) => { return request. get ( '/cart/list' )
}
封装 action 和 mutation
mutations : { setCartList ( state, newList ) { state. cartList = newList} ,
} ,
actions : { async getCartAction ( context ) { const { data } = await getCartList ( ) data. list. forEach ( item => { item. isChecked = true } ) context. commit ( 'setCartList' , data. list) }
} ,
页面中 dispatch 调用
computed : { isLogin ( ) { return this . $store. getters. token}
} ,
created ( ) { if ( this . isLogin) { this . $store. dispatch ( 'cart/getCartAction' ) }
} ,
03. 购物车 - mapState - 渲染购物车列表
将数据映射到页面
import { mapState } from 'vuex' computed : { ... mapState ( 'cart' , [ 'cartList' ] )
}
动态渲染
< div class = " cart-list" > < div class = " cart-item" v-for = " item in cartList" :key = " item.goods_id" > < van-checkbox icon-size = " 18" :value = " item.isChecked" > </ van-checkbox> < div class = " show" @click = " $router.push(`/prodetail/${item.goods_id}`)" > < img :src = " item.goods.goods_image" alt = " " > </ div> < div class = " info" > < span class = " tit text-ellipsis-2" > {{ item.goods.goods_name }}</ span> < span class = " bottom" > < div class = " price" > ¥ < span> {{ item.goods.goods_price_min }}</ span> </ div> < CountBox :value = " item.goods_num" > </ CountBox> </ span> </ div> </ div>
</ div>
04. 购物车 - 封装 getters - 动态计算展示
封装 getters:商品总数 / 选中的商品列表 / 选中的商品总数 / 选中的商品总价
getters : { cartTotal ( state ) { return state. cartList. reduce ( ( sum, item, index ) => sum + item. goods_num, 0 ) } , selCartList ( state ) { return state. cartList. filter ( item => item. isChecked) } , selCount ( state, getters ) { return getters. selCartList. reduce ( ( sum, item, index ) => sum + item. goods_num, 0 ) } , selPrice ( state, getters ) { return getters. selCartList. reduce ( ( sum, item, index ) => { return sum + item. goods_num * item. goods. goods_price_min} , 0 ) . toFixed ( 2 ) }
}
页面中 mapGetters 映射使用
computed: {...mapGetters('cart', ['cartTotal', 'selCount', 'selPrice']),
},
< div class = " cart-title" > < span class = " all" > 共< i> {{ cartTotal || 0 }}</ i> 件商品</ span> < span class = " edit" > < van-icon name = " edit" /> 编辑</ span>
</ div> < div class = " footer-fixed" > < div class = " all-check" > < van-checkbox icon-size = " 18" > </ van-checkbox> 全选</ div> < div class = " all-total" > < div class = " price" > < span> 合计:</ span> < span> ¥ < i class = " totalPrice" > {{ selPrice }}</ i> </ span> </ div> < div v-if = " true" :class = " { disabled: selCount === 0 }" class = " goPay" > 结算({{ selCount }})</ div> < div v-else :class = " { disabled: selCount === 0 }" class = " delete" > 删除({{ selCount }})</ div> </ div>
</ div>
05. 购物车 - 全选反选功能
全选 getters
getters : { isAllChecked ( state ) { return state. cartList. every ( item => item. isChecked) }
} ... mapGetters ( 'cart' , [ 'isAllChecked' ] ) , < div class = "all-check" > < van- checkbox : value= "isAllChecked" icon- size= "18" > < / van- checkbox> 全选
< / div>
点击小选,修改状态
< van- checkbox @click= "toggleCheck(item.goods_id)" ... > < / van- checkbox> toggleCheck ( goodsId ) { this . $store. commit ( 'cart/toggleCheck' , goodsId)
} , mutations : { toggleCheck ( state, goodsId ) { const goods = state. cartList. find ( item => item. goods_id === goodsId) goods. isChecked = ! goods. isChecked} ,
}
点击全选,重置状态
< div @click= "toggleAllCheck" class = "all-check" > < van- checkbox : value= "isAllChecked" icon- size= "18" > < / van- checkbox> 全选
< / div> toggleAllCheck ( ) { this . $store. commit ( 'cart/toggleAllCheck' , ! this . isAllChecked)
} , mutations : { toggleAllCheck ( state, flag ) { state. cartList. forEach ( item => { item. isChecked = flag} ) } ,
}
06. 购物车 - 数字框修改数量
封装 api 接口
export const changeCount = ( goodsId, goodsNum, goodsSkuId ) => { return request. post ( '/cart/update' , { goodsId, goodsNum, goodsSkuId} )
}
页面中注册点击事件,传递数据
< CountBox : value= "item.goods_num" @input= "value => changeCount(value, item.goods_id, item.goods_sku_id)" > < / CountBox> changeCount ( value, goodsId, skuId ) { this . $store. dispatch ( 'cart/changeCountAction' , { value, goodsId, skuId} )
} ,
提供 action 发送请求, commit mutation
mutations : { changeCount ( state, { goodsId, value } ) { const obj = state. cartList. find ( item => item. goods_id === goodsId) obj. goods_num = value}
} ,
actions : { async changeCountAction ( context, obj ) { const { goodsId, value, skuId } = objcontext. commit ( 'changeCount' , { goodsId, value} ) await changeCount ( goodsId, value, skuId) } ,
}
07. 购物车 - 编辑切换状态
data 提供数据, 定义是否在编辑删除的状态
data () {return {isEdit: false}
},
注册点击事件,修改状态
< span class = " edit" @click = " isEdit = !isEdit" > < van-icon name = " edit" /> 编辑
</ span>
底下按钮根据状态变化
< div v-if = " !isEdit" :class = " { disabled: selCount === 0 }" class = " goPay" > 去结算({{ selCount }})
</ div>
< div v-else :class = " { disabled: selCount === 0 }" class = " delete" > 删除</ div>
监视编辑状态,动态控制复选框状态
watch : { isEdit ( value ) { if ( value) { this . $store. commit ( 'cart/toggleAllCheck' , false ) } else { this . $store. commit ( 'cart/toggleAllCheck' , true ) } }
}
08. 购物车 - 删除功能完成
查看接口,封装 API ( 注意:此处 id 为获取回来的购物车数据的 id )
export const delSelect = ( cartIds ) => { return request. post ( '/cart/clear' , { cartIds} )
}
注册删除点击事件
< div v- else : class = "{ disabled: selCount === 0 }" @click= "handleDel" class = "delete" > 删除 ( { { selCount } } )
< / div> async handleDel ( ) { if ( this . selCount === 0 ) return await this . $store. dispatch ( 'cart/delSelect' ) this . isEdit = false
} ,
提供 actions
actions : { async delSelect ( context ) { const selCartList = context. getters. selCartListconst cartIds = selCartList. map ( item => item. id) await delSelect ( cartIds) Toast ( '删除成功' ) context. dispatch ( 'getCartAction' ) }
} ,
09. 购物车 - 空购物车处理
外面包个大盒子,添加 v-if 判断
< div class = " cart-box" v-if = " isLogin && cartList.length > 0" > < div class = " cart-title" > ...</ div> < div class = " cart-list" > ...</ div> < div class = " footer-fixed" > ...</ div>
</ div> < div class = " empty-cart" v-else > < img src = " @/assets/empty.png" alt = " " > < div class = " tips" > 您的购物车是空的, 快去逛逛吧</ div> < div class = " btn" @click = " $router.push('/')" > 去逛逛</ div>
</ div>
相关样式
.empty-cart { padding : 80px 30px; img { width : 140px; height : 92px; display : block; margin : 0 auto; } .tips { text-align : center; color : #666; margin : 30px; } .btn { width : 110px; height : 32px; line-height : 32px; text-align : center; background-color : #fa2c20; border-radius : 16px; color : #fff; display : block; margin : 0 auto; }
}