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');
})
},