小程序购物城项目实战(中篇)

购物城项目实战(中篇)

  • 提示
  • 商品详情轮播图动态渲染
  • 完善商品详情页面的商品简介
  • 商品详情页面-优化动态渲染
  • 点击轮播图进行大图预览
  • 商品详情页的底部工具栏
  • 商品详情页的加入购物车
  • 购物车内容页面
  • 购物车页面的动态数据更新
  • 购物车商品数量以及选中功能优化
  • 购物车页面,单个商品数量的加减功能
  • 购物车页面为空时的效果以及结算按钮
  • 支付页面

提示

(中篇)承接(上篇内容),接口文档和阿里图标库已在(上篇)给出,本文所粘贴代码或许存在不完整之处,完整代码请见(下篇)。

商品详情轮播图动态渲染

先书写轮播图结构,在goods_detail文件夹下的index.wxml内修改代码。然后index.wxss修改样式

<!--pages/goods_detail/index.wxml-->
<view class="detail_swiper">
  <!-- 轮播图内容 -->
  <swiper autoplay="true" circular="true" indicator-dots="true">
    <swiper-item wx:for="{{goodsObj.data.message.pics}}" wx:key="pics_id">
      <image mode="widthFix" src="{{item.pics_mid}}"></image>
    </swiper-item>
  </swiper>
</view>
/* pages/goods_detail/index.wxss */
.detail_swiper swiper{
  height: 70vw;
  text-align: center;
}
.detail_swiper image{
  width: 60%;
}

小程序购物城项目实战(中篇)_第1张图片

完善商品详情页面的商品简介

其中使用了富文本标签rich-text,要渲染的数据都在接口文档中请求接口返回的数据里,我们使用了goodsObj把返回的数据进行了存储。

<!--pages/goods_detail/index.wxml-->
<view class="detail_swiper">
  <!-- 轮播图内容 -->
  <swiper autoplay="true" circular="true" indicator-dots="true">
    <swiper-item wx:for="{{goodsObj.data.message.pics}}" wx:key="pics_id">
      <image mode="widthFix" src="{{item.pics_mid}}"></image>
    </swiper-item>
  </swiper>
</view>
<!-- 商品内容文字 -->
<!-- 商品价格 -->
<view class="goods_price">{{goodsObj.data.message.goods_price}}</view>
<view class="goods_name_row">
  <!-- 商品名字 -->
  <view class="goods_name">{{goodsObj.data.message.goods_name}}</view>
  <!-- 收藏商品 -->
  <view class="goods_collect">
    <!-- 这里的收藏图标是引入了阿里图标库的 -->
    <text class="iconfont icon-shoucang"></text>
    <view class="collect_text">收藏</view>
  </view>
</view>

<!-- 图文详情 -->
<view class="goods_info">
  <view class="goods_info_title">图文详情</view>
  <view class="goods_info_content">
    <!-- 富文本渲染 -->
    <rich-text nodes="{{goodsObj.data.message.goods_introduce}}"></rich-text>
  </view>
</view>
/* pages/goods_detail/index.wxss */
.detail_swiper swiper {
  height: 70vw;
  text-align: center;
}

.detail_swiper image {
  width: 60%;
}

.goods_price {
  padding: 15rpx;
  font-size: 32rpx;
  font-weight: 600;
  color: var(--themColor);
}

.goods_name_row {
  display: flex;
  border-top: 5rpx solid #dedede;
  border-bottom: 5rpx solid #dedede;
  padding: 10rpx 0;
}

.goods_name_row .goods_collect {
  flex: 1;
  display: flex;
  /* 主轴方向变成上下(纵轴)的方向 */
  flex-direction: column;
  justify-content: center;
  align-items: center;
  border-left: 1rpx solid #000;
}

.goods_name_row .goods_name {
  flex: 5;
  color: #000;
  font-size: 28rpx;
  padding: 0 10rpx;
  /* 当文字超出很多时,使用省略号来代替多出的文字进显示 */
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.goods_info {}

.goods_info_title {
  font-size: 32rpx;
  color: var(--themColor);
  font-weight: 600;
  padding: 20rpx;
}

.goods_info_content {}

效果如下
小程序购物城项目实战(中篇)_第2张图片

商品详情页面-优化动态渲染

当观察goodsObj内的数据,我们发现里边存储的有些数据并没有用到,这就会占用小程序性能,所以我们需要对goodsObj的赋值函数进行处理,并且部分iphone手机 不识别webp图片格式,因此返回的数据中是webp格式的图片在苹果手机上可能渲染失败。解决上述两个问题:

  • 在index.js里边优化setData部分代码。
  • 修改index.wxml里边使用wx:for遍历数据的格式
  • 临时自己修改webp格式的图片,使用replace函数
// pages/goods_detail/index.js
//引入用来发送请求的方法,优化后的
import {
  request
} from "../../request/index.js"
Page({

  /**
   * 页面的初始数据
   */
  data: {
    //请求返回的数据是以对象形式
    goodsObj: {}
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    //拿到跳转页面时传递过来的商品id
    const {
      goods_id
    } = options;
    this.getGoodsDetail(goods_id);
  },
  /**
   * 获取商品详情数据
   */
  async getGoodsDetail(goods_id) {
    const goodsObj = await request({
      url: "https://api-hmugo-web.itheima.net/api/public/v1/goods/detail",
      data: {
        goods_id
      }
    });
    this.setData({
      // 优化存储数据,只赋值存储小程序用到的数据
      goodsObj:{
        goods_name:goodsObj.data.message.goods_name,
        goods_price:goodsObj.data.message.goods_price,
        // iphone部分手机不支持webp格式
        // 后台修改
        // 或者自己临时修改 使用replace函数 其中\.webp是找到所有.webp的文件,g表示全选,.jpg表示全部替换为.jpg格式。
        goods_introduce:goodsObj.data.message.goods_introduce.replace(/\.webp/g,'.jpg'),
        pics:goodsObj.data.message.pics
      }
    })
  },
  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})
<!--pages/goods_detail/index.wxml-->
<view class="detail_swiper">
  <!-- 轮播图内容 -->
  <swiper autoplay="true" circular="true" indicator-dots="true">
    <swiper-item wx:for="{{goodsObj.pics}}" wx:key="pics_id">
      <image mode="widthFix" src="{{item.pics_mid}}"></image>
    </swiper-item>
  </swiper>
</view>
<!-- 商品内容文字 -->
<!-- 商品价格 -->
<view class="goods_price">{{goodsObj.goods_price}}</view>
<view class="goods_name_row">
  <!-- 商品名字 -->
  <view class="goods_name">{{goodsObj.goods_name}}</view>
  <!-- 收藏商品 -->
  <view class="goods_collect">
    <!-- 这里的收藏图标是引入了阿里图标库的 -->
    <text class="iconfont icon-shoucang"></text>
    <view class="collect_text">收藏</view>
  </view>
</view>

<!-- 图文详情 -->
<view class="goods_info">
  <view class="goods_info_title">图文详情</view>
  <view class="goods_info_content">
    <!-- 富文本渲染 -->
    <rich-text nodes="{{goodsObj.goods_introduce}}"></rich-text>
  </view>
</view>

效果正常,并且goodsObj内只有四种数据。
小程序购物城项目实战(中篇)_第3张图片

点击轮播图进行大图预览

点击商品详情处的轮播图后,会进行放大预览。步骤如下

  • 在index.wxml给轮播图绑定点击事件
  • 调用小程序的api,previewImage
// pages/goods_detail/index.js
//引入用来发送请求的方法,优化后的
import {
  request
} from "../../request/index.js"
Page({

  /**
   * 页面的初始数据
   */
  data: {
    //请求返回的数据是以对象形式
    goodsObj: {}
    
  },
  //定义要预览的大图信息数组全局变量
  goodsInfo: {},
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    //拿到跳转页面时传递过来的商品id
    const {
      goods_id
    } = options;
    this.getGoodsDetail(goods_id);
  },
  /**
   * 获取商品详情数据
   */
  async getGoodsDetail(goods_id) {
    const goodsObj = await request({
      url: "https://api-hmugo-web.itheima.net/api/public/v1/goods/detail",
      data: {
        goods_id
      }
    });
    //请求成功之后给之前定义的预览大图数组赋值
    this.goodsInfo=goodsObj.data.message;
    this.setData({
      // 优化存储数据,只赋值存储小程序用到的数据
      goodsObj: {
        goods_name: goodsObj.data.message.goods_name,
        goods_price: goodsObj.data.message.goods_price,
        // iphone部分手机不支持webp格式
        // 后台修改
        // 或者自己临时修改 使用replace函数 其中\.webp是找到所有.webp的文件,g表示全选,.jpg表示全部替换为.jpg格式。
        goods_introduce: goodsObj.data.message.goods_introduce.replace(/\.webp/g, '.jpg'),
        pics: goodsObj.data.message.pics
      },
    })
  },
  /**
   * 点击轮播图预览大图事件
   */
  handlePrevewImage(e) {
    console.log('预览');
    //  先构建要预览的图片数组
    const urls=this.goodsInfo.pics.map(v=>v.pics_mid)
    // 接受传递过来的图片url
    const current = e.currentTarget.dataset.url
    wx.previewImage({
      current: current,
      urls: urls
    })
  },
  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})
<!--pages/goods_detail/index.wxml-->
<view class="detail_swiper">
  <!-- 轮播图内容,bindtap绑定一个预览大图事件 -->
  <swiper autoplay="true" circular="true" indicator-dots="true" bindtap="handlePrevewImage" data-url="{{item.pics_mid}}">
    <swiper-item wx:for="{{goodsObj.pics}}" wx:key="pics_id">
      <image mode="widthFix" src="{{item.pics_mid}}"></image>
    </swiper-item>
  </swiper>
</view>
<!-- 商品内容文字 -->
<!-- 商品价格 -->
<view class="goods_price">{{goodsObj.goods_price}}</view>
<view class="goods_name_row">
  <!-- 商品名字 -->
  <view class="goods_name">{{goodsObj.goods_name}}</view>
  <!-- 收藏商品 -->
  <view class="goods_collect">
    <!-- 这里的收藏图标是引入了阿里图标库的 -->
    <text class="iconfont icon-shoucang"></text>
    <view class="collect_text">收藏</view>
  </view>
</view>

<!-- 图文详情 -->
<view class="goods_info">
  <view class="goods_info_title">图文详情</view>
  <view class="goods_info_content">
    <!-- 富文本渲染 -->
    <rich-text nodes="{{goodsObj.goods_introduce}}"></rich-text>
  </view>
</view>

效果如下
小程序购物城项目实战(中篇)_第4张图片

商品详情页的底部工具栏

底部导航栏的客服、分享等功能,点击后均可响应,依赖于小程序的开放能力,可以在官网文档查找相关说明。同时将按钮巧妙的隐藏在了这两个标签底层,实际上我们点击客服和分析时,点击的是隐藏的按钮。我们还实现了点击购物车图标跳转到cart购物车页面。
注意:navigator是一个类似于超链接标签,默认是以navigate方式跳转,但是这种方式不能跳转tabbar(导航栏页面),所以我们使用open-type="switchTab"进行跳转。
小程序购物城项目实战(中篇)_第5张图片

<!--pages/goods_detail/index.wxml-->
<view class="detail_swiper">
  <!-- 轮播图内容,bindtap绑定一个预览大图事件 -->
  <swiper autoplay="true" circular="true" indicator-dots="true" bindtap="handlePrevewImage"
    data-url="{{item.pics_mid}}">
    <swiper-item wx:for="{{goodsObj.pics}}" wx:key="pics_id">
      <image mode="widthFix" src="{{item.pics_mid}}"></image>
    </swiper-item>
  </swiper>
</view>
<!-- 商品内容文字 -->
<!-- 商品价格 -->
<view class="goods_price">{{goodsObj.goods_price}}</view>
<view class="goods_name_row">
  <!-- 商品名字 -->
  <view class="goods_name">{{goodsObj.goods_name}}</view>
  <!-- 收藏商品 -->
  <view class="goods_collect">
    <!-- 这里的收藏图标是引入了阿里图标库的 -->
    <text class="iconfont icon-shoucang"></text>
    <view class="collect_text">收藏</view>
  </view>
</view>

<!-- 图文详情 -->
<view class="goods_info">
  <view class="goods_info_title">图文详情</view>
  <view class="goods_info_content">
    <!-- 富文本渲染 -->
    <rich-text nodes="{{goodsObj.goods_introduce}}"></rich-text>
  </view>
</view>

<!-- 底部导航栏 -->
<view class="btm_tool">
  <!-- 客服 -->
  <view class="tool_item">
    <view class="iconfont icon-kefu"></view>
    <view>客服</view>
    <!-- 增加联系客服功能 ,隐藏在客服的下一层,透明度为0,设置其高和宽与客服部分一致-->
    <button open-type="contact"></button>
  </view>
  <!-- 分享 -->
  <view class="tool_item">
    <view class="iconfont icon-fenxiang"></view>
    <view>分享</view>
    <button open-type="share"></button>
  </view>
  <!-- 购物车 -->
  <!-- switchTab允许跳转tabBar(导航栏)页面 -->
  <navigator open-type="switchTab" url="/pages/cart/index" class="tool_item">
    <view class="tool_item">
      <view class="iconfont icon-gouwuche"></view>
      <view>购物车</view>
    </view>
  </navigator>
  <!-- 加入购物车 -->
  <view class="tool_item btn_cart">
    <view class="">加入购物车</view>
  </view>
  <!-- 立即购买 -->
  <view class="tool_item btn_buy">
    <view>立即购买</view>
  </view>
</view>
/* pages/goods_detail/index.wxss */
/* 防止增添的固定导航栏,在页面滑至最底部时造成遮挡部分页面内容,
这里设置页面的底部扩充出90rpx,给固定导航栏 */
page{
  padding-bottom: 90rpx;
}
.detail_swiper swiper {
  height: 70vw;
  text-align: center;
}

.detail_swiper image {
  width: 60%;
}

.goods_price {
  padding: 15rpx;
  font-size: 32rpx;
  font-weight: 600;
  color: var(--themColor);
}

.goods_name_row {
  display: flex;
  border-top: 5rpx solid #dedede;
  border-bottom: 5rpx solid #dedede;
  padding: 10rpx 0;
}

.goods_name_row .goods_collect {
  flex: 1;
  display: flex;
  /* 主轴方向变成上下(纵轴)的方向 */
  flex-direction: column;
  justify-content: center;
  align-items: center;
  border-left: 1rpx solid #000;
}

.goods_name_row .goods_name {
  flex: 5;
  color: #000;
  font-size: 28rpx;
  padding: 0 10rpx;
  /* 当文字超出很多时,使用省略号来代替多出的文字进显示 */
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.goods_info_title {
  font-size: 32rpx;
  color: var(--themColor);
  font-weight: 600;
  padding: 20rpx;
}

.btm_tool {
  /* position加固定定位,固定在页面中 */
  position: fixed;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 90rpx;
  background-color: #fff;
  display: flex;
}
.btm_tool .tool_item {
  border-top: 1rpx solid #ccc;
  flex:1;
  display: flex;
  /* 改变主轴方向为上下方向 */
  flex-direction: column;
  /* 垂直和水平居中 */
  justify-content: center;
  text-align: center;
  font-size: 24rpx;
  /* 父样式相对定位 */
  position: relative;
}
.btm_tool .tool_item button{
  /* 子样式绝对定位 */
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  /* 透明度 */
  opacity: 0;
}
.btn_cart {
  flex: 2;
  background-color: #ffa500;
  color: #fff;
  font-size: 28rpx;
  font-weight: 600;
}
.btn_buy {
  flex: 2;
  background-color: var(--themColor);
  color: #fff;
  font-size: 28rpx;
  font-weight: 600;
}

效果如下
小程序购物城项目实战(中篇)_第6张图片

商品详情页的加入购物车

在商品详情页,查看完某一商品后,点击加入购物车,实现加入功能。实现步骤如下

  • 先绑定点击事件
  • 获取缓存中的购物车数据,以数组的格式
  • 判断当前商品是否已经存在于购物车
  • 若存在,则修改商品数据,执行购物车的该商品数量加一
  • 把当前购物车的数组,填充(更新)到缓存中
  • 若不存在,则直接给购物车数组添加一个新元素
  • 把当前购物车数据 填充到缓存中
  • 弹出一些提示信息
    代码如下,还是在goods_detail文件夹下的index.wxml和index.js做修改
<!--pages/goods_detail/index.wxml-->
<view class="detail_swiper">
  <!-- 轮播图内容,bindtap绑定一个预览大图事件 -->
  <swiper autoplay="true" circular="true" indicator-dots="true" bindtap="handlePrevewImage"
    data-url="{{item.pics_mid}}">
    <swiper-item wx:for="{{goodsObj.pics}}" wx:key="pics_id">
      <image mode="widthFix" src="{{item.pics_mid}}"></image>
    </swiper-item>
  </swiper>
</view>
<!-- 商品内容文字 -->
<!-- 商品价格 -->
<view class="goods_price">{{goodsObj.goods_price}}</view>
<view class="goods_name_row">
  <!-- 商品名字 -->
  <view class="goods_name">{{goodsObj.goods_name}}</view>
  <!-- 收藏商品 -->
  <view class="goods_collect">
    <!-- 这里的收藏图标是引入了阿里图标库的 -->
    <text class="iconfont icon-shoucang"></text>
    <view class="collect_text">收藏</view>
  </view>
</view>

<!-- 图文详情 -->
<view class="goods_info">
  <view class="goods_info_title">图文详情</view>
  <view class="goods_info_content">
    <!-- 富文本渲染 -->
    <rich-text nodes="{{goodsObj.goods_introduce}}"></rich-text>
  </view>
</view>

<!-- 底部导航栏 -->
<view class="btm_tool">
  <!-- 客服 -->
  <view class="tool_item">
    <view class="iconfont icon-kefu"></view>
    <view>客服</view>
    <!-- 增加联系客服功能 ,隐藏在客服的下一层,透明度为0,设置其高和宽与客服部分一致-->
    <button open-type="contact"></button>
  </view>
  <!-- 分享 -->
  <view class="tool_item">
    <view class="iconfont icon-fenxiang"></view>
    <view>分享</view>
    <button open-type="share"></button>
  </view>
  <!-- 购物车 -->
  <!-- switchTab允许跳转tabBar(导航栏)页面 -->
  <navigator open-type="switchTab" url="/pages/cart/index" class="tool_item">
    <view class="tool_item">
      <view class="iconfont icon-gouwuche"></view>
      <view>购物车</view>
    </view>
  </navigator>
  <!-- 加入购物车 -->
  <!-- 增加点击事件,添加商品响应 -->
  <view class="tool_item btn_cart" bindtap="handleCartAdd">
    <view>加入购物车</view>
  </view>
  <!-- 立即购买 -->
  <view class="tool_item btn_buy">
    <view>立即购买</view>
  </view>
</view>
// pages/goods_detail/index.js
//引入用来发送请求的方法,优化后的
import {
  request
} from "../../request/index.js"
Page({

  /**
   * 页面的初始数据
   */
  data: {
    //请求返回的数据是以对象形式
    goodsObj: {}

  },
  //定义要预览的大图信息数组全局变量
  goodsInfo: {},
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    //拿到跳转页面时传递过来的商品id
    const {
      goods_id
    } = options;
    this.getGoodsDetail(goods_id);
  },
  /**
   * 获取商品详情数据
   */
  async getGoodsDetail(goods_id) {
    const goodsObj = await request({
      url: "https://api-hmugo-web.itheima.net/api/public/v1/goods/detail",
      data: {
        goods_id
      }
    });
    //请求成功之后给之前定义的预览大图数组赋值
    this.goodsInfo = goodsObj.data.message;
    this.setData({
      // 优化存储数据,只赋值存储小程序用到的数据
      goodsObj: {
        goods_name: goodsObj.data.message.goods_name,
        goods_price: goodsObj.data.message.goods_price,
        // iphone部分手机不支持webp格式
        // 后台修改
        // 或者自己临时修改 使用replace函数 其中\.webp是找到所有.webp的文件,g表示全选,.jpg表示全部替换为.jpg格式。
        goods_introduce: goodsObj.data.message.goods_introduce.replace(/\.webp/g, '.jpg'),
        pics: goodsObj.data.message.pics
      },
    })
  },
  /**
   * 点击轮播图预览大图事件
   */
  handlePrevewImage(e) {
    console.log('预览');
    //  先构建要预览的图片数组
    const urls = this.goodsInfo.pics.map(v => v.pics_mid)
    // 接受传递过来的图片url
    const current = e.currentTarget.dataset.url
    wx.previewImage({
      current: current,
      urls: urls
    })
  },
  /**
   * 用户商品加入购物车事件
   */
  handleCartAdd(e) {
    //获取缓存的商品数据,并由字符串格式转为数组格式
    let cart=wx.getStorageSync('cart')||[];
    // 判定商品是否已存在购物车数组中
    let index=cart.findIndex(v=>v.goods_id===this.goodsInfo.goods_id);
    if(index===-1){
      //不存在,第一次添加
      this.goodsInfo.num=1;
      cart.push(this.goodsInfo);
    }else{
      //存在
      cart[index].num++;
    }
    //购物车数组更新到缓存中
    wx.setStorageSync("cart",cart);
    //弹窗提示
    wx.showToast({
      title: '添加成功',
      icon:'success',
      mask:'true'
    })
  },
  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

小程序购物城项目实战(中篇)_第7张图片

购物车内容页面

点击加入购物车后,商品会被加入到购物车中,随后点击购物车,进入购物车内容页面。该页面对应cart文件夹,首先是修改页面标题,在cart文件夹下的index.json文件修改代码

{
  "usingComponents": {},
  "navigationBarTitleText": "购物车"
}

随后开始书写相关页面结构,在index.wxml中修改代码。

<!--pages/cart/index.wxml-->
<!-- 收货地址容器 -->
<view class="receive_address">
  <!-- 收货地址按钮 -->
  <!-- wx:if判断如果地址里边的用户名是否存在,不存在时显示 -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain表示按钮是否镂空,背景色透明 -->
    <!-- 绑定点击事件,获取收货地址 -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">增添收货地址</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>

<!-- 购物车具体内容 -->
<view class="cart_content">
  <view class="cart_title">购物车</view>
  <view class="cart_main">
    <view class="cart_item">
      <!-- 复选框结构 -->
      <view class="cart_checkbox">
        <checkbox-group bindchange="">
          <checkbox></checkbox>
        </checkbox-group>
      </view>
      <!-- 商品图片 -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="http://image2.suning.cn/uimg/b2c/newcatentries/0000000000-000000000178667792_2_800x800.jpg">
        </image>
      </navigator>
      <!-- 商品信息 -->
      <view class="cart_info">
        <view class="goods_name">TCL 65Q960C 65英寸 哈曼卡顿 人工智能 金属超薄 64344K+HDR 原色量子点 曲面电视(灰色)</view>
        <view class="goods_price_wrap">
          <view class="goods_price">999</view>
          <view class="cart_num_tool">
            <view class="num_edit">-</view>
            <view class="goods_nums">1</view>
            <view class="num_edit">+</view>
          </view>

        </view>
      </view>
    </view>
  </view>
</view>

<!-- 底部导航栏 -->
<view class="footer_tool">
  <!-- 全选 -->
  <view class="all_check_wrap">
    <checkbox-group>
      <checkbox>全选</checkbox>
    </checkbox-group>
  </view>
  <!-- 总价格 -->
  <view class="total_price_wrap">
    <view class="total_price">
    合计:<text class="total_price_number">999</text>
    </view>
    <view>包含运费</view>
  </view>
  <!-- 结算 -->
  <view class="order_pay_wrap">结算(1)</view>
</view>

样式文件

/* pages/cart/index.wxss */
page{
  padding-bottom: 90rpx;
}
.address {
  padding: 20rpx;
}

.address button {
  width: 60%;
}

.user_info_row {
  display: flex;
  padding: 20rpx;
}

.user_info {
  flex: 5;
}

.user_phone {
  flex: 2;
  text-align: center;
  justify-content: center;
}

.cart_title {
  padding: 20rpx;
  font-size: 36rpx;
  color: var(--themColor);
  border-top: 1rpx solid currentColor;
  border-bottom: 1rpx solid currentColor;
}

.cart_item {
  display: flex;
}

.cart_item .cart_checkbox {
  flex: 1;
  display: flex;
  justify-content: center;
  align-items: center;
}

.cart_item .cart_checkbox checkbox-group {
  display: flex;
}

.cart_item .cart_image {
  flex: 2;
  display: flex;
  justify-content: center;
  align-items: center;
}

.cart_image image {
  width: 80%;
}

.cart_info {
  flex: 4;
  display: flex;
  justify-content: space-around;
  flex-direction: column;
}

.cart_info .goods_name {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  color: #666;
}

.cart_info .goods_price_wrap {
  display: flex;
  justify-content: space-between;
}

.goods_price {
  color: var(--themColor);
  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_nums {
  width: 55rpx;
  height: 55rpx;
  display: flex;
  justify-content: center;
  align-items: center;
}
.footer_tool{
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 90rpx;
  background-color: #fff;
  display: flex;
  border-top: 1rpx solid #ccc;
}
.all_check_wrap{
  flex: 2;
  display: flex;
  justify-content: center;
  align-items: center;

}
.total_price_wrap{
  flex: 4;
  padding-right: 15rpx;
    /* 文字右对齐 */
    text-align: right;
}
.total_price_number{
  color: var(--themColor);
  font-size: 34rpx;
  font-weight: 600;
}
.order_pay_wrap{
  flex: 2;
  background-color: var(--themColor);
  color: #fff;
  font-size: 32rpx;
  font-weight: 600;
  display: flex;
  justify-content: center;
  align-items: center;
}

现在一个购物车页面的静态显示内容做好了
小程序购物城项目实战(中篇)_第8张图片

购物车页面的动态数据更新

在index.js文件中的onShow函数,要在里边获取缓存中的购物车数组,然后把购物车数组更新到data中去。

// pages/cart/index.js
Page({
  // 点击获取收货地址事件
  // 1.绑定点击事件
  // 2.调用小程序内API,"wx.chooseAddress",获取用户的收货地址
  // 购物车页面加载完毕
  // 1.获取本地存储中的收货地址数据
  // 2.如果有存储,就把数据 设置给data中的一个变量
  handleChooseAddress(e) {
    wx.chooseAddress({
      success: (result) => {
        // 把获取的地址存入本地缓存中,在存储之前先进行一个地址拼接
        result.all = result.provinceName + result.cityName + result.countyName + result.detailInfo;
        console.log(result);
        wx.setStorageSync('address', result)
      },
    });
  },
  /**
   * 页面的初始数据
   */
  data: {
    address: {},
    //声明一个变量,购物车数组
    cart:[]
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    //获取缓存中的购物车数据
    const cart=wx.getStorageSync('cart')
    //获取缓存中的收货地址信息
    const address = wx.getStorageSync('address');
    //给data赋值
    this.setData({
      address,
      cart:cart
    })
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})
<!--pages/cart/index.wxml-->
<!-- 收货地址容器 -->
<view class="receive_address">
  <!-- 收货地址按钮 -->
  <!-- wx:if判断如果地址里边的用户名是否存在,不存在时显示 -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain表示按钮是否镂空,背景色透明 -->
    <!-- 绑定点击事件,获取收货地址 -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">增添收货地址</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>

<!-- 购物车具体内容 -->
<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_checkbox">
        <checkbox-group bindchange="">
          <checkbox></checkbox>
        </checkbox-group>
      </view>
      <!-- 商品图片 -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="{{item.goods_small_logo}}">
        </image>
      </navigator>
      <!-- 商品信息 -->
      <view class="cart_info">
        <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_nums">{{item.num}}</view>
            <view class="num_edit">+</view>
          </view>

        </view>
      </view>
    </view>
  </view>
</view>

<!-- 底部导航栏 -->
<view class="footer_tool">
  <!-- 全选 -->
  <view class="all_check_wrap">
    <checkbox-group>
      <checkbox>全选</checkbox>
    </checkbox-group>
  </view>
  <!-- 总价格 -->
  <view class="total_price_wrap">
    <view class="total_price">
    合计:<text class="total_price_number">999</text>
    </view>
    <view>包含运费</view>
  </view>
  <!-- 结算 -->
  <view class="order_pay_wrap">结算(1)</view>
</view>

效果如下
小程序购物城项目实战(中篇)_第9张图片
接下来需要进行的是默认选中已添加进购物车的商品,复选框内是选中状态。在goods_detail文件夹中index.js修改。

// pages/goods_detail/index.js
//引入用来发送请求的方法,优化后的
import {
  request
} from "../../request/index.js"
Page({

  /**
   * 页面的初始数据
   */
  data: {
    //请求返回的数据是以对象形式
    goodsObj: {}

  },
  //定义要预览的大图信息数组全局变量
  goodsInfo: {},
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    //拿到跳转页面时传递过来的商品id
    const {
      goods_id
    } = options;
    this.getGoodsDetail(goods_id);
  },
  /**
   * 获取商品详情数据
   */
  async getGoodsDetail(goods_id) {
    const goodsObj = await request({
      url: "https://api-hmugo-web.itheima.net/api/public/v1/goods/detail",
      data: {
        goods_id
      }
    });
    //请求成功之后给之前定义的预览大图数组赋值
    this.goodsInfo = goodsObj.data.message;
    this.setData({
      // 优化存储数据,只赋值存储小程序用到的数据
      goodsObj: {
        goods_name: goodsObj.data.message.goods_name,
        goods_price: goodsObj.data.message.goods_price,
        // iphone部分手机不支持webp格式
        // 后台修改
        // 或者自己临时修改 使用replace函数 其中\.webp是找到所有.webp的文件,g表示全选,.jpg表示全部替换为.jpg格式。
        goods_introduce: goodsObj.data.message.goods_introduce.replace(/\.webp/g, '.jpg'),
        pics: goodsObj.data.message.pics
      },
    })
  },
  /**
   * 点击轮播图预览大图事件
   */
  handlePrevewImage(e) {
    console.log('预览');
    //  先构建要预览的图片数组
    const urls = this.goodsInfo.pics.map(v => v.pics_mid)
    // 接受传递过来的图片url
    const current = e.currentTarget.dataset.url
    wx.previewImage({
      current: current,
      urls: urls
    })
  },
  /**
   * 用户商品加入购物车事件
   */
  handleCartAdd(e) {
    //获取缓存的商品数据,并由字符串格式转为数组格式
    let cart=wx.getStorageSync('cart')||[];
    // 判定商品是否已存在购物车数组中
    let index=cart.findIndex(v=>v.goods_id===this.goodsInfo.goods_id);
    if(index===-1){
      //不存在,第一次添加
      this.goodsInfo.num=1;
      // 给商品增添一个checked的属性,值为true,以便在购物车页面的复选框进行选中
      this.goodsInfo.checked=true;
      cart.push(this.goodsInfo);
    }else{
      //存在
      cart[index].num++;
    }
    //购物车数组更新到缓存中
    wx.setStorageSync("cart",cart);
    //弹窗提示
    wx.showToast({
      title: '添加成功',
      icon:'success',
      mask:'true'
    })
  },
  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

然后给cart文件夹下的index.wxml中的checkbox标签增添属性。

<!--pages/cart/index.wxml-->
<!-- 收货地址容器 -->
<view class="receive_address">
  <!-- 收货地址按钮 -->
  <!-- wx:if判断如果地址里边的用户名是否存在,不存在时显示 -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain表示按钮是否镂空,背景色透明 -->
    <!-- 绑定点击事件,获取收货地址 -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">增添收货地址</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>

<!-- 购物车具体内容 -->
<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_checkbox">
        <checkbox-group bindchange="">
          <checkbox checked="{{item.checked}}"></checkbox>
        </checkbox-group>
      </view>
      <!-- 商品图片 -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="{{item.goods_small_logo}}">
        </image>
      </navigator>
      <!-- 商品信息 -->
      <view class="cart_info">
        <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_nums">{{item.num}}</view>
            <view class="num_edit">+</view>
          </view>

        </view>
      </view>
    </view>
  </view>
</view>

<!-- 底部导航栏 -->
<view class="footer_tool">
  <!-- 全选 -->
  <view class="all_check_wrap">
    <checkbox-group>
      <checkbox>全选</checkbox>
    </checkbox-group>
  </view>
  <!-- 总价格 -->
  <view class="total_price_wrap">
    <view class="total_price">
    合计:<text class="total_price_number">999</text>
    </view>
    <view>包含运费</view>
  </view>
  <!-- 结算 -->
  <view class="order_pay_wrap">结算(1)</view>
</view>

效果如下
小程序购物城项目实战(中篇)_第10张图片
接下来是底部导航栏的全选的实现,数据的动态更新。步骤如下
1.onShow中获取缓存中的购物车数组
2.根据购物车中的商品数据,当全部商品被选中时,全选被选中,有一个未被选中,全选按钮则不选中。
3.计算总价格和总数量,要商品被选中才进行。
4.获取缓存中购物车数组
5.遍历
6 总价格=商品的单价*商品数量
7.总数量=所有商品数量累加
8.计算后的价格数量更新到data中。

// pages/cart/index.js
Page({
  // 点击获取收货地址事件
  // 1.绑定点击事件
  // 2.调用小程序内API,"wx.chooseAddress",获取用户的收货地址
  // 购物车页面加载完毕
  // 1.获取本地存储中的收货地址数据
  // 2.如果有存储,就把数据 设置给data中的一个变量
  handleChooseAddress(e) {
    wx.chooseAddress({
      success: (result) => {
        // 把获取的地址存入本地缓存中,在存储之前先进行一个地址拼接
        result.all = result.provinceName + result.cityName + result.countyName + result.detailInfo;
        console.log(result);
        wx.setStorageSync('address', result)
      },
    });
  },
  /**
   * 页面的初始数据
   */
  data: {
    address: {},
    //声明一个变量,购物车数组
    cart:[],
    // 声明一个变量,购物车内容是否全选
    allChecked:false,
    // 定义商品总价格和总数量
    totalPrice:0,
    totalNum:0
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    //获取缓存中的购物车数据
    const cart=wx.getStorageSync('cart')
    // 计算是否全选购物车商品
    // every数组方法会遍历会接收个回调函数 那么每一个回调函数都返回true那么every方 法的返回值为true
    // 只要有一个回调函数返回了false 那么不再循环执行,直接返回false
    // 假如是空数组,那么every返回也是true,所以使用三元表达式
    const allChecked=cart.length?cart.every(v=>v.checked):false;
    //获取缓存中的收货地址信息
    const address = wx.getStorageSync('address');
    //  声明总价格和总数量
    let totalPrice=0;
    let totalNum=0;
    cart.forEach(v=>{
      if(v.checked){
        totalPrice+=v.num*v.goods_price;
        totalNum+=v.num;
      }
    })
    //给data赋值
    this.setData({
      address,
      cart:cart,
      allChecked,
      totalPrice,
      totalNum
    })
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})
<!--pages/cart/index.wxml-->
<!-- 收货地址容器 -->
<view class="receive_address">
  <!-- 收货地址按钮 -->
  <!-- wx:if判断如果地址里边的用户名是否存在,不存在时显示 -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain表示按钮是否镂空,背景色透明 -->
    <!-- 绑定点击事件,获取收货地址 -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">增添收货地址</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>

<!-- 购物车具体内容 -->
<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_checkbox">
        <checkbox-group bindchange="">
          <checkbox checked="{{item.checked}}"></checkbox>
        </checkbox-group>
      </view>
      <!-- 商品图片 -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="{{item.goods_small_logo}}">
        </image>
      </navigator>
      <!-- 商品信息 -->
      <view class="cart_info">
        <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_nums">{{item.num}}</view>
            <view class="num_edit">+</view>
          </view>

        </view>
      </view>
    </view>
  </view>
</view>

<!-- 底部导航栏 -->
<view class="footer_tool">
  <!-- 全选 -->
  <view class="all_check_wrap">
    <checkbox-group>
      <checkbox checked="{{allChecked}}">全选</checkbox>
    </checkbox-group>
  </view>
  <!-- 总价格 -->
  <view class="total_price_wrap">
    <view class="total_price">
    合计:<text class="total_price_number">{{totalPrice}}</text>
    </view>
    <view>包含运费</view>
  </view>
  <!-- 结算 -->
  <view class="order_pay_wrap">结算({{totalNum}})</view>
</view>

效果如下
小程序购物城项目实战(中篇)_第11张图片

购物车商品数量以及选中功能优化

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

// pages/cart/index.js
Page({
  /**
   * 封装一个函数,设置购物车商品状态的同时,底部工具栏,总价格,总数量重新计算 
   */
  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,
      totalPrice,
      totalNum,
      allChecked
    });
    wx.setStorageSync('cart', cart);
  },
  // 点击获取收货地址事件
  // 1.绑定点击事件
  // 2.调用小程序内API,"wx.chooseAddress",获取用户的收货地址
  // 购物车页面加载完毕
  // 1.获取本地存储中的收货地址数据
  // 2.如果有存储,就把数据 设置给data中的一个变量
  handleChooseAddress(e) {
    wx.chooseAddress({
      success: (result) => {
        // 把获取的地址存入本地缓存中,在存储之前先进行一个地址拼接
        result.all = result.provinceName + result.cityName + result.countyName + result.detailInfo;
        console.log(result);
        wx.setStorageSync('address', result)
      },
    });
  },

  // 商品的选中优化事件
  handleItemChange(e) {
    //获取被修改的商品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;
    // 将此时的数据更新到data和缓存中,并重新计算总价格和总数量,通过调用封装的函数setCart实现
    this.setCart(cart);
  },
  /**
   * 页面的初始数据
   */
  data: {
    address: {},
    //声明一个变量,购物车数组
    cart: [],
    // 声明一个变量,购物车内容是否全选
    allChecked: false,
    // 定义商品总价格和总数量
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    //获取缓存中的购物车数据
    const cart = wx.getStorageSync('cart')
    // 计算是否全选购物车商品
    // every数组方法会遍历会接收个回调函数 那么每一个回调函数都返回true那么every方 法的返回值为true
    // 只要有一个回调函数返回了false 那么不再循环执行,直接返回false
    // 假如是空数组,那么every返回也是true,所以使用三元表达式
    const allChecked = cart.length ? cart.every(v => v.checked) : false;
    //获取缓存中的收货地址信息
    const address = wx.getStorageSync('address');
    //  声明总价格和总数量
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      }
    })
    //给data赋值
    this.setData({
      address,
      cart: cart,
      allChecked,
      totalPrice,
      totalNum
    })
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})
<!--pages/cart/index.wxml-->
<!-- 收货地址容器 -->
<view class="receive_address">
  <!-- 收货地址按钮 -->
  <!-- wx:if判断如果地址里边的用户名是否存在,不存在时显示 -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain表示按钮是否镂空,背景色透明 -->
    <!-- 绑定点击事件,获取收货地址 -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">增添收货地址</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>

<!-- 购物车具体内容 -->
<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_checkbox">
        <checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
          <checkbox checked="{{item.checked}}"></checkbox>
        </checkbox-group>
      </view>
      <!-- 商品图片 -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="{{item.goods_small_logo}}">
        </image>
      </navigator>
      <!-- 商品信息 -->
      <view class="cart_info">
        <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_nums">{{item.num}}</view>
            <view class="num_edit">+</view>
          </view>

        </view>
      </view>
    </view>
  </view>
</view>

<!-- 底部导航栏 -->
<view class="footer_tool">
  <!-- 全选 -->
  <view class="all_check_wrap">
    <checkbox-group>
      <checkbox checked="{{allChecked}}">全选</checkbox>
    </checkbox-group>
  </view>
  <!-- 总价格 -->
  <view class="total_price_wrap">
    <view class="total_price">
    合计:<text class="total_price_number">{{totalPrice}}</text>
    </view>
    <view>包含运费</view>
  </view>
  <!-- 结算 -->
  <view class="order_pay_wrap">结算({{totalNum}})</view>
</view>

效果如下
小程序购物城项目实战(中篇)_第12张图片
接下来是点击购物车页面的全选按钮,则页面内所有商品全部被选中。步骤如下
1全选复选框绑定事件change
2获取data中的全选变量allChecked
3直接取反allChecked= ! allChecked
4遍历购物车数组让里面商品选中状态跟随 allChecked 改变而改变
5把购物车数组和allChecked 重新设置回data把购物车重新设置回缓存中

// pages/cart/index.js
Page({
  /**
   * 封装一个函数,设置购物车商品状态的同时,底部工具栏,总价格,总数量重新计算 
   */
  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,
      totalPrice,
      totalNum,
      allChecked
    });
    wx.setStorageSync('cart', cart);
  },
  // 点击获取收货地址事件
  // 1.绑定点击事件
  // 2.调用小程序内API,"wx.chooseAddress",获取用户的收货地址
  // 购物车页面加载完毕
  // 1.获取本地存储中的收货地址数据
  // 2.如果有存储,就把数据 设置给data中的一个变量
  handleChooseAddress(e) {
    wx.chooseAddress({
      success: (result) => {
        // 把获取的地址存入本地缓存中,在存储之前先进行一个地址拼接
        result.all = result.provinceName + result.cityName + result.countyName + result.detailInfo;
        console.log(result);
        wx.setStorageSync('address', result)
      },
    });
  },

  // 商品的选中优化事件
  handleItemChange(e) {
    //获取被修改的商品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;
    // 将此时的数据更新到data和缓存中,并重新计算总价格和总数量,通过调用封装的函数setCart实现
    this.setCart(cart);
  },
  //商品的全选功能
  handleItemAllChecked(e) {
    // 1获取data中的数据
    let {
      cart,
      allChecked
    } = this.data;
    // 2修改值
    allChecked = !allChecked;
    // 3循环修改cart数组中的商品选中状态
    cart.forEach(v => v.checked = allChecked);
    // 4把修改后的值填充回data或者缓存中,通过调用封装好的函数
    this.setCart(cart);
  },
  /**
   * 页面的初始数据
   */
  data: {
    address: {},
    //声明一个变量,购物车数组
    cart: [],
    // 声明一个变量,购物车内容是否全选
    allChecked: false,
    // 定义商品总价格和总数量
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    //获取缓存中的购物车数据
    const cart = wx.getStorageSync('cart')
    // 计算是否全选购物车商品
    // every数组方法会遍历会接收个回调函数 那么每一个回调函数都返回true那么every方 法的返回值为true
    // 只要有一个回调函数返回了false 那么不再循环执行,直接返回false
    // 假如是空数组,那么every返回也是true,所以使用三元表达式
    const allChecked = cart.length ? cart.every(v => v.checked) : false;
    //获取缓存中的收货地址信息
    const address = wx.getStorageSync('address');
    //  声明总价格和总数量
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      }
    })
    //给data赋值
    this.setData({
      address,
      cart: cart,
      allChecked,
      totalPrice,
      totalNum
    })
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})
<!--pages/cart/index.wxml-->
<!-- 收货地址容器 -->
<view class="receive_address">
  <!-- 收货地址按钮 -->
  <!-- wx:if判断如果地址里边的用户名是否存在,不存在时显示 -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain表示按钮是否镂空,背景色透明 -->
    <!-- 绑定点击事件,获取收货地址 -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">增添收货地址</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>

<!-- 购物车具体内容 -->
<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_checkbox">
        <checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
          <checkbox checked="{{item.checked}}"></checkbox>
        </checkbox-group>
      </view>
      <!-- 商品图片 -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="{{item.goods_small_logo}}">
        </image>
      </navigator>
      <!-- 商品信息 -->
      <view class="cart_info">
        <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_nums">{{item.num}}</view>
            <view class="num_edit">+</view>
          </view>

        </view>
      </view>
    </view>
  </view>
</view>

<!-- 底部导航栏 -->
<view class="footer_tool">
  <!-- 全选 -->
  <view class="all_check_wrap">
    <checkbox-group bindchange="handleItemAllChecked">
      <checkbox checked="{{allChecked}}">全选</checkbox>
    </checkbox-group>
  </view>
  <!-- 总价格 -->
  <view class="total_price_wrap">
    <view class="total_price">
    合计:<text class="total_price_number">{{totalPrice}}</text>
    </view>
    <view>包含运费</view>
  </view>
  <!-- 结算 -->
  <view class="order_pay_wrap">结算({{totalNum}})</view>
</view>

最后效果如下,每次点击全选按钮,上边的商品都会被全选中,或者全不被选中。
小程序购物城项目实战(中篇)_第13张图片

购物车页面,单个商品数量的加减功能

