小程序175-200

一、购物车

1.购物车关联 Store 对象

javascript 复制代码
// pages/cart/component/cart.js
import { ComponentWithStore } from 'mobx-miniprogram-bindings'
import { userStore } from '@/store/userstore'

ComponentWithStore({
  // 让页面和 Store 对象建立关联
  storeBindings: {
    store: userStore,
    fields: ['token']
  },

  // 组件的初始数据
  data: {
    cartList: [1, 2, 3, 4],
    emptyDes: '还没有添加商品,快去添加吧~'
  },

  // 组件的方法列表
  methods: {

    // 如果使用 Component 方法来构建也页面
    // 生命周期钩子函数需要写到 methods 中才可以
    onShow () {
      console.log(this.data.token)
    }
  }
})

2.获取并渲染购物车列表

html 复制代码
  <!-- 购物车列表结构 -->
  <view
    wx:if="{{ token && cartList.length }}"
    class="container goods-wrap"
    bindtap="onSwipeCellPageTap"
  >
    <view class="goods-item" wx:for="{{ cartList }}" wx:key="goodsId">
      <van-swipe-cell class="goods-swipe" right-width="{{ 65 }}">
        <van-cell-group border="{{ false }}">
          <view class="goods-info">
            <view class="left">
              <van-checkbox checked-color="#FA4126" value="{{ item.isChecked }}"></van-checkbox>
            </view>
            <view class="mid">
              <image class="img" src="{{ item.imageUrl }}" />
            </view>
            <view class="right">
              <view class="title">
                {{ item.name }}
              </view>
              <view class="buy">
                <view class="price">
                  <view class="symbol">¥</view>
                  <view class="num">{{ item.price }}</view>
                </view>
                <view class="buy-btn">
                  <van-stepper value="{{ item.count }}" />
                </view>
              </view>
            </view>
          </view>
        </van-cell-group>
        <view slot="right" class="van-swipe-cell__right">删除</view>
      </van-swipe-cell>
    </view>
  </view>

  <!-- 购物车列表为空展示的结构 -->
  <van-empty wx:else description="{{ emptyDes }}">
    <navigator url="/pages/index/index" wx:if="{{ token && cartList.length === 0 }}">
      <van-button round type="danger" class="bottom-button">去购物</van-button>
    </navigator>

    <navigator url="/pages/login/login" wx:else>
      <van-button round type="danger" class="bottom-button">去登录</van-button>
    </navigator>
  </van-empty>
javascript 复制代码
  // 组件的方法列表
  methods: {
    // 展示文案同时获取购物车列表数据
    async showTipGetList () {

      // 解构数据
      const { token } = this.data

      // 判断用户是否进行了登录
      if (!token) {
        this.setData({
          emptyDes: '您尚未登录,点击登录获取更多权益',
          cartList: []
        })

        return
      }

      // 如果用户进行了登录,就需要获取购物车列表数据
      const { code, data: cartList } = await reqCartList()
      if (code === 200) {
        this.setData({
          cartList,
          emptyDes: cartList.length === 0 && '还没有添加商品,快去添加吧~'
        })
      }
    },

    // 如果使用 Component 方法来构建也页面
    // 生命周期钩子函数需要写到 methods 中才可以
    onShow () {
      this.showTipGetList()
    }
  }
})

3.更新商品的购买状态

javascript 复制代码
    // 更新商品的购买状态
    async updateChecked (event) {
      console.log(event)
      // 获取最新的购买状态
      const { detail } = event
      // 获取传递的 商品 ID 以及 索引
      const { id, index } = event.target.dataset
      // 将最新的购买状态转换成后端接口需要使用的 0 和 1
      const isChecked =detail ? 1 : 0

      const res = await reqUpdateChecked(id, isChecked)
      
      if (res.code === 200) {
        // 服务器更新购买状态成功以后,获取最新的购物车列表数据更新状态
        // this.showTipGetList()
        this.setData({
          [`cartList[${index}].isChecked`]: isChecked
        })
      }
    },

4.控制全选按钮的选中状态

javascript 复制代码
// 导入 miniprogram-computed 提供的 behavior 
const computedBehavior = require('miniprogram-computed').behavior

ComponentWithStore({
  // 注册 behavior
  behaviors: [computedBehavior],

  // 让页面和 Store 对象建立关联
  storeBindings: {
    store: userStore,
    fields: ['token']
  },

  // 定义计算属性
  computed: {
    // 判断是否是全选,控制全选按钮的选中效果
    // 计算属性会被挂载到 data 对象中
    selectAllStatus (data) {
      // computed 函数不能使用 this 来访问 data 中的数据
      // 如果想访问 data 中的数据,需要使用形参
      return (
      data.cartList.length !== 0 && data.cartList.every(item => item.isChecked === 1)
      )
    }
  },
})

5.实现全选和全不选功能

javascript 复制代码
// 实现全选和全不选效果
    async selectAllStatus (event) {
      // 获取全选按钮的选中状态
      const { detail } = event
      // 需要将选中的状态转换成接口需要使用的数据
      const isChecked = detail ? 1 : 0
      // 调用接口,实现全选和全不选功能
      const res = await reqCheckAllStatus(isChecked)
      if (res.code === 200) {
        // this.showTipGetList()
        // 购物车列表数据进行深拷贝
        const newCartList = JSON.parse(JSON.stringify(this.data.cartList))
        newCartList.forEach(item => item.isChecked = isChecked)

        // 对 cartList 进行赋值,驱动视图更新
        this.setData({
          cartList: newCartList
        })
      }
    },

6.更新商品购买数量思路分析

7.更新商品的购买数量

html 复制代码
                <view class="buy-btn">
                  <van-stepper 
                    min="1" 
                    max="200" 
                    integer 
                    value="{{ item.count }} "
                    data-id="{{ item.goodsId }}"
                    data-index="{{ index }}"
                    data-oldbuynum="{{ item.count }}"
                    bindchange="changeBuyNum" 
                  />
                </view>
javascript 复制代码
// 更新购买的数量
    async changeBuyNum (event) {
      // 获取最新的购买数量
      // 如果用户输入的购买数量大于 200,需要把购买数量设置为 200
      // 最大购买数量是 200,目前购买数量是 1,假设用户输入了 666,666 - 1 = 665,665 + 1 = 666
      // 最大购买数量是 200,如果用户输入的购买数量是 666,重置为 200,200 - 1 = 199,199 + 1 = 200
      const newBuyNum = event.detail > 200 ? 200 : event.detail

      // 获取商品的 id、索引、之前的购买数量
      const { id, index, oldbuynum } = event.target.dataset

      // 使用正则验证用户输入的购买数量,是否是 1-200 之间的正整数
      const reg = /^([1-9]|[1-9]\d|1\d{2}|200)$/

      // 对用户输入的值进行验证,验证通过 true,验证失败 false
      const regRes = reg.test(newBuyNum)

      // 如果验证没有通过,说明用户输入的购买数量不合法或者小于 1,需要还原为之前的购买数量
      if (!regRes) {
        this.setData({
          [`cartList[${index}].count`]: oldbuynum
        })

        // 如果验证没有通过,需要阻止代码继续往下运行
        return
      }

      // 如果验证通过,就需要计算差值,然后把差值发送给公司的服务器,让服务器进行逻辑处理
      const disCount = newBuyNum - oldbuynum

      // 判断购买数量是否发送了改变,如果购买数量没有发送改变,不发送请求
      if (disCount === 0) return

      // 如果购买发送了改变,需要调用接口,传递差值
      const res = await reqAddCart({ goodsId: id, count: disCount })
      
      // 如果服务器更新购买数量成功,需要更新本地的购买数量
      if (res.code === 200) {
        this.setData({
          [`cartList[${index}].count`]: newBuyNum,
          // 如果购买数量发生了变化,需要让当前商品变成选中的状态
          [`cartList[${index}].isChecked`]: 1,
        })
      }
    },

8.更新商品购买数量防抖

命令:npm i miniprogram-licia --save

