您现在的位置是:首页 >技术教程 >微信小程序实现商品加入购物车案例网站首页技术教程

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

落雪小轩韩 2024-07-22 00:01:02
简介微信小程序实现商品加入购物车案例

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

小程序中可能有多个页面需要对购物车中的数据进行操作,因此我们想到把数据存到全局中。可以使用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('用户点击取消');
      }
    }
  })
},

4、已勾选商品支付成功后清除购物车中对应的数据

 let newCart = wx.getStorageSync('cart').filter(v => !v.checked)
 this.setCart(newCart)

四、附上整体代码

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

  // 购物车回填商品列表数据
  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()
  }
})

效果图如下:

在这里插入图片描述在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。