点击单个商品后的加或减按钮,则商品的数量发生变更,同时总数量和总价格发生改变。步骤如下

  1. “+”和”-" 按钮绑定同一个点击事件,区分的关键是自定义属性“+”号定义为”+1",“-”号定义为“-1”。
  2. 传递被点击的商品id ,goods__id 。
  3. 获取data中的购物车数组来获取需要被修改的商品对象。
  4. 直接修改商品对象的数量num 。
  5. 把cart数组重新设置回缓存中和data中,调用自己封装好的this. setCart计算总价格和总数量。
  6. 当数量减为1的时候,在点击减号按钮,弹窗提示是否要删除商品,弹窗提示有小程序的内置API,wx.showModal
// pages/cart/index.js
Page({
  /**
   * 封装一个函数,设置购物车商品状态的同时,底部工具栏,总价格,总数量重新计算 
   */
  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,
      totalPrice,
      totalNum,
      allChecked
    });
    wx.setStorageSync('cart', cart);
  },
  // 点击获取收货地址事件
  // 1.绑定点击事件
  // 2.调用小程序内API,"wx.chooseAddress",获取用户的收货地址
  // 购物车页面加载完毕
  // 1.获取本地存储中的收货地址数据
  // 2.如果有存储,就把数据 设置给data中的一个变量
  handleChooseAddress(e) {
    wx.chooseAddress({
      success: (result) => {
        // 把获取的地址存入本地缓存中,在存储之前先进行一个地址拼接
        result.all = result.provinceName + result.cityName + result.countyName + result.detailInfo;
        console.log(result);
        wx.setStorageSync('address', result)
      },
    });
  },

  // 商品的选中优化事件
  handleItemChange(e) {
    //获取被修改的商品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;
    // 将此时的数据更新到data和缓存中,并重新计算总价格和总数量,通过调用封装的函数setCart实现
    this.setCart(cart);
  },
  //商品的全选功能
  handleItemAllChecked(e) {
    // 1获取data中的数据
    let {
      cart,
      allChecked
    } = this.data;
    // 2修改值
    allChecked = !allChecked;
    // 3循环修改cart数组中的商品选中状态
    cart.forEach(v => v.checked = allChecked);
    // 4把修改后的值填充回data或者缓存中,通过调用封装好的函数
    this.setCart(cart);
  },
  //商品数量加减功能
  handleItemNumEdit(e) {
    
    // 1获取传递过来的参 数
    const {
      operation,
      id
    } = e.currentTarget.dataset;
    // 2获取购物车数组
    let {
      cart
    } = this.data;
    //3找到需要修改的商品的索引
    const index = cart.findIndex(v => v.goods_id === id);
    //判定商品数量小于1时,是否要被删除
    if(cart[index].num===1&&operation===-1){
      wx.showModal({
        title: '提示',
        content: '您是否要删除该商品?',
        success:(res)=>{
          if (res.confirm) {
            cart.splice(index,1);
            this.setCart(cart);
          } else if (res.cancel) {
            console.log('用户点击取消');
          }
        }
      })
    }else{
    // 4进行修改数量
    cart[index].num += operation;
    // 5设置回缓存和data中
    this.setCart(cart);}
  },
  /**
   * 页面的初始数据
   */
  data: {
    address: {},
    //声明一个变量,购物车数组
    cart: [],
    // 声明一个变量,购物车内容是否全选
    allChecked: false,
    // 定义商品总价格和总数量
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    //获取缓存中的购物车数据
    const cart = wx.getStorageSync('cart')
    // 计算是否全选购物车商品
    // every数组方法会遍历会接收个回调函数 那么每一个回调函数都返回true那么every方 法的返回值为true
    // 只要有一个回调函数返回了false 那么不再循环执行,直接返回false
    // 假如是空数组,那么every返回也是true,所以使用三元表达式
    const allChecked = cart.length ? cart.every(v => v.checked) : false;
    //获取缓存中的收货地址信息
    const address = wx.getStorageSync('address');
    //  声明总价格和总数量
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      }
    })
    //给data赋值
    this.setData({
      address,
      cart: cart,
      allChecked,
      totalPrice,
      totalNum
    })
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})
<!--pages/cart/index.wxml-->
<!-- 收货地址容器 -->
<view class="receive_address">
  <!-- 收货地址按钮 -->
  <!-- wx:if判断如果地址里边的用户名是否存在,不存在时显示 -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain表示按钮是否镂空,背景色透明 -->
    <!-- 绑定点击事件,获取收货地址 -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">增添收货地址</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>