javascript 复制代码
// 导入 debounce 防抖方法
import { debounce } from 'miniprogram-licia'


changeBuyNum: debounce(function, 500)

9.购物车商品合计

javascript 复制代码
  // 定义计算属性
  computed: {
    // 计算订单总金额
    totalPrice (data) {
      // 用来对订单总金额进行累加
      let totalPrice = 0

      data.cartList.forEach(item => {
        // 需要判断商品是否是选中的状态,isChecked 是否等于 1
        if (item.isChecked === 1) {
          totalPrice += item.price * item.count
        }
      })

      return totalPrice
    }
  },
html 复制代码
  <!-- 底部工具栏 -->
  <!-- 底部工具栏组件展示价格,默认是以 分 的形式进行展示,如果需要以 元 的方式进行展示 -->
  <van-submit-bar
    wx:if="{{ cartList.length }}"
    price="{{ totalPrice * 100 }}"
    button-text="去结算"
    tip="{{ true }}"
  >
    <van-checkbox 
      value="{{ selectAllStatus }}" 
      checked-color="#FA4126" 
      bindchange="selectAllStatus"
    > 
      全选 
    </van-checkbox>
  </van-submit-bar>

10.删除购物车中的商品

javascript 复制代码
    // 删除购物车中的商品
    async delCartGoods (event) {
      // 获取需要删除商品的 id
      const { id } = event.currentTarget.dataset

      // 询问用户是否删除该商品
       const modalRes = await wx.showModal({
        content: '您确定删除该商品吗?'
      })

      if (modalRes) {
        await reqDelCartGoods(id)

        this.showTipGetList()
      }
    },


    onHide () {
      // 在页面隐藏的时候,需要让删除滑块自动弹回
      this.onSwipeCellCommonClick()
    }
html 复制代码
      <van-swipe-cell 
        class="goods-swipe" 
        right-width="{{ 65 }}" 
        id="swipe-cell-{{ item.goodsId }}" 
        bind:tap="swipeCellOpen" 
        bind:click="onSwipeCellClick"
      >
        <van-cell-group border="{{ false }}">
          <view class="goods-info">
            <view class="left">
              <van-checkbox 
                checked-color="#FA4126" 
                value="{{ item.isChecked }}"
                bindchange="updateChecked"
                data-id="{{ item.goodsId }}"
                data-index="{{ index }}"
              ></van-checkbox>
            </view>
            <view class="mid">
              <image class="img" src="{{ item.imageUrl }}" />
            </view>
            <view class="right">
              <view class="title">
                {{ item.name }}
              </view>
              <view class="buy">
                <view class="price">
                  <view class="symbol">¥</view>
                  <view class="num">{{ item.price }}</view>
                </view>
                <view class="buy-btn">
                  <van-stepper 
                    min="1" 
                    max="200" 
                    integer 
                    value="{{ item.count }} "
                    data-id="{{ item.goodsId }}"
                    data-index="{{ index }}"
                    data-oldbuynum="{{ item.count }}"
                    bindchange="changeBuyNum" 
                  />
                </view>
              </view>
            </view>
          </view>
        </van-cell-group>
        <view 
          slot="right" 
          class="van-swipe-cell__right" 
          bind:tap="delCartGoods" 
          data-id="{{ item.goodsId }}"
        >删除</view>
      </van-swipe-cell>

二、结算支付

1.配置分包并跳转到结算页面

javascript 复制代码
    // 跳转到订单结算页面
    toOrder ()  {
      // 判断用户是否勾选了商品,如果没有勾选商品,不进行跳转
      if (this,this.data.totalPrice === 0) {
        wx.hideToast({
          title: '请选择需要购买的商品'
        })
        return
      }

      wx.navigateTo({
        url: '/modules/orderPayModule/pages/order/detail/detail'
      })
    },

2.封装结束支付的接口 API

javascript 复制代码
import http from '@/utils/http'

/**
 * @description 获取订单
 * @returns Promise
 */
export const reqOrderInfo = () => {
  return http.get('/order/trade')
}

/**
 * @description 获取订单详情页面的收货地址
 * @returns Promise
 */
export const reqOrderAddress = () => {
  return http.get('/userAddress/getOrderAddress')
}
/**
 * @description 获取立即购买商品的详细信息
 * @param { Object } param { goodsId: 商品Id, blessing: 祝福语 }
 * @returns Promise
 */

export const reqBuyNowGoods= ({ goodsId, ...data }) => {
  return http.get(`/order/buy/${goodsId}`)
}
/**
 * @description 提交订单、进行下单
 * @returns Promise
 */

export const reqSumbitOrder = () => {
  return http.post('/order/submitOrder')
}
/**
 * @description 获取微信支付预支付信息
 * @param {*} orderNo 订单 ID
 * @returns Promise
 */
export const reqPrePayInfo = (orderNo) => {
  return http.get(`/wxbChat/creatrJsapi/${orderNo}`)
}

/**
 * @description 微信支付支付的结果,查询微信支付的状态
 * @param {*} orderNo 
 * @returns Promise
 */
export const reqPayStatus = (orderNo) => {
  return http.get(`/webChat/queryPayStatus/${orderNo}`)
}

3.商品结算

(1)获取收货地址

html 复制代码
 <view class="address-card">
    <!-- 添加收货地址 -->
    <view wx:if="{{ !orderAddress.id }}" class="add-address"  bindtap="toAddress">
      <van-icon size="22px" name="add" />
      <view>添加收货地址</view>
    </view>

    <!-- 渲染收货地址 -->
    <view wx:else class="order-address detail-flex">
      <view class="address-content">
        <view class="title">{{ orderAddress.fullAddress }}</view>
        <view class="info detail-flex">
          <text>{{ orderAddress.name }}</text>
          <text>{{ orderAddress.phone }}</text>
        </view>
      </view>

      <view class="select-address">
        <navigator
          class="navigator"
          url=""
        >
          <van-icon color="#bbb" name="arrow" size="22px" />
        </navigator>
      </view>
    </view>

    <view class="top-line"></view>
  </view>
javascript 复制代码
  // 获取订单页面的收货地址
  async getAddress () {
    const { data:orderAddress } = await reqOrderAddress()

    this.setData({
      orderAddress
    })
  },

  // 在页面展示的时候进行触发
  onShow () {
    // 获取收货地址
    this.getAddress()
  }

(2)更新收货地址功能

javascript 复制代码
  // globalData 是指全局共享的数据
  // 点击收货地址时,需要将点击的收货地址赋值给 address
  // 在结算支付、订单结算页面,需要判断 address 是否存在数据
  // 如果存在数据,就展示 address 数据,如果没有数据,就从接口获取数据进行渲染
  globalData: {
    address: {}
  },
javascript 复制代码
  // 更新收货地址
  changeAddress (event) {
    // 需要判断是否是从结算支付页面进入的收货地址列表页面
    // 如果是,才能获取点击的收货地址,否则,不执行后续的逻辑,不执行切换收货地址的逻辑
    if (this.flag !== '1') return

    // 如果是从结算支付页面进入的收货地址列表页面,需要获取点击的收货地址详细信息
    const addressId = event.currentTarget.dataset.id
    
    // 需要从收货地址列表中根据 收货地址 ID 查找到点击的收货地址详情、详细信息
    const selectAddress = this.data.addressList.find(item => item.id === addressId)

    if (selectAddress) {
      // 如果获取收货地址成功以后,需要赋值给全局共享的数据
      app.globalData.address = selectAddress

      wx.navigateBack()
    }
  },
javascript 复制代码
  // 获取订单页面的收货地址
  async getAddress () {

    // 判断全局共享的 address 中是否存在数据,
    // 如果存在数据,就需要从全局共享的 address 中取到数据进行赋值
    const addressId = app.globalData.address.id

    if (addressId) {
      this.setData({
        orderAddress: app.globalData.address
      })

      return
    }

    // 如果全局共享的 address 中没有数据,就需要调用接口获取收货地址数据进行渲染
    const { data:orderAddress } = await reqOrderAddress()

    this.setData({
      orderAddress
    })
  },

  // 在页面展示的时候进行触发
  onShow () {
    // 获取收货地址
    this.getAddress()
  },

  onUnload () {
    // 在页面销毁以后需要将全局共享的 address 也进行重置
    // 如果用户再次进入结算支付页面,需要从接口地址获取默认的收货地址进行渲染
    // 需要和产品多沟通
    app.globalData.address = {}
  }
})

