加入购物车这一块不像尚品汇一样,加入购物车后,不跳转至确认页面,而是在购物车图标上添加小红点+弹窗提示。
小红点主要还是由vuex进行控制。
在点击加入购物车后,触发以下逻辑:
// 加入购物车
async addToShopCart(){
// 先判断这件商品是否已经在购物车中
// 如果是,提示并且返回
if(this.realShopcart.some(item => item.goodsId == this.goodDetail.id)){
this.$message.warning('此商品已经在购物车中,请在购物车中修改数量!');
return ;
}
// 封装数据
const data = {
userId:this.userId,
goodsId:this.goodDetail.id,
number:this.buyNum
}
try {
// 派发action,与后端进行交互
await this.$store.dispatch('addToShopcart',data);
// 控制导航栏购物车的小红点
this.$store.dispatch('setNoticeCount',data.number);
this.$message.success('加入购物车成功!');
} catch (error) {
this.$message.error(`${error}`);
}
},
然后在Header.vue导航栏的组件中:
在点击购物车按钮的时候,派发action将count重置为0,购物车的仓库见下方。
//保存链接激活状态,并切换路由
saveNavState(activeName,activeKey){
...
if(activeName == 'shopcart') {
this.$store.dispatch('setNoticeCount',0);
}
...
},
还有在购物车的勾选、多选、全选过程中,没有与服务器发生交互。
建议把购物车的相关数据都存在vuex仓库中,仓库里负责操作数据,组件里只负责派发,读取数据
注意:如果后端传来的数据项中没有
isCheck、allcheck这些判断是否选上的flag,可以在getter中进行处理,在vue组件中用mapGetters获取
这里先讲讲各个操作的逻辑,购物车包含以下几个操作:
-
多选,多选按钮的操作包括:
- 将多选按钮上的
isCheck赋反,使用! - 判断是否所有的多选框都被选上了,可以用every方法
- 先将总价初始化(改为0),然后可以使用foreach方法进行累加运算。(金额操作还是老套路,记得*100/100)
- 最后注意全部计算完再保留两位小数,用toFixed转化后,数据类型会被变为string类型,这里也是个小坑
- 将多选按钮上的
-
全选,全选按钮包括:
- 把全选按钮上的
allCheck赋反 - 遍历,将所有多选框的状态设置为和全选框的状态相同
- 先将总价清空,然后再累加金额
- toFixed转化,保留两位小数
- 把全选按钮上的
来看下shopcart.js仓库:
// shopcart购物车小仓库
const state = {
shopcart: [], // 先把从后端请求到的数据放这里(没有check属性)
allChecked: false, // 控制全选
totalPrice: 0.00 // 存储总价
noticeCount: sessionStorage.getItem('noticeInCart') || 0, // 未读,控制小红点
}
const mutations = {
// 获取购物车列表数据
GETSHOPCARTINFO(state, shopcart) {
state.shopcart = shopcart
},
// 把多选,全选的逻辑写这里。写组件里也行,但是我这样做翻车了
// 点击商品前面的商品的复选框
SELECTITEM(state, index) {
state.shopcart[index].isCheck = !state.shopcart[index].isCheck // 赋反
state.allChecked = state.shopcart.every(item => item.isCheck == true) // 检查是不是所有多选框都勾上了,是的话就勾上全选
state.totalPrice = 0 // 先将总价清空
state.shopcart.forEach(item => { // 对勾上的item进行累加
if (item.isCheck) {
// 注意toFixed后用 += 运算符会默认为字符串拼接,因为toFixed 转化后类型为字符串
state.totalPrice += ((item.price * item.number) * 100 / 100)
}
});
state.totalPrice = state.totalPrice.toFixed(2) // 最后再保留两位小数
},
// 点击底部的全选按钮
ALLSELECT(state) {
state.allChecked = !state.allChecked // 赋反
state.shopcart.forEach(item => {
item['isCheck'] = state.allChecked // 将所有多选框的状态设置为和全选框的状态相同
})
state.totalPrice = 0 // 先将总价清空
state.shopcart.forEach(item => { // 对勾上的item进行累加
if (item.isCheck) {
state.totalPrice += ((item.price * item.number) * 100 / 100)
}
})
state.totalPrice = state.totalPrice.toFixed(2) // 最后再保留两位小数
},
// 控制小红点,有sessionStorge这一步好像也没用
SETNOTICECOUNT(state, number) {
state.noticeCount = number
}
}
const actions = {
// 从后端请求数据的操作都是按老套路
// 获取购物车列表数据
async getShopcartInfo({ commit }, userId) {
...
},
// 添加入购物车
async addToShopcart({ commit }, data) {
...
},
// 删除购物车中的商品
async delGoodFromShopcart({ commit }, id) {
...
},
// 点击商品前面的商品的复选框
selectItems({ commit }, index) {
commit('SELECTITEM', index) // 传递shopcart数组中的索引,根据此找到对应item
},
// 点击底部的全选按钮
allSelect({ commit }) {
commit('ALLSELECT')
},
// 设置未读信息状态,把number存sessionStorge里,不写这里也好,但这里是对shopcart的数据进行统一管理
setNoticeCount({ commit }, number) {
sessionStorage.setItem('noticeInCart', number);
commit('SETNOTICECOUNT', number);
}
}
const getters = {
realShopcart(state) {
// 为购物车列表动态添加isCheck属性,默认为false
const realShopcart = state.shopcart.map(item => {
item['isCheck'] = false
return item
})
state.allChecked = false // 记得也将全选取消
return realShopcart
}
}
export default {
state,
mutations,
actions,
getters
}
看下shopcart.vue组件:
理清一下思路:
- 先拿到用户的id,再去后端请求购物车列表数据,注意异步操作。
- 拿到后用mapGetter获取购物车的数据,再进行渲染。
- method就放多选、全选、删除等操作,派发action通知仓库进行操作。
- 注意对
allchecked,totalPrice这些计算属性的getter和setter写全。 - 核心是对
realShopcart的监视,因为我们所有操作都是通知仓库,对realShopcart这个list进行修改,渲染也是根据这个,以此来保证金额的同步变化。
export default{
name:'ShopCart',
data() {
return {
userId:'',
};
},
computed:{
...mapGetters(['realShopcart']), // 通过getters获取加工后的数据,注意整个购物车的核心数据是这个list,都是依据这个list去渲染
// 计算属性的get set最好写全,不然可能会报错
// 计算属性均是需要从仓库中取出的属性
allChecked:{
get(){
return this.$store.state.shopcart.allChecked
},
set(val){
return this.$store.state.shopcart.allChecked = val
}
},
totalPrice:{
get(){
return this.$store.state.shopcart.totalPrice
},
set(val){
return this.$store.state.shopcart.totalPrice = val
}
}
},
watch:{
// 核心是监视 realShopcart 这个list,不管是勾选,还是改变数量,都需要被监视到,以此来改变totalPrice
realShopcart:{
immediate: true,
deep:true,
handler(newValue, oldValue){
// 采用reduce来进行依次相加,配合三元计算符过滤没被选的属性
this.totalPrice = this.realShopcart.reduce((pre,item) => pre + (item['isCheck'] ? (item.price * item.number * 100) / 100 : 0), 0).toFixed(2)
}
},
},
methods: {
// 单选全选都是派发action,去操作state中的数据
// 单选
selectItem(index) {
this.$store.dispatch('selectItems',index)
},
// 全选
allSelect() {
this.$store.dispatch('allSelect')
},
// 删除购物车中的商品
deleteGood(name,id){
// 保存this,注意这里也是坑
let _this = this
this.$confirm({
...
详情看antdUI的提示框
});
},
// 跳至商品详情
toDetail(goodsId,status){
status ? this.$router.push(`detail/${goodsId}`) : this.$message.warning('此商品已下架!') // 根据状态判断商品是否下架,并禁用多选框
},
// 结算
pay(){
if(this.realShopcart.every(item => item.isCheck === false)){ // 看看是不是没有选择
this.$message.warning('请选择要结算的商品!')
return
}
const payList = this.realShopcart.filter(item => item.isCheck) // 过滤出被勾选的item
this.$router.push('/comfirmshop') // 跳转到确认单页面
}
},
async mounted() {
// 注意异步操作
// 觉得这个不能根据sessionStroge中的Id去查询购物车列表,防止用户篡改id,拿别人的购物车
await this.$store.dispatch('getUserInfo').then(()=>{
this.userId = this.$store.state.user.userInfo.id
})
await this.$store.dispatch('getShopcartInfo',this.userId) // 先拿id,再派发信息
},
}
购物车长这样: