微信小程序实现商品加入购物车案例

思考:购物车中的数据保存在哪里?用哪种数据结构进行保存?

小程序中可能有多个页面需要对购物车中的数据进行操作,因此我们想到把数据存到全局中。可以使用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)
      }
    })
  } 
},
三、购物车底部工具栏及勾选、全选、总数、总价实现
1、设置购物车状态

计算商品总价时一般四舍五入保留两位,用到了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)
}

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

3、全选

handleAllCheck() {
  let { cartList,allChecked } = this.data
  allChecked = !allChecked
  cartList.forEach(v => v.checked = allChecked)
  // 设置购物车状态
  this.setCart(cartList)
},

4、清空购物车

handleClearCart() {
  let that = this
  wx.showModal({
    content:'确定不要了吗?',
    success(res) {
      if(res.confirm) {
        that.setCart([])
      } else if(res.cancel) {
        console.log('用户点击取消');
      }
    }
  })
},
5、已勾选商品支付成功后清除购物车中对应的数据
 let newCart = wx.getStorageSync('cart').filter(v => !v.checked)
 this.setCart(newCart)

6、当后台管理更新商品信息时购物车中的数据要对应更新
购物车中的数据是保存在本地缓存中的,如果在后台管理修改商品的信息或者删除商品,但是小程序中的缓存没有
及时更新,那么在下单的时候就会出现问题,所以需要在进入商城页面的时候就调用获取所有商品的接口跟购物车缓存中的数据进行对比,然后及时更新。
 

getAllProduct() {
    let data = {}
    ...
    goodsMallFindAll(data).then(res => {
      if(res.data.code === 1) {
        let allProduct = res.data.data
        let cart = wx.getStorageSync('cart') || []
        let allProductId = allProduct.map(e => e.id)
        //过滤掉不存在的商品
        cart = cart.filter(e => allProductId.includes(e.id))
        //商品的数据进行更新
        cart = cart.map(ele => {
          allProduct.map(ele2 => {
            if(ele.id == ele2.id) {
              ele = Object.assign(ele, ele2);
            }
          })
          return ele
        })
        wx.setStorageSync('cart', cart)
        this.setCart()
      } else {
        wx.showToast({
          title: res.data.msg,
          icon: 'none'
        })
      }
    })
  },
四、附上整体代码

(1)wxml文件如下:

 <!-- 商品菜单及列表 -->
<view class="cates">
    <!-- 左侧菜单 -->
    <scroll-view scroll-y class="left_menu">
      <view class="menu_item title">商品列表</view>
      <view class="menu_item {
   
   {index == currentIndex ? 'active' : ''}}" wx:for="{
   
   {menuList}}" wx:key="index" bindtap="handleMenuItemChange" data-index="{
   
   {index}}" data-id="{
   
   {item.id}}">{
   
   {item.name}}
      </view>
    </scroll-view>
    <!-- 右侧列表 -->
    <scroll-view scroll-y class="right_content" scroll-top="{
   
   {scrollTop}}">
      <view class="product-item" wx:for="{
   
   {productList}}" wx:key="index" bindtap="goDetail" data-item="{
   
   {item}}">
        <image class="image" src="{
   
   {item.images}}"></image>
        <view class="info">
          <view class="name">{
   
   {item.name}}</view>
          <view class="remark">{
   
   {item.remark}}</view>
          <view>
            <view class="price">¥{
   
   {item.price}}</view>
            <view wx:if="{
   
   {item.storeCount && item.storeCount != null}}" class="inventory">还剩{
   
   {item.storeCount}}件</view>
          </view>
        </view>
        <view class="stepperBox" catchtap="preventBubbling">
          <van-stepper show-minus="{
   
   {false}}" input-width="0" bind:plus="doPlusNum" data-item="{
   
   {item}}"></van-stepper>
          <view wx:if="{
   
   {item.num}}" class="num">{
   
   {item.num}}</view>
        </view>
      </view>
    </scroll-view>
  </view>
  <!-- 底部固定购物车 -->
  <view class="cart">
    <view class="cart_img_view" bindtap="handleCart">
      <image animation="{
   
   {ani}}" src="/public/image/icon_cart.png" class="cart_img"></image>
      <view class="cart_num" wx:if="{
   
   {totalNum > 0}}">
        {
   
   {totalNum}}
      </view>
    </view>
    <view class="cart_price">¥{
   
   {totalPrice}}</view>
    <view class="cart_text" bindtap="placeTheOrder">去支付</view>
  </view>
  <!-- 购物车展示 -->
  <modal hideModal="{
   
   {hideModal}}">
    <view class="cartBox">
      <view class="top">
        <view class="selectAll">
          <checkbox-group bindchange="handleAllCheck">
            <checkbox color="#fff" checked="{
   
   {allChecked}}"></checkbox>
          </checkbox-group>
          <view>已选购商品({
   
   {totalNum}}件)</view>
        </view>
        <view class="clearCart" bindtap="handleClearCart">
          <image src="/public/image/icon_del.png"></image>
          <view>清空</view>
        </view>
      </view>
      <view class="bottom">
        <view wx:for="{
   
   {cartList}}" wx:key="index" class="cart-item">
          <view class="cart-item-left">
            <checkbox-group bindchange="checkboxChange" data-id="{
   
   {item.id}}">
              <checkbox color="#fff" checked="{
   
   {item.checked}}" value="{
   
   {item.id}}"></checkbox>
            </checkbox-group>
            <view class="cart-item-left-content">
              <image></image>
              <view class="info">
                <view class="name">{
   
   {item.name}}</view>
                <view class="remark">{
   
   {item.remark}}</view>
                <view class="price">¥{
   
   {item.price}}</view>
              </view>
            </view>
          </view>
          <view class="cart-item-right">
            <van-stepper async-change min="0" show-minus="{
   
   {item.num == 0 ? false : true}}" input-width="{
   
   {item.num == 0 ? 0 : 32}}" value="{
   
   {item.num}}" disable-input bind:plus="doPlusNum" bind:minus="doMinusNum" data-item="{
   
   {item}}"></van-stepper>
          </view>
        </view>
      </view>
    </view>
  </modal>

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

  // 获取所有商品更新缓存购物车数据
  getAllProduct() {
    let data = {}
    data.shopId = wx.getStorageSync('shop').id
    goodsMallFindAll(data).then(res => {
      if(res.data.code === 1) {
        let allProduct = res.data.data
        let cart = wx.getStorageSync('cart') || []
        let allProductId = allProduct.map(e => e.id)
        cart = cart.filter(e => allProductId.includes(e.id))
        cart = cart.map(ele => {
          allProduct.map(ele2 => {
            if(ele.id == ele2.id) {
              ele = Object.assign(ele, ele2);
            }
          })
          return ele
        })
        wx.setStorageSync('cart', cart)
        this.setCart()
      } 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()
  }
})

效果图如下:

猜你喜欢

转载自blog.csdn.net/aaa123aaasqw/article/details/133944755