vue实现多商铺购物车功能

vue实现多商铺购物车功能

2021新起航,请多指教。

效果图

在这里插入图片描述

以下展示案例

  • 项目中有应用到vant组件、引入组件, 需要使用demo时,请自行修改或删除相关代码。
  • 这里分开说明,如需参考根据自行需求调整。
  • style请忽略
  • 自定义弹窗组件请点击

template

<template>
    <div class='cartWrap'>
        <!-- 头部 -->
        <div class="headerBar">
            <div class="navCenter">
                购物车
            </div>
            <div class="navRight" @click="delGoodsTap" v-if="flagCheckGoods.length > 0">
                删除
            </div>
        </div>
        
        <!-- 购物车列表 -->
        <div class="cardList">
            
            <!-- 下拉刷新 -->
            <van-pull-refresh v-model="refreshing" @refresh="onRefresh" success-text="刷新成功">
                <van-list
                    v-model="loading"
                    :finished="finished"
                    :finished-text="finishedTxt"
                    @load="onLoad"
                    :offset="10"
                >
                    <!-- 外层循环 -->
                    <div v-for="(base, bIdx) in cartList" :key="base.id">
                        <div class="checkShopList" >
                            <!-- 店铺信息 -->
                            <div class="shop-info-box bd">
                                <div class="shop-check-box commCheck">
                                    <input class="shopInput" type="checkbox" v-model="base.isCheckShop"  @change="isShopChange(bIdx, base.id)">
                                </div>

                                <div class="shop-info">
                                    <div class="title-flag">
                                        <p class="proprietary" v-if="base.isSelf == 1"></p>
                                        <div class="shop-title">{{base.baseName}}</div>
                                    </div>
                                    <div class="postageDesc">
                                        <span class="red" v-if="parseFloat(base.freightAmount) > 0">满{{base.fullAmount}}元包邮,</span>
                                        <span>{{parseFloat(base.freightAmount) <= 0 ? '包邮' : ('未满需支付'+base.freightAmount+'元邮费')}}</span>
                                    </div>
                                </div>
                            </div>
							
                            <!-- 循环某个店铺下的商品列表 -->
                            <van-swipe-cell
                                :before-close="beforeClose"
                                v-for="(item, index) in base.memberCartList" :key="item.id"
                                v-if="(Number(item.stock) > 0)"
                                :name= "`${item.goodsId},${item.goodsSkuId},${bIdx},${base.id}`"
                            >

                                <div class="checkBox">
                                    <!-- 每个商品的checkbox -->
                                    <div class="goods-check-box commCheck">
                                        <input type="checkbox" v-model="item.isCheckGoods"  @change="isGoodsChange(bIdx, base.id, index)">
                                    </div>
									
                                    <!-- 商品信息 -->
                                    <van-card
                                        :title="item.goodsName"
                                        :thumb="item.goodsMainImage"
                                        @click.stop="toCommTap('goods?id='+ item.goodsId +'&goodsType='+ item.goodsType)"
                                        class="bd"
                                    >
                                        <template #desc>
                                            <div class="checkSize" v-if="parseInt(item.goodsType) == 3">已选:{{item.webOptionname}};{{item.quantity}}件</div>   
                                            <van-tag class="tagBd" v-if="parseInt(item.goodsUserType) == 3" plain text-color="#fe5303">秒杀</van-tag>
                                        </template>
                                        
                                        <template #price>
                                            <div class="price" v-if="parseInt(item.goodsType) != 1">
                                                <p class="memberPrice"><span>{{item.goodsRetailPrice}}</span></p>
                                                <p class="newUserIden" v-if="item.goodsUserType == 1"></p>
                                                <p class="retailPrice" v-if="item.goodsUserType == 3"><span>{{item.goodsMarketPrice}}</span></p>
                                            </div>
                                        </template>
                                        
                                        <template #footer>
                                            <div class="stepperBox">
                                                <van-tag v-if="parseInt(item.goodsType) != 1 && item.goodsUserType == 1" class="tags" color="#fdf0ea" text-color="#fe5303">每天限1份</van-tag>
                                                <!-- 引入步进器组件 -->
                                                <cartcontrol v-else :quantity="Number(item.quantity)" @changeNum="onChange" :goods="item"></cartcontrol>
                                            </div>
                                        </template>
                                        
                                    </van-card>
                                </div>

                                <template #right>
                                    <van-button square text="删除" type="danger" class="delete-button" />
                                </template>
                            </van-swipe-cell>

                        </div>
                    </div>
                </van-list>
            </van-pull-refresh>
                
            <!-- 失效商品 -->
            <div class="loseList" v-if="loseList.length > 0">
                <div class="loseItem" v-for="(item, index) in loseList" :key="item.id">
                    <div class="loseTxt">失效</div>
                    <van-card
                        :title="item.goodsName"
                        :thumb="item.goodsMainImage"
                        class="bd"
                    >
                        <template #desc>
                            <div class="checkSize" v-if="parseInt(item.goodsType) == 3">已选:{{item.webOptionname}};{{item.quantity}}件</div>
                        </template>
                        
                        <template #price>
                            <div class="price price_column" v-if="parseInt(item.goodsType) != 1">
                                <p class="memberPrice"><span>{{item.goodsRetailPrice}}</span></p>
                            </div>
                        </template>
                        
                    </van-card>
                </div>
                <div class="loseBtn" @click="clearLost"><span>清空失效商品</span></div>
            </div>

			<!-- 结算部分 -->
            <div class="submitBar styBar" v-if="cartList.length > 0">
                <van-submit-bar 
                    :price="totalPrice"
                    :button-text="submitBarNum"
                    @submit="onSubmit"
                    :disabled="flagCheckGoods.length == 0 || isShowGreenCartTip"
                    label="不含运费 合计:"
                >
                    <van-checkbox v-model="isCheckAll" @click="checkAll">全选</van-checkbox>
                </van-submit-bar>
            </div>
        </div>

        <!-- 数据为空占位 -->
        <div class="nullBox" v-show="showNullBox">
            <div class="box">
                <img src="../../assets/images/icon/null_cart.png" alt="">
                <div class="des">给购物车加点什么吧</div>
            </div>
        </div>
        
        <!-- 引入弹窗组件 -->
        <popup 
            @cancel="cancelTap"
            @success="confirmTap"
            :isShowPopup="showPopup"
            :content="content"
            class="myPopup"
            >
        </popup>
        
        <!-- 引入tabbar组件 -->
        <footerGuide></footerGuide>
    </div>
