小程序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.优化-关键按钮添加防抖函数

相关推荐
懂懂tty1 小时前
Vue3 编译优化
前端·javascript·vue.js
低保和光头哪个先来1 小时前
源码篇 生命周期
前端·javascript·vue.js
持敬chijing1 小时前
Web渗透之SQL注入-盲注(布尔盲注,时间盲注)
前端·sql·oracle
AI_零食1 小时前
鸿蒙PC Electron框架天天饮水应用深度解析:健康饮水管理系统
javascript·华为·信息可视化·electron·开源·鸿蒙
河北清兮网络科技1 小时前
2026石家庄广告联盟APP开发成本明细|不同开发模式费用拆解
大数据·小程序·app·短剧app·广告联盟
NGINX开源社区1 小时前
NGINX Ingress Controller 中的 Cache Policy:VirtualServer 实战指南
java·前端·nginx
办公自动化软件定制化开发python2 小时前
开源!Edge TTS 音频转换工具 v2.1:批量文本转语音,支持段落拆分与多发音人
前端·edge·音视频
27669582922 小时前
jd 变速滑块逆向角度分析
前端·python·京东滑块·京东逆向·京东变速滑块·cfe滑块·wasm逆向
ct9782 小时前
Vue 项目性能优化
前端·vue.js·性能优化