思考:
购物车中的数据保存在哪里?用哪种数据结构进行保存?
小程序中可能有多个页面需要对购物车中的数据进行操作,因此我们想到把数据存到全局中。可以使用
wx.setStorageSync()
储存,用wx.getStorageSync()
进行获取,以数组格式方便对数据进行操作。
单件商品信息存在{}
中,在加入购物车的时候还需要加入两个字段为num
代表商品数量,checked
代表是否选中(购物车中可以选中商品进行支付),加入后要重新设置购物车的状态
。
doPlusNum(e) {
// 选中的商品信息
let productInfo = e.currentTarget.dataset.item
// 先获取缓存中的商品信息
let cart = wx.getStorageSync('cart') || []
// 判断当前商品是否第一次添加
let index = cart.findIndex(v => v.id === productInfo.id)
if(index === -1) {
// 第一次添加则把商品信息及初始化的数量和选中状态一起存入
cart.push({...productInfo,num: 1,checked: true})
} else {
// 前面添加过的话只需要更改商品中的数量即可
cart[index].num = cart[index].num + 1
}
// 把更改后的购物车数据重新存入缓存
wx.setStorageSync('cart', cart)
this.setData({cartList: cart})
wx.showToast({
title: '商品已放入购物车',
icon: 'none'
})
// 加入购物车给购物车加一个抖动的动画
this.cartWwing()
// 设置购物车状态(勾选、全选、总数、总价)
this.setCart()
},
在移出购物车的时候需要判断购物车中对应商品的状态,有多件商品则只需更改数量,只有一件商品则直接移除商品信息,最后要重新设置购物车的状态
。
doMinusNum(e) {
let that = this
let productInfo = e.currentTarget.dataset.item
let cart = wx.getStorageSync('cart') || []
// 找到缓存中对应的商品
let index = cart.findIndex(v => v.id === productInfo.id)
// 商品数量大于1则直接减去数量,然后设置购物车状态
if(cart[index].num > 1) {
cart[index].num--;
this.setCart(cart)
} else if(cart[index].num == 1) {
// 商品数量为1则给出弹窗提示
cart[index].num = 0
wx.showModal({
content: '确定不要了吗?',
success(res) {
if(res.confirm) {
// 确定移出则删除对应商品信息后设置购物车状态
cart.splice(index,1)
} else if(res.cancel) {
// 取消后商品数量不做改变
cart[index].num = 1
}
that.setCart(cart)
}
})
}
},
计算商品总价时一般四舍五入保留两位,用到了getRoundeNumber
方法
setCart(cart) {
cart = cart ? cart : wx.getStorageSync('cart') || []
if(cart.length === 0) {
this.setData({hideModal: true})
}
let allChecked = true,totalNum = 0,totalPrice = 0
cart.forEach(v => {
if(v.checked) {
// 计算已经勾选商品的总价及总数
totalPrice += getRoundeNumber(v.price * v.num) * 1
totalNum += v.num
} else {
// 购物车中存在商品且没有商品被勾选,则全选按钮取消勾选
allChecked = false
}
})
// 购物车中不存在商品,则全选按钮取消勾选
allChecked = cart.length != 0 ? allChecked : false
wx.setStorageSync('cart', cart)
this.setData({
allChecked,
totalNum,
totalPrice,
cartList: cart
})
this.handleList()
},
附:getRoundeNumber
方法如下
const getRoundeNumber = num => {
if (!Number.prototype._toFixed) {
Number.prototype._toFixed = Number.prototype.toFixed
}
Number.prototype.toFixed = function(n) {
return (this + 1e-14)._toFixed(n)
}
return Number(num).toFixed(2)
}
handleCheck(e) {
let { id } = e.currentTarget.dataset
let cartList = JSON.parse(JSON.stringify(this.data.cartList))
let index = cartList.findIndex(v => v.id === id)
cartList[index].checked = !cartList[index].checked
// 设置购物车状态
this.setCart(cartList)
},
handleAllCheck() {
let { cartList,allChecked } = this.data
allChecked = !allChecked
cartList.forEach(v => v.checked = allChecked)
// 设置购物车状态
this.setCart(cartList)
},
handleClearCart() {
let that = this
wx.showModal({
content:'确定不要了吗?',
success(res) {
if(res.confirm) {
that.setCart([])
} else if(res.cancel) {
console.log('用户点击取消');
}
}
})
},
let newCart = wx.getStorageSync('cart').filter(v => !v.checked)
this.setCart(newCart)
(1)wxml文件如下:
商品列表
{{item.name}}
{{item.name}}
{{item.remark}}
¥{{item.price}}
还剩{{item.storeCount}}件
{{item.num}}
{{totalNum}}
¥{{totalPrice}}
去支付
已选购商品({{totalNum}}件)
清空
{{item.name}}
{{item.remark}}
¥{{item.price}}
(2)scss文件如下:
在小程序中直接使用scss
语法是不支持的,需要进行一系列操作,具体的可参考微信开发者工具中使用scss一文。
.cates {
display: flex;
height: calc(100vh - 390rpx);
.left_menu {
background-color: #eeeeee;
width: 187rpx;
.menu_item {
display: flex;
justify-content: center;
align-items: center;
font-size: 30rpx;
height: 80rpx;
}
.active {
font-weight: bolder;
color: var(--themeColor);
background-color: #fff;
}
.title {
color: #1A1A1A;
font-size: 28rpx;
font-weight: bold;
background-color: none;
}
}
.right_content {
width: calc(100% - 187rpx);
padding: 0 20rpx;
box-sizing: border-box;
.product-item {
display: flex;
align-items: center;
gap: 30rpx;
height: 210rpx;
box-sizing: border-box;
position: relative;
border-bottom: 1rpx solid #eeeeee;
padding: 35rpx 0;
.image {
width: 140rpx;
height: 140rpx;
border-radius: 100%;
border: 1rpx solid var(--themeColor);
}
.info {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
.name {
font-weight: bold;
font-size: 28rpx;
}
.remark {
color: #767676;
font-size: 24rpx;
}
.price {
display: inline-block;
color: #B08657;
font-size: 28rpx;
}
.inventory {
display: inline-block;
font-size: 24rpx;
color: #c5c5c5;
margin-left: 20rpx;
}
}
.van-stepper {
position: absolute;
right: 10rpx;
bottom: 10rpx;
.van-stepper__input {
display: none;
}
}
.num {
position: absolute;
right: 0rpx;
bottom: 45rpx;
width: 35rpx;
height: 35rpx;
border-radius: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #c3a07a;
color: #fff;
font-size: 16rpx;
}
}
}
}
.cart {
position: fixed;
bottom: 40rpx;
left: 50%;
transform: translate(-50%);
z-index: 9999;
width: 710rpx;
height: 140rpx;
background-color: #fff;
border-radius: 92rpx;
display: flex;
align-items: center;
z-index: 99;
.cart_img_view {
display: flex;
justify-content:center;
align-items:Center;
position: relative;
width: 120rpx;
height: 120rpx;
border-radius: 100%;
background-color: var(--themeColor);
margin-left: 22rpx;
.cart_img {
width: 64rpx;
height: 58rpx;
}
.cart_num {
position: absolute;
width: 40rpx;
height: 40rpx;
top: -10rpx;
right: -20rpx;
background-color: #c1a077;
padding: 2.5rpx;
border-radius: 100%;
display: flex;
justify-content:center;
align-items:Center;
color: #fff;
font-size: 25rpx;
border: 1rpx solid #fff;
}
}
.cart_price {
margin-left: 40rpx;
color: #3D3D3D;
font-size: 36rpx;
font-weight: 500;
}
.cart_text {
position: absolute;
right: 0;
top: 0;
width: 190rpx;
height: 100%;
border-radius: 0rpx 92rpx 92rpx 0rpx;
background-color: var(--themeColor);
font-size: 28rpx;
color: white;
display: flex;
justify-content:center;
align-items:Center;
}
}
.popup-content-class {
padding: 0 !important;
}
.cartBox {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
z-index: 999;
max-height: 80%;
overflow-y: scroll;
padding-bottom: 250rpx;
.top {
position: -webkit-sticky;
position: sticky;
top: 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
border-bottom: 1rpx solid rgba(180, 180, 180,0.3);;
.selectAll {
display: flex;
align-items: center;
}
.clearCart {
display: flex;
align-items: center;
gap: 10rpx;
color: #b3b3b3;
font-size: 28rpx;
image {
width: 42rpx;
height: 43rpx;
}
}
}
.bottom {
padding: 30rpx;
.cart-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 20rpx;
.cart-item-left {
display: flex;
align-items: center;
gap: 30rpx;
.cart-item-left-content {
display: flex;
gap: 10rpx;
image {
width: 126rpx;
height: 126rpx;
border: 1rpx solid #eeeeee;
}
.info {
display: flex;
flex-direction: column;
justify-content: space-between;
.name {
font-size: 28rpx;
color: #333333;
}
.remark {
font-size: 24rpx;
color: #767676;
}
.price {
font-size: 28rpx;
color: #B08657;
}
}
}
}
}
}
}
.van-stepper__minus,.van-stepper__plus {
border-radius: 100% !important;
width: 45rpx !important;
height: 45rpx !important;
}
.van-stepper__minus {
border: 1rpx solid #d8d8d8 !important;
color: #d8d8d8 !important;
font-weight: bold !important;
}
.van-stepper__plus {
background-color: var(--themeColor) !important;
color: #fff !important;
}
.van-stepper__input {
background-color: #fff !important;
color: #353535 !important;
font-weight: bold !important;
}
/* 多选框 */
.wx-checkbox-input {
width: 40rpx !important;
height: 40rpx !important;
border-radius: 100% !important;
background-color: #fff !important;
}
.wx-checkbox-input.wx-checkbox-input-checked {
width: 40rpx !important;
height: 40rpx !important;
background-color: var(--themeColor) !important;
}
(3)js文件如下:
Page({
/**
* 页面的初始数据
*/
data: {
menuList:[],
productList: [],
cartList: [],
currentIndex: 0,
currentGroupId: "",
baseUrl: "",
scrollTop: 0,
hideModal: true,
ani: '',
totalNum: 0, // 已选商品数量
totalPrice: 0, // 已选商品总金额
allChecked: true,
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.getGoodsGroup()
},
// 获取商品分组
getGoodsGroup() {
...
goodsGroupFindAll(data).then(res => {
if(res.data.code === 1) {
this.setData({menuList: res.data.data.content}
if(this.data.currentGroupId) {
this.getProductList(this.data.currentGroupId)
} else {
this.getProductList(res.data.data.content[0].id)
}
} else {
wx.showToast({
title: res.data.msg,
icon: 'none'
})
}
})
},
// 获取商品列表
getProductList(groupId) {
let data = {}
data.groupId = groupId
goodsMallFindAll(data).then(res => {
if(res.data.code === 1) {
...
this.setData({productList: res.data.data})
} else {
wx.showToast({
title: res.data.msg,
icon: 'none'
})
}
})
},
// 购物车回填商品列表数据
handleList() {
let cart = wx.getStorageSync('cart') || []
let productList = this.data.productList.map(item => {
delete item.num
return item
})
productList.map(item => {
cart.map(v => {
if(item.id === v.id) {
item.num = v.num
}
})
})
this.setData({productList})
},
// 点击侧边栏
handleMenuItemChange(e) {
let {index,id} = e.currentTarget.dataset
this.setData({
currentIndex: index,
currentGroupId: id,
scrollTop: 0
})
this.getProductList(id)
},
// 点击购物车
handleCart() {
this.setData({
cartList: wx.getStorageSync('cart'),
})
if(wx.getStorageSync('cart') && wx.getStorageSync('cart').length != 0) {
this.setData({hideModal: false})
} else {
wx.showToast({
title: '请添加商品',
icon: 'none'
})
}
},
// 阻止事件冒泡
preventBubbling() {},
// 加入购物车
doPlusNum(e) {
console.log(e);
let productInfo = e.currentTarget.dataset.item
let cart = wx.getStorageSync('cart') || []
let index = cart.findIndex(v => v.id === productInfo.id)
if(index === -1) {
cart.push({...productInfo,num: 1,checked: true})
} else {
cart[index].num = cart[index].num + 1
}
wx.setStorageSync('cart', cart)
this.setData({cartList: cart})
wx.showToast({
title: '商品已放入购物车',
icon: 'none'
})
this.cartWwing()
this.setCart()
},
// 移除出购物车
doMinusNum(e) {
let that = this
console.log(e);
let productInfo = e.currentTarget.dataset.item
let cart = wx.getStorageSync('cart') || []
let index = cart.findIndex(v => v.id === productInfo.id)
if(cart[index].num > 1) {
cart[index].num--;
this.setCart(cart)
} else if(cart[index].num == 1) {
cart[index].num = 0
wx.showModal({
content: '确定不要了吗?',
success(res) {
if(res.confirm) {
cart.splice(index,1)
} else if(res.cancel) {
cart[index].num = 1
}
that.setCart(cart)
}
})
}
},
// 设置购物车状态
setCart(cart) {
cart = cart ? cart : wx.getStorageSync('cart') || []
if(cart.length === 0) {
this.setData({hideModal: true})
}
let allChecked = true,totalNum = 0,totalPrice = 0
cart.forEach(v => {
if(v.checked) {
totalPrice += getRoundeNumber(v.price * v.num) * 1
totalNum += v.num
} else {
allChecked = false
}
})
allChecked = cart.length != 0 ? allChecked : false
wx.setStorageSync('cart', cart)
this.setData({
allChecked,
totalNum,
totalPrice,
cartList: cart
})
this.handleList()
},
// 加入购物车动画
cartWwing: function(){
var animation = wx.createAnimation({
duration: 100,
timingFunction: 'ease-in'
})
animation.translateX(6).rotate(21).step()
animation.translateX(-6).rotate(-21).step()
animation.translateX(0).rotate(0).step()
// 导出动画
this.setData({
ani: animation.export()
})
},
// 购物车勾选
checkboxChange(e) {
console.log(e);
let { id } = e.currentTarget.dataset
let cartList = JSON.parse(JSON.stringify(this.data.cartList))
let index = cartList.findIndex(v => v.id === id)
cartList[index].checked = !cartList[index].checked
this.setCart(cartList)
},
// 全选
handleAllCheck() {
let { cartList,allChecked } = this.data
allChecked = !allChecked
cartList.forEach(v => v.checked = allChecked)
this.setCart(cartList)
},
// 清空购物车
handleClearCart() {
let that = this
wx.showModal({
content:'确定不要了吗?',
success(res) {
if(res.confirm) {
that.setCart([])
} else if(res.cancel) {
console.log('用户点击取消');
}
}
})
},
// 支付跳转
placeTheOrder() {
let data = {}
...
orderGoodsInsert(data).then(res => {
if(res.data.code === 1) {
...
// 删除缓存中已经下单的商品
let newCart = wx.getStorageSync('cart').filter(v => !v.checked)
this.setCart(newCart)
} else {
wx.showToast({
title: res.data.msg,
icon: 'none'
})
}
})
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
this.setCart()
}
})
效果图如下: