Small program shopping mall project practice (Part 2)

Keywords: Front-end Mini Program

Tips

(Part 2) undertake (Part 1). The interface document and Ali icon library have been given in (Part 1). The code pasted in this article may be incomplete. See (Part 2) for the complete code.

Dynamic rendering of product details rotation chart

First, write the structure of the rotation chart in goods_ Modify the code in index.wxml under the detail folder. Then index.wxss changes the style

<!--pages/goods_detail/index.wxml-->
<view class="detail_swiper">
  <!-- Content of rotation chart -->
  <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%;
}

Improve the product profile on the product details page

The rich text tag rich text is used. The data to be rendered is in the data returned by the request interface in the interface document. We use goodsObj to store the returned data.

<!--pages/goods_detail/index.wxml-->
<view class="detail_swiper">
  <!-- Content of rotation chart -->
  <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>
<!-- Product content text -->
<!-- commodity price -->
<view class="goods_price">¥{{goodsObj.data.message.goods_price}}</view>
<view class="goods_name_row">
  <!-- Commodity name -->
  <view class="goods_name">{{goodsObj.data.message.goods_name}}</view>
  <!-- Collect goods -->
  <view class="goods_collect">
    <!-- The collection icons here are imported from Ali icon library -->
    <text class="iconfont icon-shoucang"></text>
    <view class="collect_text">Collection</view>
  </view>
</view>

<!-- Graphic details -->
<view class="goods_info">
  <view class="goods_info_title">Graphic details</view>
  <view class="goods_info_content">
    <!-- Rich text rendering -->
    <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;
  /* The spindle direction becomes the up and down (vertical axis) direction */
  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;
  /* When the text exceeds a lot, use ellipsis to replace the extra text */
  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 {}

The effect is as follows

Product details page - optimize dynamic rendering

When observing the data in goodsObj, we find that some of the data stored inside are not used, which will occupy the performance of the applet. Therefore, we need to process the assignment function of goodsObj, and some iPhones do not recognize the webp image format. Therefore, the returned data is the webp image, which may fail to render on Apple phones. Solve the above two problems:

  • Optimize the code of setData in index.js.
  • Modify the format of using wx:for to traverse data in index.wxml
  • Temporarily modify the image in webp format and use the replace function
// pages/goods_detail/index.js
//The method for sending requests is introduced to optimize the
import {
  request
} from "../../request/index.js"
Page({

  /**
   * Initial data of the page
   */
  data: {
    //The data returned by the request is in the form of an object
    goodsObj: {}
  },

  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {
    //The product id passed when getting the jump page
    const {
      goods_id
    } = options;
    this.getGoodsDetail(goods_id);
  },
  /**
   * Get product details data
   */
  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({
      // Optimize the storage data, and only assign and store the data used by the applet
      goodsObj:{
        goods_name:goodsObj.data.message.goods_name,
        goods_price:goodsObj.data.message.goods_price,
        // Some iphone phones do not support webp format
        // Background modification
        // Or use the replace function for temporary modification, where \. webp is the file to find all. webp, g means select all, and. jpg means replace all with. jpg format.
        goods_introduce:goodsObj.data.message.goods_introduce.replace(/\.webp/g,'.jpg'),
        pics:goodsObj.data.message.pics
      }
    })
  },
  /**
   * Life cycle function -- monitor the completion of the first rendering of the page
   */
  onReady: function () {

  },

  /**
   * Life cycle function -- monitor page display
   */
  onShow: function () {

  },

  /**
   * Life cycle function -- listening for page hiding
   */
  onHide: function () {

  },

  /**
   * Life cycle function -- listen for page unloading
   */
  onUnload: function () {

  },

  /**
   * Page related event handler -- listen to user drop-down actions
   */
  onPullDownRefresh: function () {

  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function () {

  },

  /**
   * Users click the upper right corner to share
   */
  onShareAppMessage: function () {

  }
})
<!--pages/goods_detail/index.wxml-->
<view class="detail_swiper">
  <!-- Content of rotation chart -->
  <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>
<!-- Product content text -->
<!-- commodity price -->
<view class="goods_price">¥{{goodsObj.goods_price}}</view>
<view class="goods_name_row">
  <!-- Commodity name -->
  <view class="goods_name">{{goodsObj.goods_name}}</view>
  <!-- Collect goods -->
  <view class="goods_collect">
    <!-- The collection icons here are imported from Ali icon library -->
    <text class="iconfont icon-shoucang"></text>
    <view class="collect_text">Collection</view>
  </view>
</view>

<!-- Graphic details -->
<view class="goods_info">
  <view class="goods_info_title">Graphic details</view>
  <view class="goods_info_content">
    <!-- Rich text rendering -->
    <rich-text nodes="{{goodsObj.goods_introduce}}"></rich-text>
  </view>
</view>

The effect is normal, and there are only four kinds of data in goodsObj.

Click the rotation chart to preview the large picture

Click the rotation chart in the commodity details to zoom in and preview. The steps are as follows

  • Bind the click event to the rotation chart in index.wxml
  • Call the api of the applet, previewImage
// pages/goods_detail/index.js
//The method for sending requests is introduced to optimize the
import {
  request
} from "../../request/index.js"
Page({

  /**
   * Initial data of the page
   */
  data: {
    //The data returned by the request is in the form of an object
    goodsObj: {}
    
  },
  //Global variable that defines the large image information array to preview
  goodsInfo: {},
  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {
    //The product id passed when getting the jump page
    const {
      goods_id
    } = options;
    this.getGoodsDetail(goods_id);
  },
  /**
   * Get product details data
   */
  async getGoodsDetail(goods_id) {
    const goodsObj = await request({
      url: "https://api-hmugo-web.itheima.net/api/public/v1/goods/detail",
      data: {
        goods_id
      }
    });
    //Assign a value to the previously defined preview large image array after the request is successful
    this.goodsInfo=goodsObj.data.message;
    this.setData({
      // Optimize the storage data, and only assign and store the data used by the applet
      goodsObj: {
        goods_name: goodsObj.data.message.goods_name,
        goods_price: goodsObj.data.message.goods_price,
        // Some iphone phones do not support webp format
        // Background modification
        // Or use the replace function for temporary modification, where \. webp is the file to find all. webp, g means select all, and. jpg means replace all with. jpg format.
        goods_introduce: goodsObj.data.message.goods_introduce.replace(/\.webp/g, '.jpg'),
        pics: goodsObj.data.message.pics
      },
    })
  },
  /**
   * Click the carousel to preview the large picture event
   */
  handlePrevewImage(e) {
    console.log('preview');
    //  First build an array of pictures to preview
    const urls=this.goodsInfo.pics.map(v=>v.pics_mid)
    // Accept the passed image url
    const current = e.currentTarget.dataset.url
    wx.previewImage({
      current: current,
      urls: urls
    })
  },
  /**
   * Life cycle function -- monitor the completion of the first rendering of the page
   */
  onReady: function () {

  },

  /**
   * Life cycle function -- monitor page display
   */
  onShow: function () {

  },

  /**
   * Life cycle function -- listening for page hiding
   */
  onHide: function () {

  },

  /**
   * Life cycle function -- listen for page unloading
   */
  onUnload: function () {

  },

  /**
   * Page related event handler -- listen to user drop-down actions
   */
  onPullDownRefresh: function () {

  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function () {

  },

  /**
   * Users click the upper right corner to share
   */
  onShareAppMessage: function () {

  }
})
<!--pages/goods_detail/index.wxml-->
<view class="detail_swiper">
  <!-- Content of rotation chart,bindtap Bind a preview big picture event -->
  <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>
<!-- Product content text -->
<!-- commodity price -->
<view class="goods_price">¥{{goodsObj.goods_price}}</view>
<view class="goods_name_row">
  <!-- Commodity name -->
  <view class="goods_name">{{goodsObj.goods_name}}</view>
  <!-- Collect goods -->
  <view class="goods_collect">
    <!-- The collection icons here are imported from Ali icon library -->
    <text class="iconfont icon-shoucang"></text>
    <view class="collect_text">Collection</view>
  </view>
</view>

<!-- Graphic details -->
<view class="goods_info">
  <view class="goods_info_title">Graphic details</view>
  <view class="goods_info_content">
    <!-- Rich text rendering -->
    <rich-text nodes="{{goodsObj.goods_introduce}}"></rich-text>
  </view>
</view>

The effect is as follows

Toolbar at the bottom of the item details page

The customer service, sharing and other functions in the bottom navigation bar can respond after clicking. Depending on the openness of the applet, you can find relevant instructions in the documents on the official website. At the same time, the buttons are cleverly hidden at the bottom of these two tags. In fact, when we click customer service and analysis, we click the hidden buttons. We also realized clicking the shopping cart icon to jump to the cart shopping cart page.
Note: navigator is a hyperlink like tag. It jumps in the navigator mode by default, but it cannot jump to tabbar (navigation bar page), so we use open type = "switchtab" to jump.

<!--pages/goods_detail/index.wxml-->
<view class="detail_swiper">
  <!-- Content of rotation chart,bindtap Bind a preview big picture event -->
  <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>
<!-- Product content text -->
<!-- commodity price -->
<view class="goods_price">¥{{goodsObj.goods_price}}</view>
<view class="goods_name_row">
  <!-- Commodity name -->
  <view class="goods_name">{{goodsObj.goods_name}}</view>
  <!-- Collect goods -->
  <view class="goods_collect">
    <!-- The collection icons here are imported from Ali icon library -->
    <text class="iconfont icon-shoucang"></text>
    <view class="collect_text">Collection</view>
  </view>
</view>

<!-- Graphic details -->
<view class="goods_info">
  <view class="goods_info_title">Graphic details</view>
  <view class="goods_info_content">
    <!-- Rich text rendering -->
    <rich-text nodes="{{goodsObj.goods_introduce}}"></rich-text>
  </view>
</view>

<!-- Bottom navigation bar -->
<view class="btm_tool">
  <!-- customer service -->
  <view class="tool_item">
    <view class="iconfont icon-kefu"></view>
    <view>customer service</view>
    <!-- Add the function of contacting customer service ,Hidden in the lower layer of customer service, the transparency is 0. Set its height and width to be consistent with that of customer service-->
    <button open-type="contact"></button>
  </view>
  <!-- share -->
  <view class="tool_item">
    <view class="iconfont icon-fenxiang"></view>
    <view>share</view>
    <button open-type="share"></button>
  </view>
  <!-- Shopping Cart -->
  <!-- switchTab Allow jump tabBar(Navigation bar) page -->
  <navigator open-type="switchTab" url="/pages/cart/index" class="tool_item">
    <view class="tool_item">
      <view class="iconfont icon-gouwuche"></view>
      <view>Shopping Cart</view>
    </view>
  </navigator>
  <!-- add to cart -->
  <view class="tool_item btn_cart">
    <view class="">add to cart</view>
  </view>
  <!-- Buy now -->
  <view class="tool_item btn_buy">
    <view>Buy now</view>
  </view>
</view>
/* pages/goods_detail/index.wxss */
/* Prevent the added fixed navigation bar from blocking part of the page content when the page slides to the bottom,
Here, 90rpx is expanded at the bottom of the setup page to fix the navigation bar */
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;
  /* The spindle direction becomes the up and down (vertical axis) direction */
  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;
  /* When the text exceeds a lot, use ellipsis to replace the extra text */
  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 Add fixed positioning, fixed in the page */
  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;
  /* Change the spindle direction to the up and down direction */
  flex-direction: column;
  /* Vertical and horizontal centering */
  justify-content: center;
  text-align: center;
  font-size: 24rpx;
  /* Relative positioning of parent style */
  position: relative;
}
.btm_tool .tool_item button{
  /* Sub style absolute positioning */
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  /* transparency */
  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;
}

The effect is as follows

Add to cart on product details page

On the product details page, after viewing a product, click Add to the shopping cart to realize the add function. The implementation steps are as follows

  • Bind click event first
  • Get the shopping cart data in the cache in array format
  • Judge whether the current item already exists in the shopping cart
  • If it exists, modify the commodity data and add one to the quantity of the commodity in the shopping cart
  • Fill (update) the array of the current shopping cart into the cache
  • If it does not exist, add a new element directly to the shopping cart array
  • Fill the current shopping cart data into the cache
  • Pop up some tips
    The code is as follows, or in goods_ Modify index.wxml and index.js in the detail folder
<!--pages/goods_detail/index.wxml-->
<view class="detail_swiper">
  <!-- Content of rotation chart,bindtap Bind a preview big picture event -->
  <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>
<!-- Product content text -->
<!-- commodity price -->
<view class="goods_price">¥{{goodsObj.goods_price}}</view>
<view class="goods_name_row">
  <!-- Commodity name -->
  <view class="goods_name">{{goodsObj.goods_name}}</view>
  <!-- Collect goods -->
  <view class="goods_collect">
    <!-- The collection icons here are imported from Ali icon library -->
    <text class="iconfont icon-shoucang"></text>
    <view class="collect_text">Collection</view>
  </view>
</view>

<!-- Graphic details -->
<view class="goods_info">
  <view class="goods_info_title">Graphic details</view>
  <view class="goods_info_content">
    <!-- Rich text rendering -->
    <rich-text nodes="{{goodsObj.goods_introduce}}"></rich-text>
  </view>
</view>

<!-- Bottom navigation bar -->
<view class="btm_tool">
  <!-- customer service -->
  <view class="tool_item">
    <view class="iconfont icon-kefu"></view>
    <view>customer service</view>
    <!-- Add the function of contacting customer service ,Hidden in the lower layer of customer service, the transparency is 0. Set its height and width to be consistent with that of customer service-->
    <button open-type="contact"></button>
  </view>
  <!-- share -->
  <view class="tool_item">
    <view class="iconfont icon-fenxiang"></view>
    <view>share</view>
    <button open-type="share"></button>
  </view>
  <!-- Shopping Cart -->
  <!-- switchTab Allow jump tabBar(Navigation bar) page -->
  <navigator open-type="switchTab" url="/pages/cart/index" class="tool_item">
    <view class="tool_item">
      <view class="iconfont icon-gouwuche"></view>
      <view>Shopping Cart</view>
    </view>
  </navigator>
  <!-- add to cart -->
  <!-- Add click event and product response -->
  <view class="tool_item btn_cart" bindtap="handleCartAdd">
    <view>add to cart</view>
  </view>
  <!-- Buy now -->
  <view class="tool_item btn_buy">
    <view>Buy now</view>
  </view>
</view>
// pages/goods_detail/index.js
//The method for sending requests is introduced to optimize the
import {
  request
} from "../../request/index.js"
Page({

  /**
   * Initial data of the page
   */
  data: {
    //The data returned by the request is in the form of an object
    goodsObj: {}

  },
  //Global variable that defines the large image information array to preview
  goodsInfo: {},
  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {
    //The product id passed when getting the jump page
    const {
      goods_id
    } = options;
    this.getGoodsDetail(goods_id);
  },
  /**
   * Get product details data
   */
  async getGoodsDetail(goods_id) {
    const goodsObj = await request({
      url: "https://api-hmugo-web.itheima.net/api/public/v1/goods/detail",
      data: {
        goods_id
      }
    });
    //Assign a value to the previously defined preview large image array after the request is successful
    this.goodsInfo = goodsObj.data.message;
    this.setData({
      // Optimize the storage data, and only assign and store the data used by the applet
      goodsObj: {
        goods_name: goodsObj.data.message.goods_name,
        goods_price: goodsObj.data.message.goods_price,
        // Some iphone phones do not support webp format
        // Background modification
        // Or use the replace function for temporary modification, where \. webp is the file to find all. webp, g means select all, and. jpg means replace all with. jpg format.
        goods_introduce: goodsObj.data.message.goods_introduce.replace(/\.webp/g, '.jpg'),
        pics: goodsObj.data.message.pics
      },
    })
  },
  /**
   * Click the carousel to preview the large picture event
   */
  handlePrevewImage(e) {
    console.log('preview');
    //  First build an array of pictures to preview
    const urls = this.goodsInfo.pics.map(v => v.pics_mid)
    // Accept the passed image url
    const current = e.currentTarget.dataset.url
    wx.previewImage({
      current: current,
      urls: urls
    })
  },
  /**
   * Event triggered when a user item is added to the shopping cart
   */
  handleCartAdd(e) {
    //Get the cached commodity data and convert it from string format to array format
    let cart=wx.getStorageSync('cart')||[];
    // Determine whether the item already exists in the shopping cart array
    let index=cart.findIndex(v=>v.goods_id===this.goodsInfo.goods_id);
    if(index===-1){
      //Does not exist, added for the first time
      this.goodsInfo.num=1;
      cart.push(this.goodsInfo);
    }else{
      //existence
      cart[index].num++;
    }
    //The shopping cart array is updated to the cache
    wx.setStorageSync("cart",cart);
    //Pop up prompt
    wx.showToast({
      title: 'Added successfully',
      icon:'success',
      mask:'true'
    })
  },
  /**
   * Life cycle function -- monitor the completion of the first rendering of the page
   */
  onReady: function () {

  },

  /**
   * Life cycle function -- monitor page display
   */
  onShow: function () {

  },

  /**
   * Life cycle function -- listening for page hiding
   */
  onHide: function () {

  },

  /**
   * Life cycle function -- listen for page unloading
   */
  onUnload: function () {

  },

  /**
   * Page related event handler -- listen to user drop-down actions
   */
  onPullDownRefresh: function () {

  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function () {

  },

  /**
   * Users click the upper right corner to share
   */
  onShareAppMessage: function () {

  }
})

Shopping cart content page

After clicking Add to the shopping cart, the goods will be added to the shopping cart, and then click the shopping cart to enter the shopping cart content page. This page corresponds to the cart folder. First, modify the page title and modify the code in the index.json file under the cart folder

{
  "usingComponents": {},
  "navigationBarTitleText": "Shopping Cart"
}

Then start writing the relevant page structure and modify the code in index.wxml.

<!--pages/cart/index.wxml-->
<!-- Receiving address container -->
<view class="receive_address">
  <!-- Receiving address button -->
  <!-- wx:if Judge whether the user name in the address exists,Show when not present -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain Indicates whether the button is hollow and the background color is transparent -->
    <!-- Bind the click event to obtain the receiving address -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">Add receiving address</button>
  </view>
  <!-- When the receiving address user name exists, that is, there is a receiving address -->
  <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>

<!-- Shopping cart details -->
<view class="cart_content">
  <view class="cart_title">Shopping Cart</view>
  <view class="cart_main">
    <view class="cart_item">
      <!-- Check box structure -->
      <view class="cart_checkbox">
        <checkbox-group bindchange="">
          <checkbox></checkbox>
        </checkbox-group>
      </view>
      <!-- Product picture -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="http://image2.suning.cn/uimg/b2c/newcatentries/0000000000-000000000178667792_2_800x800.jpg">
        </image>
      </navigator>
      <!-- Commodity information -->
      <view class="cart_info">
        <view class="goods_name">TCL 65Q960C 65 4 inch Harman Caton artificial intelligence metal ultra thin 64 bit 34 core K+HDR Primary color quantum dot curved surface TV (gray)</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>

<!-- Bottom navigation bar -->
<view class="footer_tool">
  <!-- Select all -->
  <view class="all_check_wrap">
    <checkbox-group>
      <checkbox>Select all</checkbox>
    </checkbox-group>
  </view>
  <!-- Total price -->
  <view class="total_price_wrap">
    <view class="total_price">
    total:<text class="total_price_number">¥999</text>
    </view>
    <view>Including freight</view>
  </view>
  <!-- settlement -->
  <view class="order_pay_wrap">settlement(1)</view>
</view>

Style file

/* 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;
    /* Align text right */
    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;
}

Now the static display of a shopping cart page is ready

Dynamic data update of shopping cart page

The onShow function in the index.js file needs to get the shopping cart array in the cache, and then update the shopping cart array to data.

// pages/cart/index.js
Page({
  // Click to get receiving address event
  // 1. Bind click event
  // 2. Call the API in the applet, "Wx. Chooseaddress", to obtain the receiving address of the user
  // Shopping cart page loaded
  // 1. Obtain the receiving address data in the local storage
  // 2. If there is storage, set the data to a variable in data
  handleChooseAddress(e) {
    wx.chooseAddress({
      success: (result) => {
        // Store the obtained address into the local cache, and splice the address before storage
        result.all = result.provinceName + result.cityName + result.countyName + result.detailInfo;
        console.log(result);
        wx.setStorageSync('address', result)
      },
    });
  },
  /**
   * Initial data of the page
   */
  data: {
    address: {},
    //Declare a variable, shopping cart array
    cart:[]
  },

  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {

  },

  /**
   * Life cycle function -- monitor the completion of the first rendering of the page
   */
  onReady: function () {

  },

  /**
   * Life cycle function -- monitor page display
   */
  onShow: function () {
    //Get shopping cart data in cache
    const cart=wx.getStorageSync('cart')
    //Get the receiving address information in the cache
    const address = wx.getStorageSync('address');
    //Assign value to data
    this.setData({
      address,
      cart:cart
    })
  },

  /**
   * Life cycle function -- listening for page hiding
   */
  onHide: function () {

  },

  /**
   * Life cycle function -- listen for page unloading
   */
  onUnload: function () {

  },

  /**
   * Page related event handler -- listen to user drop-down actions
   */
  onPullDownRefresh: function () {

  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function () {

  },

  /**
   * Users click the upper right corner to share
   */
  onShareAppMessage: function () {

  }
})
<!--pages/cart/index.wxml-->
<!-- Receiving address container -->
<view class="receive_address">
  <!-- Receiving address button -->
  <!-- wx:if Judge whether the user name in the address exists,Show when not present -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain Indicates whether the button is hollow and the background color is transparent -->
    <!-- Bind the click event to obtain the receiving address -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">Add receiving address</button>
  </view>
  <!-- When the receiving address user name exists, that is, there is a receiving address -->
  <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>

<!-- Shopping cart details -->
<view class="cart_content">
  <view class="cart_title">Shopping Cart</view>
  <view class="cart_main">
    <view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">
      <!-- Check box structure -->
      <view class="cart_checkbox">
        <checkbox-group bindchange="">
          <checkbox></checkbox>
        </checkbox-group>
      </view>
      <!-- Product picture -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="{{item.goods_small_logo}}">
        </image>
      </navigator>
      <!-- Commodity information -->
      <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>

<!-- Bottom navigation bar -->
<view class="footer_tool">
  <!-- Select all -->
  <view class="all_check_wrap">
    <checkbox-group>
      <checkbox>Select all</checkbox>
    </checkbox-group>
  </view>
  <!-- Total price -->
  <view class="total_price_wrap">
    <view class="total_price">
    total:<text class="total_price_number">¥999</text>
    </view>
    <view>Including freight</view>
  </view>
  <!-- settlement -->
  <view class="order_pay_wrap">settlement(1)</view>
</view>

The effect is as follows

The next step is to select the goods added to the shopping cart by default, and the check box is selected. In goods_ Modify index.js in the detail folder.

// pages/goods_detail/index.js
//The method for sending requests is introduced to optimize the
import {
  request
} from "../../request/index.js"
Page({

  /**
   * Initial data of the page
   */
  data: {
    //The data returned by the request is in the form of an object
    goodsObj: {}

  },
  //Global variable that defines the large image information array to preview
  goodsInfo: {},
  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {
    //The product id passed when getting the jump page
    const {
      goods_id
    } = options;
    this.getGoodsDetail(goods_id);
  },
  /**
   * Get product details data
   */
  async getGoodsDetail(goods_id) {
    const goodsObj = await request({
      url: "https://api-hmugo-web.itheima.net/api/public/v1/goods/detail",
      data: {
        goods_id
      }
    });
    //Assign a value to the previously defined preview large image array after the request is successful
    this.goodsInfo = goodsObj.data.message;
    this.setData({
      // Optimize the storage data, and only assign and store the data used by the applet
      goodsObj: {
        goods_name: goodsObj.data.message.goods_name,
        goods_price: goodsObj.data.message.goods_price,
        // Some iphone phones do not support webp format
        // Background modification
        // Or use the replace function for temporary modification, where \. webp is the file to find all. webp, g means select all, and. jpg means replace all with. jpg format.
        goods_introduce: goodsObj.data.message.goods_introduce.replace(/\.webp/g, '.jpg'),
        pics: goodsObj.data.message.pics
      },
    })
  },
  /**
   * Click the carousel to preview the large picture event
   */
  handlePrevewImage(e) {
    console.log('preview');
    //  First build an array of pictures to preview
    const urls = this.goodsInfo.pics.map(v => v.pics_mid)
    // Accept the passed image url
    const current = e.currentTarget.dataset.url
    wx.previewImage({
      current: current,
      urls: urls
    })
  },
  /**
   * Event triggered when a user item is added to the shopping cart
   */
  handleCartAdd(e) {
    //Get the cached commodity data and convert it from string format to array format
    let cart=wx.getStorageSync('cart')||[];
    // Determine whether the item already exists in the shopping cart array
    let index=cart.findIndex(v=>v.goods_id===this.goodsInfo.goods_id);
    if(index===-1){
      //Does not exist, added for the first time
      this.goodsInfo.num=1;
      // Add a checked attribute to the product, with the value of true, so that it can be selected in the check box on the shopping cart page
      this.goodsInfo.checked=true;
      cart.push(this.goodsInfo);
    }else{
      //existence
      cart[index].num++;
    }
    //The shopping cart array is updated to the cache
    wx.setStorageSync("cart",cart);
    //Pop up prompt
    wx.showToast({
      title: 'Added successfully',
      icon:'success',
      mask:'true'
    })
  },
  /**
   * Life cycle function -- monitor the completion of the first rendering of the page
   */
  onReady: function () {

  },

  /**
   * Life cycle function -- monitor page display
   */
  onShow: function () {

  },

  /**
   * Life cycle function -- listening for page hiding
   */
  onHide: function () {

  },

  /**
   * Life cycle function -- listen for page unloading
   */
  onUnload: function () {

  },

  /**
   * Page related event handler -- listen to user drop-down actions
   */
  onPullDownRefresh: function () {

  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function () {

  },

  /**
   * Users click the upper right corner to share
   */
  onShareAppMessage: function () {

  }
})

Then add attributes to the checkbox tag in index.wxml under the cart folder.

<!--pages/cart/index.wxml-->
<!-- Receiving address container -->
<view class="receive_address">
  <!-- Receiving address button -->
  <!-- wx:if Judge whether the user name in the address exists,Show when not present -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain Indicates whether the button is hollow and the background color is transparent -->
    <!-- Bind the click event to obtain the receiving address -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">Add receiving address</button>
  </view>
  <!-- When the receiving address user name exists, that is, there is a receiving address -->
  <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>

<!-- Shopping cart details -->
<view class="cart_content">
  <view class="cart_title">Shopping Cart</view>
  <view class="cart_main">
    <view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">
      <!-- Check box structure -->
      <view class="cart_checkbox">
        <checkbox-group bindchange="">
          <checkbox checked="{{item.checked}}"></checkbox>
        </checkbox-group>
      </view>
      <!-- Product picture -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="{{item.goods_small_logo}}">
        </image>
      </navigator>
      <!-- Commodity information -->
      <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>

<!-- Bottom navigation bar -->
<view class="footer_tool">
  <!-- Select all -->
  <view class="all_check_wrap">
    <checkbox-group>
      <checkbox>Select all</checkbox>
    </checkbox-group>
  </view>
  <!-- Total price -->
  <view class="total_price_wrap">
    <view class="total_price">
    total:<text class="total_price_number">¥999</text>
    </view>
    <view>Including freight</view>
  </view>
  <!-- settlement -->
  <view class="order_pay_wrap">settlement(1)</view>
</view>

The effect is as follows

Next is the implementation of select all in the bottom navigation bar and the dynamic update of data. The steps are as follows
1. Get the shopping cart array in the cache in Onshow
2. According to the commodity data in the shopping cart, when all commodities are selected, select all is selected, one is not selected, and the select all button is not selected.
3. Calculate the total price and total quantity only when the commodity is selected.
4. Get shopping cart array in cache
5. Traversal
6. Total price = unit price of goods * quantity of goods
7. Total quantity = cumulative quantity of all commodities
8. Update the calculated price quantity to data.

// pages/cart/index.js
Page({
  // Click to get receiving address event
  // 1. Bind click event
  // 2. Call the API in the applet, "Wx. Chooseaddress", to obtain the receiving address of the user
  // Shopping cart page loaded
  // 1. Obtain the receiving address data in the local storage
  // 2. If there is storage, set the data to a variable in data
  handleChooseAddress(e) {
    wx.chooseAddress({
      success: (result) => {
        // Store the obtained address into the local cache, and splice the address before storage
        result.all = result.provinceName + result.cityName + result.countyName + result.detailInfo;
        console.log(result);
        wx.setStorageSync('address', result)
      },
    });
  },
  /**
   * Initial data of the page
   */
  data: {
    address: {},
    //Declare a variable, shopping cart array
    cart:[],
    // Declare a variable, whether to select all the contents of the shopping cart
    allChecked:false,
    // Define total price and total quantity of goods
    totalPrice:0,
    totalNum:0
  },

  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {

  },

  /**
   * Life cycle function -- monitor the completion of the first rendering of the page
   */
  onReady: function () {

  },

  /**
   * Life cycle function -- monitor page display
   */
  onShow: function () {
    //Get shopping cart data in cache
    const cart=wx.getStorageSync('cart')
    // Calculate whether to select all shopping cart items
    // The every array method will traverse and receive callback functions, so each callback function will return true, and the return value of the every method will be true
    // As long as a callback function returns false, it will not execute circularly and return false directly
    // If it is an empty array, then every returns true, so a ternary expression is used
    const allChecked=cart.length?cart.every(v=>v.checked):false;
    //Get the receiving address information in the cache
    const address = wx.getStorageSync('address');
    //  Declare total price and total quantity
    let totalPrice=0;
    let totalNum=0;
    cart.forEach(v=>{
      if(v.checked){
        totalPrice+=v.num*v.goods_price;
        totalNum+=v.num;
      }
    })
    //Assign value to data
    this.setData({
      address,
      cart:cart,
      allChecked,
      totalPrice,
      totalNum
    })
  },

  /**
   * Life cycle function -- listening for page hiding
   */
  onHide: function () {

  },

  /**
   * Life cycle function -- listen for page unloading
   */
  onUnload: function () {

  },

  /**
   * Page related event handler -- listen to user drop-down actions
   */
  onPullDownRefresh: function () {

  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function () {

  },

  /**
   * Users click the upper right corner to share
   */
  onShareAppMessage: function () {

  }
})
<!--pages/cart/index.wxml-->
<!-- Receiving address container -->
<view class="receive_address">
  <!-- Receiving address button -->
  <!-- wx:if Judge whether the user name in the address exists,Show when not present -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain Indicates whether the button is hollow and the background color is transparent -->
    <!-- Bind the click event to obtain the receiving address -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">Add receiving address</button>
  </view>
  <!-- When the receiving address user name exists, that is, there is a receiving address -->
  <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>

<!-- Shopping cart details -->
<view class="cart_content">
  <view class="cart_title">Shopping Cart</view>
  <view class="cart_main">
    <view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">
      <!-- Check box structure -->
      <view class="cart_checkbox">
        <checkbox-group bindchange="">
          <checkbox checked="{{item.checked}}"></checkbox>
        </checkbox-group>
      </view>
      <!-- Product picture -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="{{item.goods_small_logo}}">
        </image>
      </navigator>
      <!-- Commodity information -->
      <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>

<!-- Bottom navigation bar -->
<view class="footer_tool">
  <!-- Select all -->
  <view class="all_check_wrap">
    <checkbox-group>
      <checkbox checked="{{allChecked}}">Select all</checkbox>
    </checkbox-group>
  </view>
  <!-- Total price -->
  <view class="total_price_wrap">
    <view class="total_price">
    total:<text class="total_price_number">¥{{totalPrice}}</text>
    </view>
    <view>Including freight</view>
  </view>
  <!-- settlement -->
  <view class="order_pay_wrap">settlement({{totalNum}})</view>
</view>

The effect is as follows

Shopping cart item quantity and selected function optimization

Optimization of selected functions of goods
1. Bind change event
2. Get the modified commodity object
3. The selected status of the commodity object is reversed
4. Refill data and cache
5. Recalculate select all. Total price total quantity

// pages/cart/index.js
Page({
  /**
   * Encapsulate a function to recalculate the bottom toolbar, total price and total quantity while setting the commodity status of the shopping cart 
   */
  setCart(cart) {
    let allChecked = true;
    //  Declare total price and total quantity
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      } else {
        allChecked = false;
      }
    })
    //Determine whether the array is empty
    allChecked = cart.length != 0 ? allChecked : false;
    this.setData({
      cart,
      totalPrice,
      totalNum,
      allChecked
    });
    wx.setStorageSync('cart', cart);
  },
  // Click to get receiving address event
  // 1. Bind click event
  // 2. Call the API in the applet, "Wx. Chooseaddress", to obtain the receiving address of the user
  // Shopping cart page loaded
  // 1. Obtain the receiving address data in the local storage
  // 2. If there is storage, set the data to a variable in data
  handleChooseAddress(e) {
    wx.chooseAddress({
      success: (result) => {
        // Store the obtained address into the local cache, and splice the address before storage
        result.all = result.provinceName + result.cityName + result.countyName + result.detailInfo;
        console.log(result);
        wx.setStorageSync('address', result)
      },
    });
  },

  // Selected optimization event for a product
  handleItemChange(e) {
    //Get the modified product id
    const goods_id = e.currentTarget.dataset.id;
    // Get shopping cart array
    let {
      cart
    } = this.data;
    //Find the item object to be modified in the shopping cart array
    let index = cart.findIndex(v => v.goods_id === goods_id);
    // Modify the selected status of the commodity object and invert it
    cart[index].checked = !cart[index].checked;
    // Update the data to the data and cache, and recalculate the total price and total quantity by calling the encapsulated function setCart
    this.setCart(cart);
  },
  /**
   * Initial data of the page
   */
  data: {
    address: {},
    //Declare a variable, shopping cart array
    cart: [],
    // Declare a variable, whether to select all the contents of the shopping cart
    allChecked: false,
    // Define total price and total quantity of goods
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {

  },

  /**
   * Life cycle function -- monitor the completion of the first rendering of the page
   */
  onReady: function () {

  },

  /**
   * Life cycle function -- monitor page display
   */
  onShow: function () {
    //Get shopping cart data in cache
    const cart = wx.getStorageSync('cart')
    // Calculate whether to select all shopping cart items
    // The every array method will traverse and receive callback functions, so each callback function will return true, and the return value of the every method will be true
    // As long as a callback function returns false, it will not execute circularly and return false directly
    // If it is an empty array, then every returns true, so a ternary expression is used
    const allChecked = cart.length ? cart.every(v => v.checked) : false;
    //Get the receiving address information in the cache
    const address = wx.getStorageSync('address');
    //  Declare total price and total quantity
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      }
    })
    //Assign value to data
    this.setData({
      address,
      cart: cart,
      allChecked,
      totalPrice,
      totalNum
    })
  },

  /**
   * Life cycle function -- listening for page hiding
   */
  onHide: function () {

  },

  /**
   * Life cycle function -- listen for page unloading
   */
  onUnload: function () {

  },

  /**
   * Page related event handler -- listen to user drop-down actions
   */
  onPullDownRefresh: function () {

  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function () {

  },

  /**
   * Users click the upper right corner to share
   */
  onShareAppMessage: function () {

  }
})
<!--pages/cart/index.wxml-->
<!-- Receiving address container -->
<view class="receive_address">
  <!-- Receiving address button -->
  <!-- wx:if Judge whether the user name in the address exists,Show when not present -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain Indicates whether the button is hollow and the background color is transparent -->
    <!-- Bind the click event to obtain the receiving address -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">Add receiving address</button>
  </view>
  <!-- When the receiving address user name exists, that is, there is a receiving address -->
  <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>

<!-- Shopping cart details -->
<view class="cart_content">
  <view class="cart_title">Shopping Cart</view>
  <view class="cart_main">
    <view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">
      <!-- Check box structure -->
      <view class="cart_checkbox">
        <checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
          <checkbox checked="{{item.checked}}"></checkbox>
        </checkbox-group>
      </view>
      <!-- Product picture -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="{{item.goods_small_logo}}">
        </image>
      </navigator>
      <!-- Commodity information -->
      <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>

<!-- Bottom navigation bar -->
<view class="footer_tool">
  <!-- Select all -->
  <view class="all_check_wrap">
    <checkbox-group>
      <checkbox checked="{{allChecked}}">Select all</checkbox>
    </checkbox-group>
  </view>
  <!-- Total price -->
  <view class="total_price_wrap">
    <view class="total_price">
    total:<text class="total_price_number">¥{{totalPrice}}</text>
    </view>
    <view>Including freight</view>
  </view>
  <!-- settlement -->
  <view class="order_pay_wrap">settlement({{totalNum}})</view>
</view>

The effect is as follows

Next, click the select all button on the shopping cart page to select all products on the page. The steps are as follows
1 select all checkbox to bind event change
2 get the all selected variable allChecked in data
3 directly invert allchecked =! allChecked
4 traverse the shopping cart array so that the selected status of the goods in it changes with the change of allChecked
5 reset the shopping cart array and allChecked back to data, and reset the shopping cart back to the cache

// pages/cart/index.js
Page({
  /**
   * Encapsulate a function to recalculate the bottom toolbar, total price and total quantity while setting the commodity status of the shopping cart 
   */
  setCart(cart) {
    let allChecked = true;
    //  Declare total price and total quantity
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      } else {
        allChecked = false;
      }
    })
    //Determine whether the array is empty
    allChecked = cart.length != 0 ? allChecked : false;
    this.setData({
      cart,
      totalPrice,
      totalNum,
      allChecked
    });
    wx.setStorageSync('cart', cart);
  },
  // Click to get receiving address event
  // 1. Bind click event
  // 2. Call the API in the applet, "Wx. Chooseaddress", to obtain the receiving address of the user
  // Shopping cart page loaded
  // 1. Obtain the receiving address data in the local storage
  // 2. If there is storage, set the data to a variable in data
  handleChooseAddress(e) {
    wx.chooseAddress({
      success: (result) => {
        // Store the obtained address into the local cache, and splice the address before storage
        result.all = result.provinceName + result.cityName + result.countyName + result.detailInfo;
        console.log(result);
        wx.setStorageSync('address', result)
      },
    });
  },

  // Selected optimization event for a product
  handleItemChange(e) {
    //Get the modified product id
    const goods_id = e.currentTarget.dataset.id;
    // Get shopping cart array
    let {
      cart
    } = this.data;
    //Find the item object to be modified in the shopping cart array
    let index = cart.findIndex(v => v.goods_id === goods_id);
    // Modify the selected status of the commodity object and invert it
    cart[index].checked = !cart[index].checked;
    // Update the data to the data and cache, and recalculate the total price and total quantity by calling the encapsulated function setCart
    this.setCart(cart);
  },
  //Select all of products
  handleItemAllChecked(e) {
    // 1 get the data in data
    let {
      cart,
      allChecked
    } = this.data;
    // 2 modify value
    allChecked = !allChecked;
    // 3 cycle to modify the selected status of goods in the cart array
    cart.forEach(v => v.checked = allChecked);
    // 4 fill the modified value back into the data or cache, and call the encapsulated function
    this.setCart(cart);
  },
  /**
   * Initial data of the page
   */
  data: {
    address: {},
    //Declare a variable, shopping cart array
    cart: [],
    // Declare a variable, whether to select all the contents of the shopping cart
    allChecked: false,
    // Define total price and total quantity of goods
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {

  },

  /**
   * Life cycle function -- monitor the completion of the first rendering of the page
   */
  onReady: function () {

  },

  /**
   * Life cycle function -- monitor page display
   */
  onShow: function () {
    //Get shopping cart data in cache
    const cart = wx.getStorageSync('cart')
    // Calculate whether to select all shopping cart items
    // The every array method will traverse and receive callback functions, so each callback function will return true, and the return value of the every method will be true
    // As long as a callback function returns false, it will not execute circularly and return false directly
    // If it is an empty array, then every returns true, so a ternary expression is used
    const allChecked = cart.length ? cart.every(v => v.checked) : false;
    //Get the receiving address information in the cache
    const address = wx.getStorageSync('address');
    //  Declare total price and total quantity
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      }
    })
    //Assign value to data
    this.setData({
      address,
      cart: cart,
      allChecked,
      totalPrice,
      totalNum
    })
  },

  /**
   * Life cycle function -- listening for page hiding
   */
  onHide: function () {

  },

  /**
   * Life cycle function -- listen for page unloading
   */
  onUnload: function () {

  },

  /**
   * Page related event handler -- listen to user drop-down actions
   */
  onPullDownRefresh: function () {

  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function () {

  },

  /**
   * Users click the upper right corner to share
   */
  onShareAppMessage: function () {

  }
})
<!--pages/cart/index.wxml-->
<!-- Receiving address container -->
<view class="receive_address">
  <!-- Receiving address button -->
  <!-- wx:if Judge whether the user name in the address exists,Show when not present -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain Indicates whether the button is hollow and the background color is transparent -->
    <!-- Bind the click event to obtain the receiving address -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">Add receiving address</button>
  </view>
  <!-- When the receiving address user name exists, that is, there is a receiving address -->
  <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>

<!-- Shopping cart details -->
<view class="cart_content">
  <view class="cart_title">Shopping Cart</view>
  <view class="cart_main">
    <view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">
      <!-- Check box structure -->
      <view class="cart_checkbox">
        <checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
          <checkbox checked="{{item.checked}}"></checkbox>
        </checkbox-group>
      </view>
      <!-- Product picture -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="{{item.goods_small_logo}}">
        </image>
      </navigator>
      <!-- Commodity information -->
      <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>

<!-- Bottom navigation bar -->
<view class="footer_tool">
  <!-- Select all -->
  <view class="all_check_wrap">
    <checkbox-group bindchange="handleItemAllChecked">
      <checkbox checked="{{allChecked}}">Select all</checkbox>
    </checkbox-group>
  </view>
  <!-- Total price -->
  <view class="total_price_wrap">
    <view class="total_price">
    total:<text class="total_price_number">¥{{totalPrice}}</text>
    </view>
    <view>Including freight</view>
  </view>
  <!-- settlement -->
  <view class="order_pay_wrap">settlement({{totalNum}})</view>
</view>

The final effect is as follows. Each time you click the select all button, all the above commodities will be selected or not selected.

Shopping cart page, addition and subtraction of the quantity of a single item

Click the plus or minus button after a single commodity, the quantity of the commodity will change, and the total quantity and total price will change. The steps are as follows

  1. The "+" and "-" buttons are bound to the same click event. The key to distinguish is that the "+" sign of the user-defined attribute is defined as "+ 1" and the "-" sign is defined as "- 1".
  2. Pass the clicked product ID, goods__id .
  3. Get the shopping cart array in data to get the commodity object to be modified.
  4. Directly modify the quantity num of the commodity object.
  5. Reset the cart array back to the cache and data, and call the this. setCart that you encapsulated to calculate the total price and the total quantity.
  6. When the quantity is reduced to 1, click the minus sign button, and a pop-up window will prompt whether to delete the commodity. The pop-up window will prompt that there is the built-in API of the applet, wx.showModal.