(3)获取订单详情数据

javascript 复制代码
  // 获取订单详情的数据
  async getOrderInfo () {
    const { data: orderInfo } = await reqOrderInfo()

    // 判断是否存在祝福语
    // 如果需要购买的是多个商品,筛选第一个存在祝福语的商品进行赋值
    const orderGoods = orderInfo.cartVoList.find(item => item.blessing !== '')
    
    this.setData({
      orderInfo,
      blessing: orderGoods && orderGoods.blessing
    })
  },

  // 在页面展示的时候进行触发
  onShow () {
    // 获取收货地址
    this.getAddress()

    // 获取需要下单商品的详细信息
    this.getOrderInfo()
  },

(4)获取立即购买数据

javascript 复制代码
  // 获取订单详情的数据
  async getOrderInfo () {

    const { goodsId, blessing } = this.data

    const { data: orderInfo } = goodsId
      ? await reqBuyNowGoods({ goodsId, blessing }) 
      : await reqOrderInfo()

    // 判断是否存在祝福语
    // 如果需要购买的是多个商品,筛选第一个存在祝福语的商品进行赋值
    const orderGoods = orderInfo.cartVoList.find(item => item.blessing !== '')
    
    this.setData({
      orderInfo,
      blessing: orderGoods ? '' : orderGoods.blessing
    })
  },

(5)收集送达时间

javascript 复制代码
  // 期望送达日期确定按钮
  onConfirmTimerPicker(event) {
    // 使用 Vant 提供的时间选择组件,获取的时间是时间戳
    // 需要将时间戳转换为年月日在页面中进行展示
    // 可以调用小程序给提供的日期格式化方法对时间进行转换
    console.log(event.detail)

    // formatTime 方法接收 JS 的日期对象作为参数
    // 因此需要将时间戳转换成 JS 的日期对象
    const timeRes = formatTime(new Date(event.detail))
    

    this.setData({
      show: false,
      deliveryDate: timeRes
    })
  },

(6)收集送达时间

javascript 复制代码
  // 处理提交订单
  async submitOrder () {
    // 需要从 data 中解构数据
    const {
      buyName,
      buyPhone,
      deliveryDate,
      blessing,
      orderAddress,
      orderInfo
    } = this.data
    // 需要根据接口要求组织请求参数

    const params = {
      buyName,
      buyPhone,
      cartList: orderInfo, cartList,
      deliveryDate,
      remarks: blessing,
      userAddressId: orderAddress.id 
    }

    // 对请求参数进行验证
    const { valid } = await this.validatorPerson(params)
    console.log(valid)
  },

    // 对收货人、订购人信息进行验证
    validatorPerson (params) {

      // 验证订购人,是否只包含大小写字母、数字和中文字符
      const nameRegExp = '^[a-zA-Z\\d\\u4e00-\\u9fa5]+$'
        
      // 验证订购人手机号,是否符合中国大陆手机号码的格式
      const phoneReg = '^1(?:3\\d|4[4-9]|5[0-35-9]|6[67]|7[0-8]|8\\d|9\\d)\\d{8}$'
  
      // 创建验证规则
      const rules = {
        userAddressId: {
          required: true, message: '请输入收货地址'
        },
        buyName: [
          { required: true, message: '请输入订购人姓名' },
          { pattern: nameRegExp, message: '订购人姓名不合法' }
        ],
        buyPhone: [
          { required: true, message: '请输入订购人手机号' },
          { pattern: phoneReg, message: '订购人手机号不合法' }
        ],
        deliveryDate: { required: true, message: '请选择送达日期' },
      }
  
      // 传入验证规则进行实例化
      const validator = new Schema(rules)
  
      // 调用实例方法对请求参数进行验证
      // 注意:我们希望将验证结果通过 Promise 的形式返回给函数的调用者
      return new Promise((resolve) => {
        validator.validate(params, (errors) => {
          if (errors) {
            // 如果验证失败,需要给用户进行提示
            wx.hideToast({ title: errors[0].message })
            // 如果属性值是 false,说明验证失败
            resolve({ valid: false })
          } else {
            // 如果属性值是 true,说明验证成功
            resolve({ valid: true })
          }
        })
      })
    },