</template>

js

初始化数据
  • 引入及注册组件
  • computed计算价格及统计结算商品数量
<script>
import footerGuide from "@/components/common/footer";
import cartcontrol from '../cartcontrol/cartcontrol';
import {SwipeCell, Card, Dialog, SubmitBar } from 'vant';
import popup from '@/components/common/popup'
import { mapMutations, mapState } from 'vuex'
import { setStore, getStore, getToken } from "../../api/utils"
var that;

export default {
    name: "cart",
    data() {
        return {
            content: '', //弹窗的内容
            showPopup: false, //是否显示删除弹窗
            cartList: [], //购物车列表
            loseList: [], //失效商品列表
            loading: false,
            finished: false,
            refreshing: false,
            finishedTxt: '', //没有更多了
            page: 1,
            pageSize: 13,
            showNullBox: false, // 是否显示占位
            isCurrentOperType: '', //是否删除商品 1:删除商品 2:删除失效商品 3:结算商品 4:右滑动删除
            isCheckAll: false, //是否全选
            flagCheckGoods: [], // 标识存储是否有选中的商品id
            isCountNum: 0, // 统计当前结算商品的数量
        }
    },
    components: {
        footerGuide,
        cartcontrol,
        [SwipeCell.name]: SwipeCell,
        [Card.name]: Card,
        [Dialog.name]: Dialog,
        [SubmitBar.name]: SubmitBar,
        popup
    },
    computed: {
        // 统计结算商品数量
        submitBarNum() {
            const count = this.isCountNum;
			
            $('.van-submit-bar__bar .van-button__text .num').remove();
            if(count > 0) {
                let subNum = `<span class="num">(${count})</span>`
                $('.van-submit-bar__bar .van-button__text').append(subNum);
            }
            // return `结算(${count})`
            return `结算`
        },
        // 计算总价格
        totalPrice() {
            let tmpMoney = this.calcPrice();
            return (Math.round(tmpMoney * 100) / 100) * 100; //单位(分) *100
        },
    },
    created() {
        that = this;
        this.loadings();
    }
    inject:['reload'],//注入reload方法
    methods: {
        ...mapMutations(['UPDATE_GOODS']),
        loadings(message = "") {
            this.$toast.loading({
                message: message,
                duration: 0,
                forbidClick: true,
                loadingType: 'spinner'
            });
        },
        toCommTap(url) {
            this.$router.push(url);
        }
   }
}