// pages/cart/index.js
Page({
  /**
   * Encapsulate a function to recalculate the bottom toolbar, total price and total quantity while setting the commodity status of the shopping cart 
   */
  setCart(cart) {
    let allChecked = true;
    //  Declare total price and total quantity
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      } else {
        allChecked = false;
      }
    })
    //Determine whether the array is empty
    allChecked = cart.length != 0 ? allChecked : false;
    this.setData({
      cart,
      totalPrice,
      totalNum,
      allChecked
    });
    wx.setStorageSync('cart', cart);
  },
  // Click to get receiving address event
  // 1. Bind click event
  // 2. Call the API in the applet, "Wx. Chooseaddress", to obtain the receiving address of the user
  // Shopping cart page loaded
  // 1. Obtain the receiving address data in the local storage
  // 2. If there is storage, set the data to a variable in data
  handleChooseAddress(e) {
    wx.chooseAddress({
      success: (result) => {
        // Store the obtained address into the local cache, and splice the address before storage
        result.all = result.provinceName + result.cityName + result.countyName + result.detailInfo;
        console.log(result);
        wx.setStorageSync('address', result)
      },
    });
  },

  // Selected optimization event for a product
  handleItemChange(e) {
    //Get the modified product id
    const goods_id = e.currentTarget.dataset.id;
    // Get shopping cart array
    let {
      cart
    } = this.data;
    //Find the item object to be modified in the shopping cart array
    let index = cart.findIndex(v => v.goods_id === goods_id);
    // Modify the selected status of the commodity object and invert it
    cart[index].checked = !cart[index].checked;
    // Update the data to the data and cache, and recalculate the total price and total quantity by calling the encapsulated function setCart
    this.setCart(cart);
  },
  //Select all of products
  handleItemAllChecked(e) {
    // 1 get the data in data
    let {
      cart,
      allChecked
    } = this.data;
    // 2 modify value
    allChecked = !allChecked;
    // 3 cycle to modify the selected status of goods in the cart array
    cart.forEach(v => v.checked = allChecked);
    // 4 fill the modified value back into the data or cache, and call the encapsulated function
    this.setCart(cart);
  },
  //Commodity quantity addition and subtraction function
  handleItemNumEdit(e) {
    
    // 1 get the passed parameters
    const {
      operation,
      id
    } = e.currentTarget.dataset;
    // 2 get shopping cart array
    let {
      cart
    } = this.data;
    //3 find the index of the item to be modified
    const index = cart.findIndex(v => v.goods_id === id);
    //Determine whether to delete when the commodity quantity is less than 1
    if(cart[index].num===1&&operation===-1){
      wx.showModal({
        title: 'Tips',
        content: 'Do you want to delete this item?',
        success:(res)=>{
          if (res.confirm) {
            cart.splice(index,1);
            this.setCart(cart);
          } else if (res.cancel) {
            console.log('The user clicks cancel');
          }
        }
      })
    }else{
    // 4. Quantity to be modified
    cart[index].num += operation;
    // 5 set back to cache and data
    this.setCart(cart);}
  },
  /**
   * Initial data of the page
   */
  data: {
    address: {},
    //Declare a variable, shopping cart array
    cart: [],
    // Declare a variable, whether to select all the contents of the shopping cart
    allChecked: false,
    // Define total price and total quantity of goods
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {

  },

  /**
   * Life cycle function -- monitor the completion of the first rendering of the page
   */
  onReady: function () {

  },

  /**
   * Life cycle function -- monitor page display
   */
  onShow: function () {
    //Get shopping cart data in cache
    const cart = wx.getStorageSync('cart')
    // Calculate whether to select all shopping cart items
    // The every array method will traverse and receive callback functions, so each callback function will return true, and the return value of the every method will be true
    // As long as a callback function returns false, it will not execute circularly and return false directly
    // If it is an empty array, then every returns true, so a ternary expression is used
    const allChecked = cart.length ? cart.every(v => v.checked) : false;
    //Get the receiving address information in the cache
    const address = wx.getStorageSync('address');
    //  Declare total price and total quantity
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      }
    })
    //Assign value to data
    this.setData({
      address,
      cart: cart,
      allChecked,
      totalPrice,
      totalNum
    })
  },

  /**
   * Life cycle function -- listening for page hiding
   */
  onHide: function () {

  },

  /**
   * Life cycle function -- listen for page unloading
   */
  onUnload: function () {

  },

  /**
   * Page related event handler -- listen to user drop-down actions
   */
  onPullDownRefresh: function () {

  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function () {

  },

  /**
   * Users click the upper right corner to share
   */
  onShareAppMessage: function () {

  }
})
<!--pages/cart/index.wxml-->
<!-- Receiving address container -->
<view class="receive_address">
  <!-- Receiving address button -->
  <!-- wx:if Judge whether the user name in the address exists,Show when not present -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain Indicates whether the button is hollow and the background color is transparent -->
    <!-- Bind the click event to obtain the receiving address -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">Add receiving address</button>
  </view>
  <!-- When the receiving address user name exists, that is, there is a receiving address -->
  <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>

<!-- Shopping cart details -->
<view class="cart_content">
  <view class="cart_title">Shopping Cart</view>
  <view class="cart_main">
    <view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">
      <!-- Check box structure -->
      <view class="cart_checkbox">
        <checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
          <checkbox checked="{{item.checked}}"></checkbox>
        </checkbox-group>
      </view>
      <!-- Product picture -->
      <navigator class="cart_image">
        <image mode="widthFix"
          src="{{item.goods_small_logo}}">
        </image>
      </navigator>
      <!-- Commodity information -->
      <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>

<!-- Bottom navigation bar -->
<view class="footer_tool">
  <!-- Select all -->
  <view class="all_check_wrap">
    <checkbox-group bindchange="handleItemAllChecked">
      <checkbox checked="{{allChecked}}">Select all</checkbox>
    </checkbox-group>
  </view>
  <!-- Total price -->
  <view class="total_price_wrap">
    <view class="total_price">
    total:<text class="total_price_number">¥{{totalPrice}}</text>
    </view>
    <view>Including freight</view>
  </view>
  <!-- settlement -->
  <view class="order_pay_wrap">settlement({{totalNum}})</view>
</view>

The effect is as follows

Effect when shopping cart page is empty and settlement button

Check the array length in the cart. If it is 0, it means there are no goods. Otherwise, it is judged that there are goods. You can do this directly by using the block tag in index.wxml.

<!--pages/cart/index.wxml-->
<!-- Receiving address container -->
<view class="receive_address">
  <!-- Receiving address button -->
  <!-- wx:if Judge whether the user name in the address exists,Show when not present -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain Indicates whether the button is hollow and the background color is transparent -->
    <!-- Bind the click event to obtain the receiving address -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">Add receiving address</button>
  </view>
  <!-- When the receiving address user name exists, that is, there is a receiving address -->
  <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>

<!-- Shopping cart details -->
<view class="cart_content">
  <view class="cart_title">Shopping Cart</view>
  <view class="cart_main">
    <!-- When cart When the length of the array is not 0, the product information is displayed -->
    <block wx:if="{{cart.length!==0}}">
      <view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">
        <!-- Check box structure -->
        <view class="cart_checkbox">
          <checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
            <checkbox checked="{{item.checked}}"></checkbox>
          </checkbox-group>
        </view>
        <!-- Product picture -->
        <navigator class="cart_image">
          <image mode="widthFix" src="{{item.goods_small_logo}}">
          </image>
        </navigator>
        <!-- Commodity information -->
        <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>
    <!-- When goods cart When the length of the array in is 0, it indicates that there are no products -->
    <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>


<!-- Bottom navigation bar -->
<view class="footer_tool">
  <!-- Select all -->
  <view class="all_check_wrap">
    <checkbox-group bindchange="handleItemAllChecked">
      <checkbox checked="{{allChecked}}">Select all</checkbox>
    </checkbox-group>
  </view>
  <!-- Total price -->
  <view class="total_price_wrap">
    <view class="total_price">
      total:<text class="total_price_number">¥{{totalPrice}}</text>
    </view>
    <view>Including freight</view>
  </view>
  <!-- settlement -->
  <view class="order_pay_wrap">settlement({{totalNum}})</view>
</view>

The effect is as follows

The premise of the settlement function is that there is a receiving address and goods exist. Then jump to the payment page. wx.showToast is used
And wx.navigateTo's wechat built-in API.