4.小程序支付

(1)小程序支付流程

(2)创建平台订单

javascript 复制代码
  // 处理提交订单
  async submitOrder () {
    // 对请求参数进行验证
    const { valid } = await this.validatorPerson(params)
    
    // 如果请求参数验证失败,直接 return,不执行后续的逻辑
    if (!valid) return

    // 调用接口,创建平台订单
    const res = await reqSubmitOrder(params)

    if (res.code === 200) {
      // 在平台订单创建成功以后,需要将服务器、后端返回的订单编号挂载到页面实例上
      this.orderNo = res.data
    }
  },

(3)获取预付单信息

javascript 复制代码
 async submitOrder () {
      if (res.code === 200) {
      // 在平台订单创建成功以后,需要将服务器、后端返回的订单编号挂载到页面实例上
      this.orderNo = res.data

      // 获取预付单信息、支付参数
      this.advancePay()
    }
  },

    // 获取预付单信息、支付参数
    async advancePay () {
      const payParams = await reqPrePayInfo(this.orderNo)

      if (payParams.code === 200) {
        console.log(payParams.data)
      }
    },

(4)发起微信支付

javascript 复制代码
    // 获取预付单信息、支付参数
    async advancePay () {
      try {
        const payParams = await reqPrePayInfo(this.orderNo)

        if (payParams.code === 200) {
          // payParams.data 就是获取的支付参数

          // 调用 wx.requestPayment 发起微信支付
          const payInfo = await wx.requestPayment(payParams.data)
        }
      } catch (error) {
        wx.hideToast({
          title: '支付失败,请联系客服',
          icon: 'error'
        })
      }
    },

(5)支付状态查询

javascript 复制代码
    // 获取预付单信息、支付参数
    async advancePay () {
      try {
        const payParams = await reqPrePayInfo(this.orderNo)

        if (payParams.code === 200) {
          // payParams.data 就是获取的支付参数

          // 调用 wx.requestPayment 发起微信支付
          const payInfo = await wx.requestPayment(payParams.data)

          // 获取支付的结果
          if (payInfo.errMsg === 'requestPayment: ok') {
            // 查询支付的状态
            const payStatus = await reqPayStatus(this.orderNo)
            
            if (payStatus.code === 200) {
              wx.redirectTo({
                url: "/modules/orderPayModule/pages/goods/list/list",
                success: () => {
                  wx.hideToast({
                    title: '支付成功',
                    icon: 'success'
                  })
                }
              })
            }
          }
        }
      } catch (error) {
        wx.hideToast({
          title: '支付失败',
          icon: 'error'
        })
      }
    },

三、订单列表

四、代码优化

1.分享功能

javascript 复制代码
  // 转发功能,转发给好友、群聊
  onShareAppMessage () {
    return {
      title: '我一定会回来的!',
      path: '/pages/index/index',
      imageUrl: '../../../../../assets/images/love.jpg'
    }
  },

  // 能够把小程序分享到朋友圈
  onShareTimeline () {}

2.优化-分包调整

3.优化-关键按钮添加防抖函数

相关推荐
卸任21 小时前
Vibe coding工具使用心得
前端·ai编程
brien921 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance1 天前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M1 天前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写1 天前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李1 天前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易1 天前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
清晨Coding1 天前
删了 Cookie 还能自动登录?Google系网站登录态自动恢复的原理分析
javascript
风骏时光牛马1 天前
前沿视界
前端
kyriewen1 天前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员