</script>
获取购物车列表数据
  • 这里使用了vant中的List列表PullRefresh 下拉刷新
  • 返回的数据处理可根据自行的需求进行调整。
/**
 * List 初始化后会触发一次 load 事件
 */
onLoad() {
    // token存在请求数据
    if (this.token) { 
        this.getCartList();
    }
},
    
/**
 * 下拉刷新事件
 */
onRefresh() {
  	this.page = 1;
    this.finished = false;

   	// 重新加载数据
   	// 将 loading 设置为 true,表示处于加载状态
    this.loading = true;
    this.onLoad();
},
    
/**
 * 获取购物车列表数据
 */
getCartList() {
    let params = {
        pageNumber: this.page,
        pageSize: this.pageSize,
    }

     this.axios.cartList(params).then((res) => {
         this.$toast.clear();
         if(parseInt(res.code) != 200) {
             this.$toast(res.msg);
             return;
         }
         // 重置
         if (this.refreshing) {
             this.cartList = [];
             this.loseList = [];
             this.refreshing = false;
         }

         let datas = res.data.goodsList;

         if (datas.length > 0) {
             // 给数组添加默认数据
             datas.forEach((map, key) => {
                 datas[key].isCheckShop = false;
                 map.memberCartList.forEach((child, i) => {
                     map.memberCartList[i].isCheckGoods = false;
                 })
             })
         }

         this.cartList = this.cartList.concat(datas); // 正常商品
         this.loseList = res.data.invalidGoodsList;  // 失效商品

         // 显示暂无数据
         if(this.page == 1 && (datas.length == 0 && that.loseList.length == 0)) {
             this.showNullBox = true;
             this.finishedTxt = '';
         }

         if (datas.length != this.pageSize) {
             //全部加载完成,是否已加载完成,加载完成后不再触发load事件
             this.loading = false;
             this.finished = true;
         }

         this.page++;
         this.loading = false;
     })
},
后端返回数据格式

仅供参考

在这里插入图片描述

操作步进器组件
  • 项目中引入步进器子组件, 在使用组件时把当前操作项数据进行传值
  • items是监听子组件回调数据。
onChange(items) {
    console.log(items);
    // 根据个人需求是否处理逻辑
}

说明:

  • 如果不是通过组件方式时,操作步进器时, 在定义事件时把当前商铺index、商品index进行传参。
  • 例:
<div class="minus" @click.stop="minusNum(bIdx, index)">-</div>
<div class="stepInput">{{quantity}}</div>
<div class="plus" @click.stop="addNum(bIdx, index)">+</div>
minusNum(shopIdx, proIdx) {
    // ... 判断当前数量
    
    that.cartList[shopIdx].memberCartList[proIdx].quantity--;
}
addNum(shopIdx, proIdx) {
    // .... 判断库存相关
    
    that.cartList[shopIdx].memberCartList[proIdx].quantity++;
}
点击某商品时改变选中状态
/**
 * 点击某商品时改变选中状态
 * shopIdx: 当前店铺的index
 * baseId: 当前店铺的id
 * proIdx: 当前商品index
 */