// pages/cart/index.js
Page({
  /**
   * Encapsulate a function to recalculate the bottom toolbar, total price and total quantity while setting the commodity status of the shopping cart 
   */
  setCart(cart) {
    let allChecked = true;
    //  Declare total price and total quantity
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      } else {
        allChecked = false;
      }
    })
    //Determine whether the array is empty
    allChecked = cart.length != 0 ? allChecked : false;
    this.setData({
      cart,
      totalPrice,
      totalNum,
      allChecked
    });
    wx.setStorageSync('cart', cart);
  },
  // Click to get receiving address event
  // 1. Bind click event
  // 2. Call the API in the applet, "Wx. Chooseaddress", to obtain the receiving address of the user
  // Shopping cart page loaded
  // 1. Obtain the receiving address data in the local storage
  // 2. If there is storage, set the data to a variable in data
  handleChooseAddress(e) {
    wx.chooseAddress({
      success: (result) => {
        // Store the obtained address into the local cache, and splice the address before storage
        result.all = result.provinceName + result.cityName + result.countyName + result.detailInfo;
        console.log(result);
        wx.setStorageSync('address', result)
      },
    });
  },

  // Selected optimization event for a product
  handleItemChange(e) {
    //Get the modified product id
    const goods_id = e.currentTarget.dataset.id;
    // Get shopping cart array
    let {
      cart
    } = this.data;
    //Find the item object to be modified in the shopping cart array
    let index = cart.findIndex(v => v.goods_id === goods_id);
    // Modify the selected status of the commodity object and invert it
    cart[index].checked = !cart[index].checked;
    // Update the data to the data and cache, and recalculate the total price and total quantity by calling the encapsulated function setCart
    this.setCart(cart);
  },
  //Select all of products
  handleItemAllChecked(e) {
    // 1 get the data in data
    let {
      cart,
      allChecked
    } = this.data;
    // 2 modify value
    allChecked = !allChecked;
    // 3 cycle to modify the selected status of goods in the cart array
    cart.forEach(v => v.checked = allChecked);
    // 4 fill the modified value back into the data or cache, and call the encapsulated function
    this.setCart(cart);
  },
  //Commodity quantity addition and subtraction function
  handleItemNumEdit(e) {

    // 1 get the passed parameters
    const {
      operation,
      id
    } = e.currentTarget.dataset;
    // 2 get shopping cart array
    let {
      cart
    } = this.data;
    //3 find the index of the item to be modified
    const index = cart.findIndex(v => v.goods_id === id);
    //Determine whether to delete when the commodity quantity is less than 1
    if (cart[index].num === 1 && operation === -1) {
      wx.showModal({
        title: 'Tips',
        content: 'Do you want to delete this item?',
        success: (res) => {
          if (res.confirm) {
            cart.splice(index, 1);
            this.setCart(cart);
          } else if (res.cancel) {
            console.log('The user clicks cancel');
          }
        }
      })
    } else {
      // 4. Quantity to be modified
      cart[index].num += operation;
      // 5 set back to cache and data
      this.setCart(cart);
    }
  },

  // Settlement function of commodity page
  handlePay(e) {
    //1. Judge the receiving address and whether there is commodity quantity
    const {
      address,
      totalNum
    } = this.data;
    if (!address.userName) {
      wx.showToast({
        title: 'Please add shipping address',
        icon: 'error',
        duration: 2000,
      })
      return;
    }
    if (totalNum===0) {
      wx.showToast({
        title: 'Please add product',
        icon: 'error',
        duration: 2000
      })
      return;
    }
    // Jump to wechat payment page
    wx.navigateTo({
      url: '/pages/pay/index',
    })
  },
  /**
   * Initial data of the page
   */
  data: {
    address: {},
    //Declare a variable, shopping cart array
    cart: [],
    // Declare a variable, whether to select all the contents of the shopping cart
    allChecked: false,
    // Define total price and total quantity of goods
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {

  },

  /**
   * Life cycle function -- monitor the completion of the first rendering of the page
   */
  onReady: function () {

  },

  /**
   * Life cycle function -- monitor page display
   */
  onShow: function () {
    //Get shopping cart data in cache
    const cart = wx.getStorageSync('cart')
    // Calculate whether to select all shopping cart items
    // The every array method will traverse and receive callback functions, so each callback function will return true, and the return value of the every method will be true
    // As long as a callback function returns false, it will not execute circularly and return false directly
    // If it is an empty array, then every returns true, so a ternary expression is used
    const allChecked = cart.length ? cart.every(v => v.checked) : false;
    //Get the receiving address information in the cache
    const address = wx.getStorageSync('address');
    //  Declare total price and total quantity
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      }
    })
    //Assign value to data
    this.setData({
      address,
      cart: cart,
      allChecked,
      totalPrice,
      totalNum
    })
  },

  /**
   * Life cycle function -- listening for page hiding
   */
  onHide: function () {

  },

  /**
   * Life cycle function -- listen for page unloading
   */
  onUnload: function () {

  },

  /**
   * Page related event handler -- listen to user drop-down actions
   */
  onPullDownRefresh: function () {

  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function () {

  },

  /**
   * Users click the upper right corner to share
   */
  onShareAppMessage: function () {

  }
})
<!--pages/cart/index.wxml-->
<!-- Receiving address container -->
<view class="receive_address">
  <!-- Receiving address button -->
  <!-- wx:if Judge whether the user name in the address exists,Show when not present -->
  <view class="address" wx:if="{{!address.userName}}">
    <!-- plain Indicates whether the button is hollow and the background color is transparent -->
    <!-- Bind the click event to obtain the receiving address -->
    <button type="primary" plain="true" bindtap="handleChooseAddress">Add receiving address</button>
  </view>
  <!-- When the receiving address user name exists, that is, there is a receiving address -->
  <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>

<!-- Shopping cart details -->
<view class="cart_content">
  <view class="cart_title">Shopping Cart</view>
  <view class="cart_main">
    <!-- When cart When the length of the array is not 0, the product information is displayed -->
    <block wx:if="{{cart.length!==0}}">
      <view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">
        <!-- Check box structure -->
        <view class="cart_checkbox">
          <checkbox-group data-id="{{item.goods_id}}" bindchange="handleItemChange">
            <checkbox checked="{{item.checked}}"></checkbox>
          </checkbox-group>
        </view>
        <!-- Product picture -->
        <navigator class="cart_image">
          <image mode="widthFix" src="{{item.goods_small_logo}}">
          </image>
        </navigator>
        <!-- Commodity information -->
        <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>
    <!-- When goods cart When the length of the array in is 0, it indicates that there are no products -->
    <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>


<!-- Bottom navigation bar -->
<view class="footer_tool">
  <!-- Select all -->
  <view class="all_check_wrap">
    <checkbox-group bindchange="handleItemAllChecked">
      <checkbox checked="{{allChecked}}">Select all</checkbox>
    </checkbox-group>
  </view>
  <!-- Total price -->
  <view class="total_price_wrap">
    <view class="total_price">
      total:<text class="total_price_number">¥{{totalPrice}}</text>
    </view>
    <view>Including freight</view>
  </view>
  <!-- settlement -->
  <view class="order_pay_wrap" bindtap="handlePay">settlement({{totalNum}})</view>
</view>

The final effect is as follows
**
**

Payment page

The pay folder corresponds to the payment page. First, modify the name of the page in index.json, as shown below.

{
  "usingComponents": {},
  "navigationBarTitleText": "payment"
}

Then the page changes


The overall style of the payment page is similar to that of the shopping cart page, so you can copy the code of the previous shopping cart page in large sections. Then make minor modifications.

<!--pages/pay/index.wxml-->
<!-- Receiving address container -->
<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>

<!-- Shopping cart details -->
<view class="cart_content">
  <view class="cart_title">Shopping Cart</view>
  <view class="cart_main">
      <view class="cart_item" wx:for="{{cart}}" wx:key="goods_id">
        <!-- Product picture -->
        <navigator class="cart_image">
          <image mode="widthFix" src="{{item.goods_small_logo}}">
          </image>
        </navigator>
        <!-- Commodity information -->
        <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>