<!-- 购物车具体内容 -->
<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_checkbox">
        <checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
          <checkbox checked="{{item.checked}}"></checkbox>
        </checkbox-group>
      </view>
      <!-- 商品图片 -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="{{item.goods_small_logo}}">
        </image>
      </navigator>
      <!-- 商品信息 -->
      <view class="cart_info">
        <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" bindtap="handleItemNumEdit" data-id="{{item.goods_id}}" data-operation="{{-1}}">-</view>
            <view class="goods_nums">{{item.num}}</view>
            <view class="num_edit" bindtap="handleItemNumEdit" data-id="{{item.goods_id}}" data-operation="{{1}}">+</view>
          </view>

        </view>
      </view>
    </view>
  </view>
</view>

<!-- 底部导航栏 -->
<view class="footer_tool">
  <!-- 全选 -->
  <view class="all_check_wrap">
    <checkbox-group bindchange="handleItemAllChecked">
      <checkbox checked="{{allChecked}}">全选</checkbox>
    </checkbox-group>
  </view>
  <!-- 总价格 -->
  <view class="total_price_wrap">
    <view class="total_price">
    合计:<text class="total_price_number">{{totalPrice}}</text>
    </view>
    <view>包含运费</view>
  </view>
  <!-- 结算 -->
  <view class="order_pay_wrap">结算({{totalNum}})</view>
</view>

效果如下
小程序购物城项目实战(中篇)_第14张图片

购物车页面为空时的效果以及结算按钮

查看cart中的数组长度,如果为0,则表示没商品,否则判断为有商品。直接在index.wxml中使用block标签即可完成。

<!--pages/cart/index.wxml-->
<!-- 收货地址容器 -->
<view class="receive_address">
  <!-- 收货地址按钮 -->
  <!-- wx:if判断如果地址里边的用户名是否存在,不存在时显示 -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain表示按钮是否镂空,背景色透明 -->
    <!-- 绑定点击事件,获取收货地址 -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">增添收货地址</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>

<!-- 购物车具体内容 -->
<view class="cart_content">
  <view class="cart_title">购物车</view>
  <view class="cart_main">
    <!-- 当cart数组的长度不为0时,则显示商品信息 -->
    <block wx:if="{{cart.length!==0}}">
      <view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">
        <!-- 复选框结构 -->
        <view class="cart_checkbox">
          <checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
            <checkbox checked="{{item.checked}}"></checkbox>
          </checkbox-group>
        </view>
        <!-- 商品图片 -->
        <navigator class="cart_image">
          <image mode="widthFix" src="{{item.goods_small_logo}}">
          </image>
        </navigator>
        <!-- 商品信息 -->
        <view class="cart_info">
          <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" bindtap="handleItemNumEdit" data-id="{{item.goods_id}}" data-operation="{{-1}}">-
              </view>
              <view class="goods_nums">{{item.num}}</view>
              <view class="num_edit" bindtap="handleItemNumEdit" data-id="{{item.goods_id}}" data-operation="{{1}}">+
              </view>
            </view>
          </view>
        </view>
      </view>
    </block>
    <!-- 当商品cart中的数组长度为0时,表示没有商品 -->
    <block wx:else>
      <image mode="widthFix"
        src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.mp.itc.cn%2Fupload%2F20170401%2F2f523043409747a9b68c1bcf6fd353a5_th.jpeg&refer=http%3A%2F%2Fimg.mp.itc.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1637485819&t=36e036c0adbfcb56e7658e4730b87461">
      </image>
    </block>
  </view>
</view>


