一、购物车
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.优化-关键按钮添加防抖函数