<!-- Bottom navigation bar -->
<view class="footer_tool">
  <!-- Total price -->
  <view class="total_price_wrap">
    <view class="total_price">
      total:<text class="total_price_number">¥{{totalPrice}}</text>
    </view>
    <view>Including freight</view>
  </view>
  <!-- payment -->
  <view class="order_pay_wrap">payment({{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;
    /* Align text right */
    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({
  /**
   * Encapsulate a function to recalculate the bottom toolbar, total price and total quantity while setting the commodity status 
   */
  setCart(cart) {
    let allChecked = true;
    //  Declare total price and total quantity
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      } else {
        allChecked = false;
      }
    })
    //Determine whether the array is empty
    allChecked = cart.length != 0 ? allChecked : false;
    this.setData({
      cart,
      totalPrice,
      totalNum,
    });
    wx.setStorageSync('cart', cart);
  },
  
  /**
   * Initial data of the page
   */
  data: {
    address: {},
    //Declare a variable, shopping cart array
    cart: [],
    // Declare a variable, whether to select all the contents of the shopping cart
    allChecked: false,
    // Define total price and total quantity of goods
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {

  },

  /**
   * Life cycle function -- monitor the completion of the first rendering of the page
   */
  onReady: function () {

  },

  /**
   * Life cycle function -- monitor page display
   */
  onShow: function () {
    //Get shopping cart data in cache
    const cart = wx.getStorageSync('cart')
    // Calculate whether to select all shopping cart items
    // The every array method will traverse and receive callback functions, so each callback function will return true, and the return value of the every method will be true
    // As long as a callback function returns false, it will not execute circularly and return false directly
    // If it is an empty array, then every returns true, so a ternary expression is used
    const allChecked = cart.length ? cart.every(v => v.checked) : false;
    //Get the receiving address information in the cache
    const address = wx.getStorageSync('address');
    //  Declare total price and total quantity
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      }
    })
    //Assign value to data
    this.setData({
      address,
      cart: cart,
      allChecked,
      totalPrice,
      totalNum
    })
  },

  /**
   * Life cycle function -- listening for page hiding
   */
  onHide: function () {

  },

  /**
   * Life cycle function -- listen for page unloading
   */
  onUnload: function () {

  },

  /**
   * Page related event handler -- listen to user drop-down actions
   */
  onPullDownRefresh: function () {

  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function () {

  },

  /**
   * Users click the upper right corner to share
   */
  onShareAppMessage: function () {

  }
})

The final modified payment page style is as follows


Next is some related payment logic. For the goods on this page, we must ensure that they have been selected on the shopping cart page, that is, the checkbox is selected as true. Therefore, you need to filter in index.js to filter what really needs to be paid.

// pages/pay/index.js
Page({
  /**
   * Encapsulate a function to recalculate the bottom toolbar, total price and total quantity while setting the commodity status 
   */
  setCart(cart) {
    let allChecked = true;
    //  Declare total price and total quantity
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      } else {
        allChecked = false;
      }
    })
    //Determine whether the array is empty
    allChecked = cart.length != 0 ? allChecked : false;
    this.setData({
      cart,
      totalPrice,
      totalNum,
    });
    wx.setStorageSync('cart', cart);
  },

  /**
   * Initial data of the page
   */
  data: {
    address: {},
    //Declare a variable, shopping cart array
    cart: [],
    // Declare a variable, whether to select all the contents of the shopping cart
    allChecked: false,
    // Define total price and total quantity of goods
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {

  },

  /**
   * Life cycle function -- monitor the completion of the first rendering of the page
   */
  onReady: function () {

  },

  /**
   * Life cycle function -- monitor page display
   */
  onShow: function () {
    //Get shopping cart data in cache
    let cart = wx.getStorageSync('cart')
    //Get the receiving address information in the cache
    const address = wx.getStorageSync('address');
    //Filtered shopping cart array (really selected items)
    cart = cart.filter(v => v.checked);
     //  Declare total price and total quantity
     let totalPrice = 0;
     let totalNum = 0;
     cart.forEach(v => {
         totalPrice += v.num * v.goods_price;
         totalNum += v.num;
     })
     this.setData({
       cart,
       totalPrice,
       totalNum, address,
     });
  },

  /**
   * Life cycle function -- listening for page hiding
   */
  onHide: function () {

  },

  /**
   * Life cycle function -- listen for page unloading
   */
  onUnload: function () {

  },

  /**
   * Page related event handler -- listen to user drop-down actions
   */
  onPullDownRefresh: function () {

  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function () {

  },

  /**
   * Users click the upper right corner to share
   */
  onShareAppMessage: function () {

  }
})

You can see that there are three items in the shopping cart, and only two items are settled. After jumping to the payment page, there are really only two items.


Now let's click the payment button to make wechat payment. Who or account can wechat payment be realized? At present, only the applet developed by the enterprise account can be implemented, and the developer must be added to the white list in the background of the applet of the enterprise account. For specific details, you need to view the applet development document( Wechat payment).
The overall process is as follows

In fact, the application process is as follows. When clicking the payment button, first judge whether there is a token value in the cache when the user logs in. If not, jump to the authorization page to obtain the token. If there is a token value, go to the step of creating an order. At this time, the index.js code of the payment page is as follows. Because there is no token value, click payment and jump to the auth page for authorization.

// pages/pay/index.js
Page({
  /**
   * Encapsulate a function to recalculate the bottom toolbar, total price and total quantity while setting the commodity status 
   */
  setCart(cart) {
    let allChecked = true;
    //  Declare total price and total quantity
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      if (v.checked) {
        totalPrice += v.num * v.goods_price;
        totalNum += v.num;
      } else {
        allChecked = false;
      }
    })
    //Determine whether the array is empty
    allChecked = cart.length != 0 ? allChecked : false;
    this.setData({
      cart,
      totalPrice,
      totalNum,
    });
    wx.setStorageSync('cart', cart);
  },

  /**
   * Initial data of the page
   */
  data: {
    address: {},
    //Declare a variable, shopping cart array
    cart: [],
    // Declare a variable, whether to select all the contents of the shopping cart
    allChecked: false,
    // Define total price and total quantity of goods
    totalPrice: 0,
    totalNum: 0
  },

  /**
   * Life cycle function -- listening for page loading
   */
  onLoad: function (options) {

  },

  /**
   * Life cycle function -- monitor page display
   */
  onShow: function () {
    //Get shopping cart data in cache
    let cart = wx.getStorageSync('cart')
    //Get the receiving address information in the cache
    const address = wx.getStorageSync('address');
    //Filtered shopping cart array (really selected items)
    cart = cart.filter(v => v.checked);
    //  Declare total price and total quantity
    let totalPrice = 0;
    let totalNum = 0;
    cart.forEach(v => {
      totalPrice += v.num * v.goods_price;
      totalNum += v.num;
    })
    this.setData({
      cart,
      totalPrice,
      totalNum,
      address,
    });
  },
  // Click payment event
  handleOrderPay(e) {
    //Judge whether there is a token in the cache
    const token = wx.getStorageSync('token');
    //If it doesn't exist
    if (!token) {
      //Jump to the page to get user login information
      wx.navigateTo({
        url: '/pages/auth/index',
      })
      return;
    }
    //Otherwise, there is a token value
    console.log('Already token Yes');
  },

  /**
   * Life cycle function -- listening for page hiding
   */
  onHide: function () {

  },

  /**
   * Life cycle function -- listen for page unloading
   */
  onUnload: function () {

  },

  /**
   * Page related event handler -- listen to user drop-down actions
   */
  onPullDownRefresh: function () {

  },

  /**
   * Handler for bottom pull event on page
   */
  onReachBottom: function () {

  },

  /**
   * Users click the upper right corner to share
   */
  onShareAppMessage: function () {

  }
})

In the auth folder, first modify the page name in index.json.

{
  "usingComponents": {},
  "navigationBarTitleText": "to grant authorization"
}

Then add a button in the index.wxml code under the folder, and click to authorize.

<!--pages/auth/index.wxml-->
<!-- Obtain user authorization and bind click events -->
<button type="primary" plain="true" open-type="getUserInfo" bindgetuserinfo="handleGetUserInfo">
  Get authorization
</button>

The index.wxss style is as follows

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

Then write the relevant logic code in index.js. Note that we have customized a token value here because we are not an enterprise wechat.

// pages/auth/index.js
// Introduce encapsulated request function
import {
  request
} from "../../request/index.js"
Page({
  //Get user information
  async handleGetUserInfo(e) {
    // Print the event obtained after clicking e
    // console.log(e);
    // Get the following properties in event e.
    const {
      encryptedData,
      rawData,
      iv,
      signature
    } = e.detail;
    //Get the code value after the applet is successfully logged in
    wx.login({
      timeout: 10000,
      success: (result) => {
        // Check the code value
        // 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
    };
    //Send a request to get the user's token value
    const token = await request({
      url: "https://api-hmugo-web.itheima.net/api/public/v1/users/wxlogin",
      data: this.loginParams,
      method: "post"
    });
    //Store the token in the cache and jump back to the previous page (here we use a custom token because we are not an enterprise wechat account)
    wx.setStorageSync('token', 'BearereyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjIzLCJpYXQiOjE1NjQ3MzAwNzksImV4cCI6MTAwMTU2NDczMDA3OH0.YPt-XeLnjV-_1ITaXGY2FhxmCe4NvXuRnRB8OMCfnPo');
    // Back to previous page
    wx.navigateBack({
      delta: 1
    })
    
  }
})

After running, click the authorization button, and the data in the cache is as follows:

After clicking the authorization once, click the payment button on the payment page, and the following effects will appear

Posted by Kalland on Fri, 22 Oct 2021 06:58:02 -0700