<!-- 底部导航栏 -->
<view class="footer_tool">
  <!-- 全选 -->
  <view class="all_check_wrap">
    <checkbox-group bindchange="handleItemAllChecked">
      <checkbox checked="{{allChecked}}">全选</checkbox>
    </checkbox-group>
  </view>
  <!-- 总价格 -->
  <view class="total_price_wrap">
    <view class="total_price">
      合计:<text class="total_price_number">{{totalPrice}}</text>
    </view>
    <view>包含运费</view>
  </view>
  <!-- 结算 -->
  <view class="order_pay_wrap">结算({{totalNum}})</view>
</view>

效果如下
小程序购物城项目实战(中篇)_第15张图片
结算功能的实现,前提是有收货地址,并且有商品存在。然后跳转到支付页面。使用到了wx.showToast
wx.navigateTo的微信内置API。

// pages/cart/index.js
Page({
  /**
   * 封装一个函数,设置购物车商品状态的同时,底部工具栏,总价格,总数量重新计算 
   */
  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,
      totalPrice,
      totalNum,
      allChecked
    });
    wx.setStorageSync('cart', cart);
  },
  // 点击获取收货地址事件
  // 1.绑定点击事件
  // 2.调用小程序内API,"wx.chooseAddress",获取用户的收货地址
  // 购物车页面加载完毕
  // 1.获取本地存储中的收货地址数据
  // 2.如果有存储,就把数据 设置给data中的一个变量
  handleChooseAddress(e) {
    wx.chooseAddress({
      success: (result) => {
        // 把获取的地址存入本地缓存中,在存储之前先进行一个地址拼接
        result.all = result.provinceName + result.cityName + result.countyName + result.detailInfo;
        console.log(result);
        wx.setStorageSync('address', result)
      },
    });
  },

  // 商品的选中优化事件
  handleItemChange(e) {
    //获取被修改的商品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;
    // 将此时的数据更新到data和缓存中,并重新计算总价格和总数量,通过调用封装的函数setCart实现
    this.setCart(cart);
  },
  //商品的全选功能
  handleItemAllChecked(e) {
    // 1获取data中的数据
    let {
      cart,
      allChecked
    } = this.data;
    // 2修改值
    allChecked = !allChecked;
    // 3循环修改cart数组中的商品选中状态
    cart.forEach(v => v.checked = allChecked);
    // 4把修改后的值填充回data或者缓存中,通过调用封装好的函数
    this.setCart(cart);
  },
  //商品数量加减功能
  handleItemNumEdit(e) {

    // 1获取传递过来的参 数
    const {
      operation,
      id
    } = e.currentTarget.dataset;
    // 2获取购物车数组
    let {
      cart
    } = this.data;
    //3找到需要修改的商品的索引
    const index = cart.findIndex(v => v.goods_id === id);
    //判定商品数量小于1时,是否要被删除
    if (cart[index].num === 1 && operation === -1) {
      wx.showModal({
        title: '提示',
        content: '您是否要删除该商品?',
        success: (res) => {
          if (res.confirm) {
            cart.splice(index, 1);
            this.setCart(cart);
          } else if (res.cancel) {
            console.log('用户点击取消');
          }
        }
      })
    } else {
      // 4进行修改数量
      cart[index].num += operation;
      // 5设置回缓存和data中
      this.setCart(cart);
    }
  },

  // 商品页面的结算功能
  handlePay(e) {
    //1. 判断收货地址和是否有商品数量
    const {
      address,
      totalNum
    } = this.data;
    if (!address.userName) {
      wx.showToast({
        title: '请添加收货地址',
        icon: 'error',
        duration: 2000,
      })
      return;
    }
    if (totalNum===0) {
      wx.showToast({
        title: '请添加商品',
        icon: 'error',
        duration: 2000
      })
      return;
    }
    // 跳转到微信支付页面
    wx.navigateTo({
      url: '/pages/pay/index',
    })
  },
  /**
   * 页面的初始数据
   */
  data: {
    address: {},
    //声明一个变量,购物车数组
    cart: [],
    // 声明一个变量,购物车内容是否全选
    allChecked: false,
    // 定义商品总价格和总数量
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    //获取缓存中的购物车数据
    const cart = wx.getStorageSync('cart')
    // 计算是否全选购物车商品
    // every数组方法会遍历会接收个回调函数 那么每一个回调函数都返回true那么every方 法的返回值为true
    // 只要有一个回调函数返回了false 那么不再循环执行,直接返回false
    // 假如是空数组,那么every返回也是true,所以使用三元表达式
    const allChecked = cart.length ? cart.every(v => v.checked) : false;
    //获取缓存中的收货地址信息
    const address = wx.getStorageSync('address');
    //  声明总价格和总数量
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      }
    })
    //给data赋值
    this.setData({
      address,
      cart: cart,
      allChecked,
      totalPrice,
      totalNum
    })
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})
<!--pages/cart/index.wxml-->
<!-- 收货地址容器 -->
<view class="receive_address">
  <!-- 收货地址按钮 -->
  <!-- wx:if判断如果地址里边的用户名是否存在,不存在时显示 -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain表示按钮是否镂空,背景色透明 -->
    <!-- 绑定点击事件,获取收货地址 -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">增添收货地址</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>

<!-- 购物车具体内容 -->
<view class="cart_content">
  <view class="cart_title">购物车</view>
  <view class="cart_main">
    <!-- 当cart数组的长度不为0时,则显示商品信息 -->
    <block wx:if="{{cart.length!==0}}">
      <view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">
        <!-- 复选框结构 -->
        <view class="cart_checkbox">
          <checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
            <checkbox checked="{{item.checked}}"></checkbox>
          </checkbox-group>
        </view>
        <!-- 商品图片 -->
        <navigator class="cart_image">
          <image mode="widthFix" src="{{item.goods_small_logo}}">
          </image>
        </navigator>
        <!-- 商品信息 -->
        <view class="cart_info">
          <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" bindtap="handleItemNumEdit" data-id="{{item.goods_id}}" data-operation="{{-1}}">-
              </view>
              <view class="goods_nums">{{item.num}}</view>
              <view class="num_edit" bindtap="handleItemNumEdit" data-id="{{item.goods_id}}" data-operation="{{1}}">+
              </view>
            </view>
          </view>
        </view>
      </view>
    </block>
    <!-- 当商品cart中的数组长度为0时,表示没有商品 -->
    <block wx:else>
      <image mode="widthFix"
        src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.mp.itc.cn%2Fupload%2F20170401%2F2f523043409747a9b68c1bcf6fd353a5_th.jpeg&refer=http%3A%2F%2Fimg.mp.itc.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1637485819&t=36e036c0adbfcb56e7658e4730b87461">
      </image>
    </block>
  </view>
</view>


<!-- 底部导航栏 -->
<view class="footer_tool">
  <!-- 全选 -->
  <view class="all_check_wrap">
    <checkbox-group bindchange="handleItemAllChecked">
      <checkbox checked="{{allChecked}}">全选</checkbox>
    </checkbox-group>
  </view>
  <!-- 总价格 -->
  <view class="total_price_wrap">
    <view class="total_price">
      合计:<text class="total_price_number">{{totalPrice}}</text>
    </view>
    <view>包含运费</view>
  </view>
  <!-- 结算 -->
  <view class="order_pay_wrap" bindtap="handlePay">结算({{totalNum}})</view>
</view>

最后效果如下
**小程序购物城项目实战(中篇)_第16张图片
**

支付页面

pay文件夹对应的为支付页面,首先是修改页面的名称,在index.json中修改,如下。

{
  "usingComponents": {},
  "navigationBarTitleText": "支付"
}

随后页面发生变化

小程序购物城项目实战(中篇)_第17张图片
支付页面整体风格与购物车页面相似,所以这里可以大段复制之前购物车页面的代码。然后进行细微修改。

<!--pages/pay/index.wxml-->
<!-- 收货地址容器 -->
<view class="receive_address">
  <view  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>

<!-- 购物车具体内容 -->
<view class="cart_content">
  <view class="cart_title">购物车</view>
  <view class="cart_main">
      <view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">
        <!-- 商品图片 -->
        <navigator class="cart_image">
          <image mode="widthFix" src="{{item.goods_small_logo}}">
          </image>
        </navigator>
        <!-- 商品信息 -->
        <view class="cart_info">
          <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="goods_nums">x{{item.num}}</view>
            </view>
          </view>
        </view>
      </view>
  </view>
</view>


<!-- 底部导航栏 -->
<view class="footer_tool">
  <!-- 总价格 -->
  <view class="total_price_wrap">
    <view class="total_price">
      合计:<text class="total_price_number">{{totalPrice}}</text>
    </view>
    <view>包含运费</view>
  </view>
  <!-- 支付 -->
  <view class="order_pay_wrap">支付({{totalNum}})</view>
</view>

/* pages/pay/index.wxss */
page{
  padding-bottom: 90rpx;
}
.address {
  padding: 20rpx;
}

.address button {
  width: 60%;
}

.user_info_row {
  display: flex;
  padding: 20rpx;
}

