8 | 小程序实战之购物车(收货地址授权 + 商品全选反选 + 总价格和总数量的计算 + 结算按钮功能)

8.1 效果

在这里插入图片描述

8.2 收货地址

8.2.1 收获地址按钮样式

注意在cart文件夹下

<!-- 收货地址 -->
<view class="revice_address_row">
  <view class="address_btn">
    <button
     bindtap="handleChooseAddress"
     type="primary"
     plain
    >获取收货地址
    </button>
  </view>
</view>
.revice_address_row{
  .address_btn{ 
    padding: 20rpx;
    button{
      width: 60%;
    }
  }
}
8.2.2 使用es7的async语法获取收获地址
(1)思路

1 绑定点击事件
2 调用小程序内置 api 获取用户的收货地址 wx.chooseAddress(但单单用这种方法不行,取消后无法再去授权)
3 获取用户对小程序所授予获取地址的权限状态(scope) wx.getSetting (用这种方法

  1. 假设用户点击获取收货地址的提示框 确定 authSetting scope.address
    scope 值 true 直接调用 获取收货地址

  2. 假设用户从来没有调用过收货地址的api
    scope undefined 直接调用 获取收货地址

  3. 假设用户点击获取收货地址的提示框 取消
    scope 值 false

    1. 诱导用户自己打开授权设置页面(wx.openSetting) 当用户重新给予获取地址权限的时候
    2. 获取收货地址
  4. 把获取到的收货地址存入到本地存储中 wx.setStorageSync

(2)代码
  1. 在util文件夹下新建一个asyncWx.js 用来写微信的内置api请求
// 获取配置信息
export const getSetting=()=>{
  return new Promise((resolve,reject)=>{
    wx.getSetting({
      success: (result) => {
        resolve(result);
      },
      fail: (err) =>{
        reject(err);
      }
    });
  })
}
// 跳出是否允许访问地址的弹窗
export const chooseAddress=()=>{
  return new Promise((resolve,reject)=>{
    wx.chooseAddress({
      success: (result) => {
        resolve(result);
      },
      fail: (err) =>{
        reject(err);
      }
    });
  })
}
// 打开设置界面
export const openSetting=()=>{
  return new Promise((resolve,reject)=>{
    wx.openSetting({
      success: (result) => {
        resolve(result);
      },
      fail: (err) => {
        reject(err);
      }
    });
  })
}
  1. 在 index.js 中使用es7的async语法写获取收获地址的流程
// 引入用来发送请求的方法 一定要把路径补全
import {getSetting,chooseAddress,openSetting} from "../../utils/asyncWx.js"
import regeneratorRuntime from "../../lib/runtime/runtime.js";
Page({
  /**
   * 页面的初始数据
   */
  data: {
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
  },
  async handleChooseAddress(e){
    try {
      // 1 获取权限状态
      const res1 = await getSetting(); 
      // 发现一些属性名称很怪异的时候,都要使用 []形式来获取属性
      const scopeAddress = res1.authSetting["scope.address"]
      // 2 判断权限状态
      if(scopeAddress === false){
        await openSetting();
      }
      // 4 调用获取收获地址的 api
      const address = await chooseAddress();
      // 5 存入到缓存中
      wx.setStorageSync('address', address);
    } catch (error) {
      console.log(error);
    }
  }
})

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

8.2.3 收获地址和按钮切换显示
(1)思路

本地存储中有地址数据,不显示按钮显示地址信息;否则,显示按钮不显示地址信息

(2)代码
<!-- 收货地址 -->
<view class="revice_address_row">
  <!-- 当收货地址不存在 按钮显示 因为空对象bool类型也是true 所以写 address.userName  -->
  <view class="address_btn" wx:if="{{!address.userName}}">
    <button
     bindtap="handleChooseAddress"
     type="primary"
     plain
    >获取收货地址
    </button>
  </view>
  <!-- 当收货地址 存在 详细信息就显示 -->
  <view wx:else class="user_info_row">
    <view class="user_info">
      <view>{{address.userName}}</view>
      <view>{{address.all}}</view>
    </view>
    <view class="user_phone">{{address.telNumber}}</view>
  </view>
</view>
.revice_address_row{
  .address_btn{ 
    padding: 20rpx;
    button{
      width: 60%;
    }
  }
  .user_info_row{
    display: flex;
    padding: 20rpx;
    .user_info{
      flex: 5;
    }
    .user_phone{
      flex: 3;
      text-align: right;
    }
  }
}
// 引入用来发送请求的方法 一定要把路径补全
import {getSetting,chooseAddress,openSetting} from "../../utils/asyncWx.js"
import regeneratorRuntime from "../../lib/runtime/runtime.js";
Page({
  data: {
    address:{}
  },
  /*
    使用onShow周期函数是因为购物车需要频繁被打开和隐藏,
    重新打开可以初始化页面
  */
  onShow(){
    // 1 获取缓存中的收货地址信息
    const address = wx.getStorageSync('address');
    this.setData({
      address
    })
  },
  async handleChooseAddress(e){
    try {
      // 1 获取权限状态
      const res1 = await getSetting(); 
      // 发现一些属性名称很怪异的时候,都要使用 []形式来获取属性
      const scopeAddress = res1.authSetting["scope.address"]
      // 2 判断权限状态
      if(scopeAddress === false){
        await openSetting();
      }
      // 4 调用获取收获地址的 api
      let address = await chooseAddress();
      address.all = address.provinceName + address.cityName + address.countyName + address.detailInfo;
      // 5 存入到缓存中
      wx.setStorageSync('address', address);
    } catch (error) {
      console.log(error);
    }
  }
})

8.3 购物车内容动态渲染

8.3.1 插件

这里推荐一个插件css tree,当你要写页面样式时,你可以使用这个插件在你的wxml页面选择准备要写样式的组件,ctrl+shift+p 选择css tree 即可看到生成的css样式文件,你可复制它再到主样式文件中粘贴,当然,如果想要删除相同的多余文字,可以选择它并按住ctrl + shift + L 多选删除
在这里插入图片描述

8.3.2 代码
<!-- 购物车内容 -->
<view class="cart_content">
  <view class="cart_title">购物车</view>
  <view class="cart_main">
    <view
     class="cart_item"
     wx:for="{{cart}}"
     wx:key="goods_id"
    >
      <!-- 复选框 -->
      <view class="cart_chk_wrap">
        <checkbox-group data-id="{{item.goods_id}}" bindchange="handeItemChange">
          <checkbox checked="{{item.checked}}"></checkbox>
        </checkbox-group>
      </view>
      <!-- 商品图片 -->
      <navigator class="cart_img_wrap">
        <image mode="widthFix" src="{{item.goods_small_logo}}"></image>
      </navigator>
      <!-- 商品信息 -->
      <view class="cart_info_wrap">
        <view class="goods_name">{{item.goods_name}}</view>
        <view class="goods_price_wrap">
          <view class="goods_price">¥{{item.goods_price}}</view>
          <view class="cart_num_tool">
            <view class="num_edit">-</view>
            <view class="goods_num">{{item.num}}</view>
            <view class="num_edit">+</view>
          </view>
        </view>
      </view>
    </view>
  </view>
</view>
.cart_content {
  .cart_title {
    padding: 20rpx;
    font-size: 36rpx;
    color: var(--themeColor);
    border-top: 1rpx solid currentColor;
    border-bottom: 1rpx solid currentColor;
  }
  .cart_main {
    .cart_item {
      display: flex;
      padding: 10rpx;
      border-bottom: 1rpx solid #ccc;
      .cart_chk_wrap {
        flex: 1;
        display: flex;
        justify-content: center;
        align-items: center;
        checkbox-group {
          checkbox {
          }
        }
      }
      .cart_img_wrap {
        flex: 2;
        display: flex;
        justify-content: center;
        align-items: center;
        image {
          width: 80%;
        }
      }
      .cart_info_wrap {
        flex: 4;
        display: flex;
        flex-direction: column;
        justify-content: space-around;
        .goods_name {
          display: -webkit-box;
          overflow: hidden;
          -webkit-box-orient: vertical;
          -webkit-line-clamp:2;
          color: #666;
        }
        .goods_price_wrap {
          display: flex;
          justify-content: space-between;
          .goods_price {
            color: var(--themeColor);
            font-size: 34rpx;
          }
          .cart_num_tool {
            display: flex;
            .num_edit {
              width: 55rpx;
              height: 55rpx;
              display: flex;
              justify-content: center;
              align-items: center;
              border: 1rpx solid #ccc;
            }
            .goods_num {
              width: 55rpx;
              height: 55rpx;
              display: flex;
              justify-content: center;
              align-items: center;
            }
          }
        }
      }
    }
  }
}
data: {
    address:{},
    cart:[]
  },
  /*
    使用onShow周期函数是因为购物车需要频繁被打开和隐藏,
    重新打开可以初始化页面
  */
  onShow(){
    // 1 获取缓存中的收货地址信息
    const address = wx.getStorageSync('address');
    // 2 获取缓存中的购物车数组
    const cart = wx.getStorageSync('cart');
    this.setData({
      address,
      cart
    })
  },

在这里插入图片描述

8.4 购物车底部工具栏

8.4.1 页面布局
<!-- 底部工具栏 -->
<view class="footer_tool">
  <!-- 全选 -->
  <view class="all_chk_wrap">
    <checkbox-group>
      <checkbox>全选</checkbox>
    </checkbox-group>
  </view>
  <!-- 总价格 -->
  <view class="total_price_wrap">
    <view class="total_price">
      合计:
      <text class="total_price_text">¥999</text>
    </view>
    <view>包含运费</view>
  </view>
  <!-- 结算 -->
  <view class="order_pay_wrap">
    结算(2)
  </view>
</view>
.footer_tool {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 90rpx;
  background-color: #fff;
  display: flex;
  border-top: 1rpx solid #ccc;
  .all_chk_wrap {
    flex: 2;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .total_price_wrap {
    flex: 5;
    padding-right: 15rpx;
    text-align: right;
    .total_price {
      .total_price_text {
        color: var(--themeColor);
        font-size: 34rpx;
        font-weight: 600;
      }
    }
  }
  .order_pay_wrap {
    flex: 3;
    background-color: var(--themeColor);
    color: #fff;
    font-size: 32rpx;
    font-weight: 600;
    display: flex;
    justify-content: center;
    align-items: center;
  }
}

在这里插入图片描述

8.4.2 全选的数据展示
8.4.2.1 效果

如果购物车的商品都被选中,则全选按钮也会自动选上,否则,全选按钮不会被选上

8.4.2.2 思路

1 在 onShow 周期函数中获取缓存中的购物车数组
2 根据购物车中的商品数据 所有的商品都被选中 checked=true 全选就被选中

8.4.2.3 代码

(1)因为购物车商品的复选框,应该在加入购物车时,同时被选上,所以需在商品详情的index.js文件中加入 this.GoodsInfo.checked = true; 然后,在购物车的index.wxml 中加入

//点击 加入购物车
  handleCartAdd(e){
    // 1 获取缓存中的购物车数据 数组格式 如果没有赋予空数组
    let cart = wx.getStorageSync('cart') || [];
    // 2 判断 商品对象是否存在于购物车中
    // findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。
    let index = cart.findIndex(v=>v.goods_id === this.GoodsInfo.goods_id);
    console.log(index);
    if(index === -1){
      // 3 不存在 第一次添加
      this.GoodsInfo.num = 1;
      this.GoodsInfo.checked = true;
      ...
        <!-- 复选框 -->
      <view class="cart_chk_wrap">
        <checkbox-group data-id="{{item.goods_id}}">
          <checkbox checked="{{item.checked}}"></checkbox>
        </checkbox-group>

(2)全选的行为

data: {
    address:{},
    cart:[],
    allChecked: false
  },
  
  /*
    使用onShow周期函数是因为购物车需要频繁被打开和隐藏,
    重新打开可以初始化页面
  */
onShow(){
    // 1 获取缓存中的收货地址信息
    const address = wx.getStorageSync('address');
    const cart = wx.getStorageSync('cart') || [];
    // 计算全选
    // every 是遍历数组方法 会接收一个回调函数 如果每个回调函数都返回true 那么every方法的返回值为true
    // 注意: 空数组调用every,返回值也为 true
    const allChecked = cart.length?cart.every(v=>v.checked):false;
    this.setData({
      address,
      cart,
      allChecked
    })
  },
<!-- 全选 -->
  <view class="all_chk_wrap">
    <checkbox-group>
      <checkbox checked="{{allChecked}}">全选</checkbox>
    </checkbox-group>
  </view>

在这里插入图片描述

8.4.3 总价格和总数量
8.4.3.1 效果

计算所有被选中的商品总价和总数量

8.4.3.2 思路

1 商品都需要被选中 我们才拿它来计算
2 获取购物车数组
3 遍历
4 判断商品是否被选中
5 总价格 += 商品的单价 * 商品的数量
6 总数量 += 商品的数量
7 把计算出来的价格和数量返回到data中

8.4.3.3 代码
data: {
    address:{},
    cart:[],
    allChecked: false,
    totalPrice: 0,
    totalNum: 0
  },
  /*
    使用onShow周期函数是因为购物车需要频繁被打开和隐藏,
    重新打开可以初始化页面
  */
  onShow(){
    // 1 获取缓存中的收货地址信息
    const address = wx.getStorageSync('address');
    const cart = wx.getStorageSync('cart') || [];
    // 计算全选
    // every 是遍历数组方法 会接收一个回调函数 如果每个回调函数都返回true 那么every方法的返回值为true
    // 注意: 空数组调用every,返回值也为 true
    // 为了提高性能,不用那么多个循环
    // const allChecked = cart.length?cart.every(v=>v.checked):false;
    let allChecked = true;
    // 总价格和总数量
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v=>{
      if(v.checked){
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      }else{
        allChecked = false;
      }
    })
    // 判断数组是否为空
    allChecked = cart.length!=0?allChecked:false;
    this.setData({
      address,
      cart,
      allChecked,
      totalPrice,
      totalNum
    })
  },
<!-- 总价格 -->
  <view class="total_price_wrap">
    <view class="total_price">
      合计:
      <text class="total_price_text">¥{{totalPrice}}</text>
    </view>
    <view>包含运费</view>
  </view>
  <!-- 结算 -->
  <view class="order_pay_wrap">
    结算({{totalNum}})
  </view>

在这里插入图片描述

然而,这还不行,你点复选框,总价和数量不会改变

8.4.4 商品选中
8.4.4.1 效果

只计算被选中的商品的总价格和数量,没被选中的商品不计算,简单来说是,利用 8.4.3 总价格和总数量 结合复选框实现总价和数量的计算

8.4.4.2 思路

1 绑定复选框的change事件
2 获取到被修改的商品对象
3 商品对象的选中状态 取反
4 重新填充回data中和缓存中
5 重新计算全选、总价格和总数量

8.4.4.3 代码

(1)绑定复选框的change事件

        <!-- 复选框 -->
      <view class="cart_chk_wrap">
        <checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
          <checkbox checked="{{item.checked}}"></checkbox>
        </checkbox-group>
      </view>

(2)获取到被修改的商品对象、商品对象的选中状态 取反、重新计算全选、总价格和总数量

// 商品选中
  handleItemChange(e){
    // 获取商品的 goods_id
    const goods_id = e.currentTarget.dataset.id;
    // 获取购物车数组
    let {cart} = this.data;
    // 获取被修改的商品对象
    let index = cart.findIndex(v=>v.goods_id===goods_id);
    // 被选中状态取反
    cart[index].checked = !cart[index].checked;
    // 重新计算总价格和总数量
    this.setCart(cart);
  },

(3)为了降低代码冗余,定义一个用来重新计算总价格和总数量的方法

 // 计算总价格和总数量
  setCart(cart){
    let allChecked = true;
    // 总价格和总数量
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v=>{
      if(v.checked){
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      }else{
        allChecked = false;
      }
    })
    // 判断数组是否为空
    allChecked = cart.length!=0?allChecked:false;
    this.setData({
      cart,
      allChecked,
      totalPrice,
      totalNum
    })
    wx.setStorageSync('cart', cart)
  }

在这里插入图片描述

8.4.5 全选和反选
8.4.5.1 效果

全选就会计算购物车中所有商品的总价和总数量,再点一次全选即取消全选,就什么都不买

8.4.5.2 思路

1 全选复选框绑定事件 change
2 获取 data中的全选变量 allChecked
3 直接取反 allChecked=!allChecked
4 遍历购物车数组 让里面 商品 选中状态跟随 allChecked 改变而改变
5 把购物车数组 和 allChecked 重新设置回data 把购物车重新设置回 缓存中

8.4.5.3 代码
<!-- 全选 -->
  <view class="all_chk_wrap">
    <checkbox-group bindchange="handleAllChange">
      <checkbox checked="{{allChecked}}">全选</checkbox>
    </checkbox-group>
  </view>
// 全选和反选
  handleAllChange(){
    // 1 获取data中的数据
    let { cart, allChecked } = this.data;
    // 2 修改值
    allChecked = !allChecked;
    // 3 循环修改cart数组 中的商品选中状态
    cart.forEach(v => v.checked = allChecked);
    // 4 把修改后的值 填充回data或者缓存中
    this.setCart(cart);
  },

8.5 商品数量编辑

8.5.1 思路

1 “+” “-” 按钮 绑定同一个点击事件 区分的关键 自定义属性
1 “+” “+1”
2 “-” “-1”
2 传递被点击的商品id goods_id
3 获取data中的购物车数组 来获取需要被修改的商品对象
4 当 购物车的数量 =1 同时 用户 点击 “-”
使用async语法编写 wx.showModal 的api请求 弹窗提示 询问用户 是否要删除
1 确定 直接执行删除
2 取消 什么都不做
4 直接修改商品对象的数量 num
5 把cart数组 重新设置回 缓存中 和data中 this.setCart

8.5.2 代码

<view class="cart_num_tool">
            <view
             bindtap="handleItemNumEdit"
             data-id="{{item.goods_id}}"
             data-operation="{{-1}}"
             class="num_edit"
            >-
            </view>
            <view class="goods_num">{{item.num}}</view>
            <view
             bindtap="handleItemNumEdit"
             data-id="{{item.goods_id}}"
             data-operation="{{1}}"
             class="num_edit"
            >+
            </view>
          </view>

使用async语法编写 wx.showModal 的api请求

// 弹出提示窗口
export const showModal=({content})=>{
  return new Promise((resolve,reject)=>{
    wx.showModal({
      title: '提示',
      content: content,
      success :(res) =>{
        resolve(res);
      },
      fail:(err)=>{
        reject(err);
      }
    })
  })
}
// 商品数量的编辑功能
  async handleItemNumEdit(e) {
    // 1 获取传递过来的参数 
    const { operation, id } = e.currentTarget.dataset;
    // 2 获取购物车数组
    let { cart } = this.data;
    // 3 找到需要修改的商品的索引
    const index = cart.findIndex(v => v.goods_id === id);
    // 4 判断是否要执行删除
    if (cart[index].num === 1 && operation === -1) {
      // 4.1 弹窗提示
      const res = await showModal({ content: "您是否要删除?" });
      if (res.confirm) {
        // 4.2 从index位开始,删除一个元素
        cart.splice(index, 1);
        this.setCart(cart);
      }
    } else {
      // 5 进行修改数量
      cart[index].num += operation;
      // 6 设置回缓存和data中
      this.setCart(cart);
    }
  },

8.6 没有商品的状态显示

在这里插入图片描述

代码
<!-- 当cart数组 长度不为0 显示 商品信息 -->
    <block wx:if="{{cart.length!==0}}">
      <view
       class="cart_item"
       wx:for="{{cart}}"
       wx:key="goods_id"
      >
        <!-- 复选框 -->
        <view class="cart_chk_wrap">
          <checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
            <checkbox checked="{{item.checked}}"></checkbox>
          </checkbox-group>
        </view>
        <!-- 商品图片 -->
        <navigator class="cart_img_wrap">
          <image mode="widthFix" src="{{item.goods_small_logo}}"></image>
        </navigator>
        <!-- 商品信息 -->
        。。。
    </block>
    <block wx:else>
      <image mode="widthFix" src="http://hbimg.b0.upaiyun.com/e1b1467beea0a9c7d6a56b32bac6d7e5dcd914f7c3e6-YTwUd6_fw658"></image>
    </block>

8.7 结算按钮功能

8.7.1 效果

如果没有获取收货地址,按结算按钮会出现"您还没有选择收货地址"的弹窗;如果没有购买商品,按结算按钮会出现"您还没有选购商品"的弹窗

8.7.2 思路

1 绑定结算按钮 bindtap 事件
2 使用async语法编写 wx.showToast 的api请求 显示消息提示框
3 判断有没有收货地址信息
4 判断用户有没有选购商品
5 经过以上的验证 跳转到 支付页面!

8.7.3 代码
<!-- 结算 -->
  <view class="order_pay_wrap" bindtap="handlePay">
    结算({{totalNum}})
  </view>
// 显示消息提示框
export const showToast=({title})=>{
  return new Promise((resolve,reject)=>{
    wx.showToast({
      title: title,
      icon: 'none',
      success :(res) =>{
        resolve(res);
      },
      fail:(err)=>{
        reject(err);
      }
    })
  })
}
// 结算按钮
  async handlePay(){
    // 1 判断收货地址
    const {address,totalNum}=this.data;
    // 判断有没有收货地址
    if(!address.userName){
      await showToast({title:"您还没有选择收货地址"});
      return;
    }
    // 2 判断用户有没有选购商品
    if(totalNum===0){
      await showToast({title:"您还没有选购商品"});
      return;
    }
    // 3 跳转到 支付页面
    wx.navigateTo({
      url: '/pages/pay/index'
    });
  },

在这里插入图片描述

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值