Vue2购物车的实现

559 阅读1分钟

加入购物车这一块不像尚品汇一样,加入购物车后,不跳转至确认页面,而是在购物车图标上添加小红点+弹窗提示。

小红点主要还是由vuex进行控制。

image.png

在点击加入购物车后,触发以下逻辑:

// 加入购物车
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仓库中,仓库里负责操作数据,组件里只负责派发,读取数据

注意:如果后端传来的数据项中没有isCheckallcheck这些判断是否选上的flag,可以在getter中进行处理,在vue组件中用mapGetters获取

这里先讲讲各个操作的逻辑,购物车包含以下几个操作:

  1. 多选,多选按钮的操作包括:

    • 将多选按钮上的isCheck赋反,使用
    • 判断是否所有的多选框都被选上了,可以用every方法
    • 先将总价初始化(改为0),然后可以使用foreach方法进行累加运算。(金额操作还是老套路,记得*100/100)
    • 最后注意全部计算完再保留两位小数,用toFixed转化后,数据类型会被变为string类型,这里也是个小坑
  2. 全选,全选按钮包括:

    • 把全选按钮上的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组件:

理清一下思路:

  1. 先拿到用户的id,再去后端请求购物车列表数据,注意异步操作。
  1. 拿到后用mapGetter获取购物车的数据,再进行渲染。
  2. method就放多选、全选、删除等操作,派发action通知仓库进行操作。
  3. 注意对allcheckedtotalPrice这些计算属性的getter和setter写全。
  4. 核心是对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,再派发信息
    },
}

购物车长这样:

image.png