.user_info {
  flex: 5;
}

.user_phone {
  flex: 2;
  text-align: center;
  justify-content: center;
}

.cart_title {
  padding: 20rpx;
  font-size: 36rpx;
  color: var(--themColor);
  border-top: 1rpx solid currentColor;
  border-bottom: 1rpx solid currentColor;
}

.cart_item {
  display: flex;
}

.cart_item .cart_image {
  flex: 2;
  display: flex;
  justify-content: center;
  align-items: center;
}

.cart_image image {
  width: 80%;
}

.cart_info {
  flex: 4;
  display: flex;
  justify-content: space-around;
  flex-direction: column;
}

.cart_info .goods_name {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  color: #666;
}

.cart_info .goods_price_wrap {
  display: flex;
  justify-content: space-between;
}

.goods_price {
  color: var(--themColor);
  font-size: 34rpx;
}

.cart_num_tool {
  display: flex;
}


.goods_nums {
  width: 55rpx;
  height: 55rpx;
  display: flex;
  justify-content: center;
  align-items: center;
}
.footer_tool{
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 90rpx;
  background-color: #fff;
  display: flex;
  border-top: 1rpx solid #ccc;
}
.total_price_wrap{
  flex: 4;
  padding-right: 15rpx;
    /* 文字右对齐 */
    text-align: right;
}
.total_price_number{
  color: var(--themColor);
  font-size: 34rpx;
  font-weight: 600;
}
.order_pay_wrap{
  flex: 2;
  background-color: var(--themColor);
  color: #fff;
  font-size: 32rpx;
  font-weight: 600;
  display: flex;
  justify-content: center;
  align-items: center;
}

// pages/pay/index.js
Page({
  /**
   * 封装一个函数,设置商品状态的同时,底部工具栏,总价格,总数量重新计算 
   */
  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,
      totalPrice,
      totalNum,
    });
    wx.setStorageSync('cart', cart);
  },
  
  /**
   * 页面的初始数据
   */
  data: {
    address: {},
    //声明一个变量,购物车数组
    cart: [],
    // 声明一个变量,购物车内容是否全选
    allChecked: false,
    // 定义商品总价格和总数量
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    //获取缓存中的购物车数据
    const cart = wx.getStorageSync('cart')
    // 计算是否全选购物车商品
    // every数组方法会遍历会接收个回调函数 那么每一个回调函数都返回true那么every方 法的返回值为true
    // 只要有一个回调函数返回了false 那么不再循环执行,直接返回false
    // 假如是空数组,那么every返回也是true,所以使用三元表达式
    const allChecked = cart.length ? cart.every(v => v.checked) : false;
    //获取缓存中的收货地址信息
    const address = wx.getStorageSync('address');
    //  声明总价格和总数量
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      }
    })
    //给data赋值
    this.setData({
      address,
      cart: cart,
      allChecked,
      totalPrice,
      totalNum
    })
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

最终经过修改后的支付页面样式如下

小程序购物城项目实战(中篇)_第18张图片
接下来是一些相关的支付逻辑,对于在此页的商品,我们必须要保证他曾经是在购物车页面被选中过的,即checkbox被选中为true的。所以需要在index.js内进行过滤,筛选真正需要支付的。

// pages/pay/index.js
Page({
  /**
   * 封装一个函数,设置商品状态的同时,底部工具栏,总价格,总数量重新计算 
   */
  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,
      totalPrice,
      totalNum,
    });
    wx.setStorageSync('cart', cart);
  },

  /**
   * 页面的初始数据
   */
  data: {
    address: {},
    //声明一个变量,购物车数组
    cart: [],
    // 声明一个变量,购物车内容是否全选
    allChecked: false,
    // 定义商品总价格和总数量
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    //获取缓存中的购物车数据
    let cart = wx.getStorageSync('cart')
    //获取缓存中的收货地址信息
    const address = wx.getStorageSync('address');
    //过滤后的购物车数组(真正被选中的商品)
    cart = cart.filter(v => v.checked);
     //  声明总价格和总数量
     let totalPrice = 0;
     let totalNum = 0;
     cart.forEach(v => {
         totalPrice += v.num * v.goods_price;
         totalNum += v.num;
     })
     this.setData({
       cart,
       totalPrice,
       totalNum, address,
     });
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

可以看到购物车有三件商品,只结算两件,跳转到支付页面之后,确实只有两件商品。
小程序购物城项目实战(中篇)_第19张图片
小程序购物城项目实战(中篇)_第20张图片
现在来实现点击支付按钮,进行微信支付功能。微信支付有哪些人或者帐号可以实现呢?目前来说,只有企业帐号开发的小程序可以实现,并且企业帐号的小程序后台中必须给开发者添加上白名单。具体详细信息,需要查看小程序开发文档(微信支付)。
总体流程如下
小程序购物城项目实战(中篇)_第21张图片
实际上应用的流程如下,当点击支付按钮时,先判断缓存中有没有用户登录时的token值,若没有,则跳转到授权页面,获取token,若有token值,则进行创建订单步骤。此时支付页面的index.js代码如下,因为没有token值,所以点击支付后跳转到了auth页面,进行授权。

// pages/pay/index.js
Page({
  /**
   * 封装一个函数,设置商品状态的同时,底部工具栏,总价格,总数量重新计算 
   */
  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,
      totalPrice,
      totalNum,
    });
    wx.setStorageSync('cart', cart);
  },

  /**
   * 页面的初始数据
   */
  data: {
    address: {},
    //声明一个变量,购物车数组
    cart: [],
    // 声明一个变量,购物车内容是否全选
    allChecked: false,
    // 定义商品总价格和总数量
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    //获取缓存中的购物车数据
    let cart = wx.getStorageSync('cart')
    //获取缓存中的收货地址信息
    const address = wx.getStorageSync('address');
    //过滤后的购物车数组(真正被选中的商品)
    cart = cart.filter(v => v.checked);
    //  声明总价格和总数量
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      totalPrice += v.num * v.goods_price;
      totalNum += v.num;
    })
    this.setData({
      cart,
      totalPrice,
      totalNum,
      address,
    });
  },
  // 点击支付事件
  handleOrderPay(e) {
    //判断缓存中有无token
    const token = wx.getStorageSync('token');
    //如果不存在
    if (!token) {
      //跳转页面到获取用户登录信息
      wx.navigateTo({
        url: '/pages/auth/index',
      })
      return;
    }
    //否则就是有token值
    console.log('已经有token了');
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

auth文件夹下,首先是在index.json中进行修改页面的名字 。

{
  "usingComponents": {},
  "navigationBarTitleText": "授权"
}

随后在该文件夹下的index.wxml代码中添加一个按钮,点击就进行授权。

<!--pages/auth/index.wxml-->
<!-- 获取用户授权,绑定点击事件 -->
<button type="primary" plain="true" open-type="getUserInfo" bindgetuserinfo="handleGetUserInfo">
  获取授权
</button>

index.wxss样式如下

/* pages/auth/index.wxss */
button{
  margin-top: 40rpx;
  width: 70%;
}

然后在index.js中书写 相关逻辑代码,注意这里我们自定义了一个token值,因为我们不是企业微信。

// pages/auth/index.js
// 引入封装好的请求函数
import {
  request
} from "../../request/index.js"
Page({
  //获取用户信息
  async handleGetUserInfo(e) {
    // 打印下点击后获得的事件e
    // console.log(e);
    // 获取事件e中的如下属性。
    const {
      encryptedData,
      rawData,
      iv,
      signature
    } = e.detail;
    //获取小程序登录成功后的code值
    wx.login({
      timeout: 10000,
      success: (result) => {
        // 查看得到了code值
        // console.log(result);
        const code = result.code;
        wx.setStorageSync('code', code)
      },
      fail: () => {},
      complete: () => {}
    })
    const code = wx.getStorageSync('code');
    const loginParams = {
      encryptedData,
      rawData,
      iv,
      signature,
      code
    };
    //发送请求,获取用户的token值
    const token = await request({
      url: "https://api-hmugo-web.itheima.net/api/public/v1/users/wxlogin",
      data: this.loginParams,
      method: "post"
    });
    //把token存入到缓存中,同时跳转回上一个页面(这里我们使用了一个自定义的token,因为我们不是企业微信账户)
    wx.setStorageSync('token', 'BearereyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjIzLCJpYXQiOjE1NjQ3MzAwNzksImV4cCI6MTAwMTU2NDczMDA3OH0.YPt-XeLnjV-_1ITaXGY2FhxmCe4NvXuRnRB8OMCfnPo');
    // 返回上一页
    wx.navigateBack({
      delta: 1
    })
    
  }
})

运行后,点击授权按钮,缓存中数据如下所示
小程序购物城项目实战(中篇)_第22张图片
并且点击过一次授权后,此时在支付页面,点击支付按钮,就会出现如下效果
小程序购物城项目实战(中篇)_第23张图片

你可能感兴趣的:(前端,小程序)