isGoodsChange(shopIdx, baseId, proIdx) {
    this.isShopIdx = shopIdx;
    this.isShopId = baseId;

    let checkShopList = that.cartList[shopIdx].memberCartList.filter((item, key) => {
        if (that.cartList[shopIdx].memberCartList[proIdx].id == item.id) {
            that.isExistGoodsId(item);
        }
        return item.isCheckGoods == true;
    })

    checkShopList.length == that.cartList[shopIdx].memberCartList.length ? (that.cartList[shopIdx].isCheckShop = true) : (that.cartList[shopIdx].isCheckShop = false);

    // 是否选中店铺
    that.isCheckAllShop();
},
选中店铺,把该店铺下所有商品选中
/**
 * 当选中某店铺时,该商铺下的所有商品选中. 反之不选中。
 * shopIdx: 当前店铺的index
 * id: 当前店铺的id
 */
isShopChange(shopIdx, id) {
    this.isShopIdx = shopIdx;
    this.isShopId = id;

    that.cartList[shopIdx].memberCartList.forEach((item) => {
        item.isCheckGoods = that.cartList[shopIdx].isCheckShop;
		
        // 是否存在商品id
        that.isExistGoodsId(item);
    });
	
    // 是否选中店铺
    that.isCheckAllShop();
},
是否存在商品id
isExistGoodsId(item) { // 是否存在商品id
    if (item.isCheckGoods) {
        if (that.flagCheckGoods.indexOf(item.id) < 0) { //商品不存在时,添加
            that.flagCheckGoods.push(item.id);
        }
    } else {
        // 删除商品
        that.flagCheckGoods.splice(that.flagCheckGoods.indexOf(item.id), 1);
    }
}
是否选中所有店铺
isCheckAllShop() {
    // 过滤返回已选中的商店
    let isFilterShop = that.cartList.filter((item, i) => {
        return item.isCheckShop == true;
    })

    // 判断是否选中所有店铺,如选中则改变全选状态
    isFilterShop.length == that.cartList.length ? that.isCheckAll = true : that.isCheckAll = false;

    // 计算价格
    that.calcPrice();
},
全选所有购物车商品

本项目接口中把有效和无效商品分开返回, 所以不用过滤。

/**
 * 全选所有商品事件
 */
checkAll() {
    this.isCheckAll = !this.isCheckAll;

    that.flagCheckGoods = []; // 重置
    that.cartList.forEach((item, i) => {
        item.isCheckShop = that.isCheckAll;
        this.isShopIdx = i;
        this.isShopId = item.id;
        item.memberCartList.forEach((childItem) => {
            childItem.isCheckGoods = that.isCheckAll;
            // 全选时,把对应商品id存入数组
            if (childItem.isCheckGoods) {
                if (that.flagCheckGoods.indexOf(childItem.id) < 0) {
                    that.flagCheckGoods.push(childItem.id);
                }
            } else {
                that.flagCheckGoods = [];
            }
        })
    })
},
操作删除按钮,显示弹窗组件
delGoodsTap() {
    if(this.flagCheckGoods.length == 0) {
        this.$toast('你还没有选择宝贝哟~');
        return;
    }
    this.showPopup = true;
    this.content = '确定删除勾选商品?';
    this.isCurrentOperType = 1;
},
操作删除失效商品
clearLost() { //清空失效商品
    this.showPopup = true;
    this.isCurrentOperType = 2;
    this.content = '确定删除失效商品?';
},
popup组件触发确定事件
/**
 * 取消事件
 */
cancelTap() {
    this.showPopup = false;
},

/**
 * 确定事件
 * 
 */
confirmTap() {
    this.showPopup = false;

    // 处理选中的商品id, 格式: [{},{}]
    let handleData = [];
    if (this.isCurrentOperType == 1) {
        handleData = this.handleSubmitFormat();
    } else { //失效商品
        this.loseList.forEach((item, index) => {
            var tempObj = {};
            tempObj['goodsId'] = item.goodsId
            tempObj['goodsSkuId'] = item.goodsSkuId
            handleData.push(tempObj)
        })
    }

    // 删除商品
    this.deleteGoods(handleData);
},
根据类型处理提交格式
  • 请根据自行需求处理
handleSubmitFormat() { //处理提交接口格式
    let handleData = [];
    this.cartList.forEach((itemW, index)=> {
        itemW.memberCartList.forEach((item) => {
            if(that.flagCheckGoods.indexOf(item.id) !== -1) {
                if (that.isCurrentOperType == 1) { // 删除商品
                    handleData.push({"goodsId": item.goodsId, "goodsSkuId": item.goodsSkuId });
                } else if (that.isCurrentOperType == 3) { // 商品结算
                    handleData.push({"cartId": item.id});
                }
            }
        })
    })
    return handleData;
},
删除商品
deleteGoods(arr) { //删除商品
    let that = this;
    this.loadings();

    let params = {
        item: arr
    }
    this.axios.deleteCartGoods(params).then((res) => {
        that.$toast.clear();
        if(parseInt(res.code) != 200) {
            that.$toast(res.msg);
            return;
        }

        // 更新数据
        that.delHandleFilter(arr);
        that.flagCheckGoods = [];
        this.isShowGreenCartTip = false;

        // 如果操作的某基地下没数据时,过滤基地
        if (that.isCurrentOperType != 2) {
            if (that.cartList[that.isShopIdx].memberCartList.length <= 0) {
                that.cartList = that.cartList.filter((item) => that.isShopId != item.id)
            }
        }
        
		// 显示占位
        if (that.cartList.length == 0 && that.loseList.length == 0) {
            that.showNullBox = true;
        }
        
        // 如果全选,刷新页面
        if (that.isCheckAll) {
            that.reload();
        }

        // 更新本地数据
        let counts = parseInt(res.data.cartTotal);
        that.UPDATE_GOODS(counts)
    })
},
根据类型处理删除商品
 delHandleFilter(arr) { //处理删除类型
     let that = this;
     switch(this.isCurrentOperType) {
         case 1:
             that.isFilterDel();
             break;
         case 2:
             that.loseList = that.loseList.filter(item => arr.indexOf(item.goodsId) != -1);
             break;
         case 4:
             that.isFilterDel(arr);
             break;
     }
 },
根据类型判断删除条件
isFilterDel(arr) {
    that.cartList.forEach((val, index) => {
        val.memberCartList.filter((item) => {
            that.cartList[index].memberCartList = val.memberCartList.filter(item => {
                if (that.isCurrentOperType === 1) {
                    return that.flagCheckGoods.indexOf(item.id) == -1;
                } else if (that.isCurrentOperType === 4) {
                    return arr[0].goodsId !== item.goodsId;
                }
            })
        })
    })
},
结算

选择购物车商品,提交结算,根据需求处理数据

 onSubmit() {
     this.isCurrentOperType = 3;

     this.loadings('提交中...');
     this.isLoading = true;

     let selectGoodsArr = this.handleSubmitFormat();
     setStore('selectGoods', selectGoodsArr);
	
	// item: [{cartId: "1802"}, {cartId: "1516"}]
     let params = {
         item: selectGoodsArr
     }
     this.axios.settleAccounts(params).then((res) => {
         that.$toast.clear();
         if(parseInt(res.code) != 200) {
             this.$toast(res.msg);
             return;
         }
         
         // 跳转订单预览页面
         setStore('settle', res.data);
         that.$router.push('orderPreview');
     })
 },
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值