1.mobx-miniprogram
1.1 mobx-miniprogram 介绍
1.数据绑定: properties
2.获取组件实例: this.selectComponent()
3.事件绑定: this.triggerEvent()
4.获取应用实例: getApp()
5.页面间通信: EventChannel
6.事件总线: pubsub-js
-
目的与功能:
-
解决复杂项目中组件间数据同步的问题,提供状态管理功能。
-
使得在多个页面间需要同步使用的状态变得易于管理,当状态改变时,所有关联组件都会自动更新数据。
-
-
核心概念:
-
observable:用于创建一个被监测的对象,对象的属性即应用的状态(state),这些状态会被转换成响应式数据。
-
-
使用方式:
-
安装:需要安装两个包,即mobx-miniprogram和mobx-miniprogram-bindings。前者用于创建Store对象(存储应用的数据),后者用于将状态和组件、页面进行绑定关联。
-
创建Store对象:使用mobx-miniprogram的observable等概念和方法来创建Store对象,用于存储和管理应用的状态。
-
1.2创建 store 对象
1.安装依赖
首先,需要在的小程序项目中安装 mobx-miniprogram
。
2. 创建 Store 文件
在你的小程序项目中创建一个新的 JavaScript 文件,例如 store.js
,用于定义你的 Store
。
3. 导入 MobX 相关模块
在 store.js
文件中,你需要从 mobx-miniprogram
中导入 observable
、action
等模块。
// store.js
import { observable, action } from 'mobx-miniprogram';
4.定义store
使用 observable
函数来定义你的状态,并使用 action
来定义修改状态的方法。
// store.js
const store = observable({
// 数据字段
numA: 1,
numB: 2,
// actions
update: action(function() {
const sum = this.numA + this.numB;
this.numA = this.numB;
this.numB = sum;
}),
// 还可以定义其他计算属性或方法...
});
// 导出 store,以便在其他文件中使用
export default store;
5.在页面或组件中使用store
在小程序页面或组件中,你需要通过 getApp()
方法获取全局的 App
实例,并从中获取 Store
。然后,你可以直接在页面的 data
中使用 Store
的状态,或者在方法中使用 Store
的 action
。
示例(在页面中使用):
// page/index/index.js
Page({
data: {
// 初始化时从 Store 获取数据
numA: 0,
numB: 0,
sum: 0,
},
onLoad: function () {
// 获取全局的 App 实例
const app = getApp();
// 假设你在 App 实例中保存了 Store
const store = app.globalData.store;
// 初始化页面数据
this.setData({
numA: store.numA,
numB: store.numB,
sum: store.numA + store.numB, // 或者直接使用 store.sum 如果已定义
});
// 监听 Store 的变化(可选)
// 注意:小程序并没有像 React 那样的细粒度更新机制,所以需要自己实现
// ...
},
// 在方法中使用 Store 的 action
updateNums: function() {
const app = getApp();
const store = app.globalData.store;
store.update();
// 由于小程序的响应机制,你可能需要手动更新页面数据
this.setData({
numA: store.numA,
numB: store.numB,
sum: store.numA + store.numB, // 或者直接使用 store.sum 如果已定义
});
},
// ...
});
1.3在组件中使用 Store 数据
1.1在组件的 data
中使用 Store 数据
// components/MyComponent/MyComponent.js
Component({
properties: {},
data: {
// 初始化时从 Store 获取数据
numA: 0,
numB: 0,
sum: 0,
},
lifetimes: {
attached() {
// 在组件加载时从 Store 获取数据
const app = getApp();
const store = app.globalData.store;
this.setData({
numA: store.numA,
numB: store.numB,
sum: store.sum,
});
},
},
methods: {
// 假设你有一个方法来更新 Store 中的数据
updateStoreData() {
const app = getApp();
const store = app.globalData.store;
store.update(); // 调用 Store 中的 action
// 由于小程序的响应机制,你可能需要手动更新页面数据
this.setData({
numA: store.numA,
numB: store.numB,
sum: store.sum,
});
},
},
});
1.4在页面中使用 Store 数据-方式
1.在页面中使用store
// pages/index/index.js
Page({
data: {
// 初始时不需要在 data 中声明 Store 中的数据
// 因为我们会通过计算属性或方法来获取
},
onLoad: function () {
// 获取 Store 实例
const app = getApp();
const store = app.globalData.store;
// 你可以在页面加载时直接设置 data 中的值
this.setData({
count: store.count,
});
// 或者,你可以使用计算属性来简化这个过程(但小程序本身不支持计算属性)
// 监听 Store 中的变化(可选,但通常不需要,因为你会在更新 Store 后手动更新页面)
// ...
},
// 假设你有一个按钮点击事件来更新 Store 中的数据
incrementCount: function() {
const app = getApp();
const store = app.globalData.store;
store.increment(); // 调用 Store 中的 action
// 由于小程序的响应机制,你需要手动更新页面数据
this.setData({
count: store.count,
});
},
// ... 其他代码
});
2.在页面WXML中使用数据
在页面的 WXML 文件中,你可以直接使用 data
中的数据来渲染页面:
<!-- pages/index/index.wxml -->
<view>{{count}}</view>
<button bindtap="incrementCount">Increment</button>
2.用户管理
1.1用户登录-实现小程序登录功能
思路分析:
当用户没有登录的时候,需要点击个人中心的头像,跳转到登录页面进行登录。在登录成功以后,需要再次返回到个人中心页面
在登录页面我们使用了 Vant 提供的两个组件来进行页面结构的绘制
1.empty 组件:空状态时的占位提示
2.button 组件:按钮组件
给登录按钮绑定点击事件,在事件处理程序中,调用wx.login 获取 临时登录凭证code
然后调用后端接口,将 临时登录凭证code 传递给后端
Page({
onGetUserInfo: function(e) {
if (e.detail.userInfo) {
// 用户已授权,可以在这里获取到用户信息
const userInfo = e.detail.userInfo;
// 你可以将用户信息发送到服务器进行保存
// ...
// 接下来调用 wx.login 获取 code
this.getLoginCode();
} else {
// 用户未授权
}
},
getLoginCode: function() {
wx.login({
success: function(res) {
if (res.code) {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
// ...
} else {
console.log('登录失败!' + res.errMsg);
}
}
});
}
});
1.2用户信息-使用数据渲染用户信息
思路分析:
在获取到数据以后,我们已经将用户信息数据存储到本地和store
这一节我们需要从Store 中取出用户信息数据,并渲染到页面上
个人中心页面展示用于展示个人信息
如果用户没有登录的时候,展示没有登录的头像、提示用户登录的文案信息,不展示设置按钮如果用户已经登录,展示用户的头像和昵称,并且展示设置按钮,方便用户对收货地址、头像、昵称进行更改
<view>
<image src="{{userInfo.avatarUrl}}" mode="aspectFit" style="width: 100px; height: 100px;"></image>
<text>{{userInfo.nickName}}</text>
</view>
1.3更新用户信息-渲染用户信息
思路分析:
1.点击个人中心的设置,然后点击修改个人资料,就可以对用户的头像和昵称进行修改
2.在这个页面中,我们需要先渲染信息用户,用户信息目前是存储到 Store 中的,因此我们需要先从 Store中取出用户信息的数据,进行渲染的渲染。
3.让页面和 Store 数据建立关联,可以使用 mobx-miniprogram-bindings 提供的 Behaviorwithstore 方法
<view>
<image src="{{userInfo.avatarUrl}}" mode="aspectFit" style="width: 100px; height: 100px;"></image>
<text>{{userInfo.nickName}}</text>
</view>
<!-- 假设还有一个按钮用于触发获取用户信息的操作 -->
<button open-type="getUserInfo" bindgetuserinfo="getUserInfo">获取用户信息</button>
3.收货地址
1.1定义新增参数以及封装接口 API
收货地址
1.收货地址列表
2.新增收货地址
3.编辑收货地址
4.删除收货地址
思路分析:
点击新建地址按钮,需要跳转到新增地址页面
因为新增和编辑收货地址页面是同一个页面,我们需要在这个页面处理新增和编辑功能,为了做区分处理。
我们在后续做进行编辑的时候传递 id 属性,值为收货地址的 id 值。
封装API接口
// api.js
function saveAddress(address, successCallback, failCallback) {
wx.request({
url: '你的服务器地址/saveAddress', // 替换为你的服务器地址
method: 'POST',
data: {
address: JSON.stringify(address)
},
success: function(res) {
if (typeof successCallback === 'function') {
successCallback(res.data);
}
},
fail: function(err) {
if (typeof failCallback === 'function') {
failCallback(err);
}
}
});
}
// 在其他页面中引入并使用
const api = require('./api.js');
// ...
saveAddress(this.data.address, function(data) {
// 处理成功的情况
}, function(err) {
// 处理失败的情况
});
// ...
1.2收集省市区数据
思路分析
省市区的结构使用了小程序本身自带的 picker 件,并将组件的 mode 属性设置为了 region ,从而变成省市区选择器如果想获取省市区的数据,需要给 picker 选择组件添加 change 事件来监听属性值的改变,获取选中的省市区
wx.request({
url: '第三方API的URL', // 替换为实际的API地址
success: (res) => {
// 假设API返回的数据在res.data中
const provinces = res.data.provinces; // 省份列表
// 你可以在这里对获取到的数据进行处理,并保存到小程序的data中
this.setData({
provinces: provinces,
selectedProvince: provinces[0], // 默认选中第一个省份
cities: [], // 初始时城市列表为空
selectedCity: null, // 初始时未选中城市
districts: [], // 初始时区县列表为空
selectedDistrict: null, // 初始时未选中区县
});
},
fail: (err) => {
// 处理请求失败的情况
console.error(err);
}
});
1.3 收集新增地址其他请求参数
思路分析:
使用简易双向数据 model:value绑定来收集新增地址表单数据
在将数据收集以后,需要组织两个数据
1.是否是默认地址,0不设置为默认地址,1设置为默认地址
2.拼接完整的收货地址
Page({
// ...
saveAddress: function() {
// 你可以在这里添加验证逻辑,确保数据有效
// 收集地址数据
const addressData = this.data.address;
// 发送请求到服务器保存地址数据
wx.request({
url: '你的服务器地址/saveAddress', // 替换为你的服务器地址
method: 'POST',
data: {
// 转换为服务器需要的格式,如 JSON
address: JSON.stringify(addressData)
},
success: function(res) {
// 处理服务器返回的响应
console.log(res.data);
// 可以根据需要更新页面状态或显示提示信息
},
fail: function(err) {
// 处理请求失败的情况
console.error(err);
// 显示错误信息给用户
}
});
},
// ...
});
1.4async-validator 基本使用
知识点:
async-validator 是一个基于 JavaScript 的表单验证库,支持异步验证规则和自定义验证规则主流的 UI 组件库 Ant-design 和 Element 中的表单验证都是基于 async-validator使用 async-validator 可以方便地构建表单验证逻辑,使得错误提示信息更加友好和灵活
使用步骤:
1.安装并在项目中导入 async-validator
2.创建验证规则
3.创建表单验证实例,将验证规则传递给构造函数,产生实例
4.调用实例方法 validate 对数据进行验证
第一个参数:需要验证的数据
第二个参数:回调函数,回调函数有两个参数errors,fields
"errors:如果验证成功,返回 null,验证错误,返回数组
"fields:需要验证的字段,属性值错误数组
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
],
password: [
{ required: true, validator: checkPassword, trigger: 'blur' }
]
};
// 自定义异步验证函数
function checkPassword(rule, value, callback) {
// 模拟异步验证
setTimeout(() => {
if (value === '123456') {
callback(new Error('密码不能太简单, 不可以是123456'));
} else {
callback();
}
}, 1000);
}
<view class="container address-list" bindtap="onSwipeCellPage">
<view class="list-warpper" wx:if="{{ addressList.length }}">
<view wx:for="{{ addressList }}" wx:key="id" class="list-item">
<van-swipe-cell
id="swipe-cell-{{ item.id }}"
bind:open="swipeCellOpen"
bind:click="onSwipeCellClick"
right-width="{{ 65 }}"
>
<van-cell-group border="{{ false }}">
<view class="list-item-box">
<view class="info" bindtap="changeAddress" data-id="{{ item.id }}">
<view class="user-info">
<text>{{ item.name }}</text>
<text>{{ item.phone }}</text>
<text wx:if="{{ item.isDefault === 1 }}" class="default-tag">默认</text>
</view>
<view class="address-info"> {{ item.fullAddress }} </view>
</view>
<view class="editBtn" bindtap="toEdit" data-id="{{ item.id }}">
<van-icon name="edit" size="22px" color="#999" />
</view>
</view>
</van-cell-group>
<view
slot="right"
class="van-swipe-cell__right"
bindtap="delAddress"
data-id="{{ item.id }}"
>
<text>删除</text>
</view>
</van-swipe-cell>
</view>
</view>
<van-empty wx:else description="还没有收货地址,快去添加吧~" />
<view class="footer">
<view class="btn">
<navigator url="/modules/settingModule/pages/address/add/index">
新增地址
</navigator>
</view>
</view>
</view>
4.商品管理
1.1配置商品管理分包
思路分析:
随着项目功能的增加,项目体积也随着增大,从而影响小程序的加载速度,影响用户的体验因此我们需要将 商品列表 和 商品详情 功能配置成一个分包,当用户在访问设置页面时,还预先加载商品列表 和 商品详情 所在的分包
// utils/api/product.js
const baseURL = 'https://your-api-domain.com/api'; // 你的API基础地址
// 获取商品列表
function getProductList(params = {}) {
return wx.request({
url: `${baseURL}/products`, // 假设这是你的商品列表API地址
method: 'GET',
data: params,
success: (res) => {
if (res.data && res.data.code === 200) {
return res.data.data; // 返回商品列表数据
} else {
// 处理错误情况
throw new Error(res.data.message || '获取商品列表失败');
}
},
fail: (err) => {
// 处理网络错误等情况
throw new Error('网络请求失败: ' + err.message);
}
});
}
// ... 其他商品相关API函数
// 导出API函数
module.exports = {
getProductList,
// ... 其他API函数
};
1.2商品列表-获取商品列表数据并渲染
思路分析:
在准备商品列表的请求参数以后
在页面调用 API 函数获取商品列表的数据,在获取到数据以后,使用后端返回的数据对页面进行渲染。
实现步骤:
1.在 /pages/goods/list/list.js 中导入封装好的获取商品列表的 API 函数
2.页面数据在页面加载的时候进行调用,在onLoad 钩子函数中调用reqGoodsList 方法3.在获取到数据以后,使用后端返回的数据对页面进行渲染
Page({
data: {
productList: []
},
onLoad: function() {
this.getProductList();
},
getProductList: function() {
wx.request({
url: '你的服务器地址/api/products', // 替换为你的 API 地址
method: 'GET',
success: (res) => {
// 假设服务器返回的数据结构为 { code: 200, data: [商品列表] }
if (res.data.code === 200) {
this.setData({
productList: res.data.data
});
} else {
// 处理错误情况
wx.showToast({
title: '获取商品列表失败',
icon: 'none'
});
}
},
fail: (err) => {
// 请求失败处理
wx.showToast({
title: '网络错误',
icon: 'none'
});
}
});
},
// ... 其他代码
});
1.3 商品详情-获取并渲染商品详情
思路分析:
点击首页轮播图以及点击商品列表商品的时候,需要跳转到商品详情页面在跳转时将商品的 id 传递到了商品详情页面,只需要使用 id 向后端服务器请求数据,获取对应商品的详情数据在获取到数据以后,使用后端返回的数据对页面进行染。
实现步骤:
1.在 /pages/goods/detail/detail.js 中导入封装好的获取商品列表的 API 函数
2.页面数据在页面加载的时候进行调用,在 onLoad 钩子函数中调用regGoodsInfo 方法3.在获取到数据以后,使用后端返回的数据对页面进行渲染
Page({
data: {
productDetail: {} // 商品详情数据
},
onLoad: function(options) {
// 假设从 URL 参数中获取商品 ID
const productId = options.id;
this.getProductDetail(productId);
},
getProductDetail: function(productId) {
wx.request({
url: '你的服务器地址/api/products/' + productId, // 替换为你的 API 地址
method: 'GET',
success: (res) => {
// 假设服务器返回的数据结构为 { code: 200, data: 商品详情 }
if (res.data.code === 200) {
this.setData({
productDetail: res.data.data
});
} else {
// 处理错误情况
wx.showToast({
title: '获取商品详情失败',
icon: 'none'
});
}
},
fail: (err) => {
// 请求失败处理
wx.showToast({
title: '网络错误',
icon: 'none'
});
}
});
}
// ... 其他代码
});
1.4配置 @ 路径别名优化访问路径
在对小程序进行分包时,如果访问小程序根目录下的文件,那么访问的路径就会很长在 vue 中,可以使用 @ 符号指向源码目录,简化路径,小程序也给提供了配置的方式。
在小程序中可以在 app.json 中使用resolveAlias配置项用来自定义模块路径的映射规则。
许多现代的编辑器或 IDE(如 VSCode、WebStorm 等)都提供了路径自动补全的功能。你可以在你的编辑器或 IDE 中配置你的项目结构,以便它能够理解你的文件和目录的层次结构。然后,当你开始输入一个文件或目录的路径时,编辑器或 IDE 会自动为你提供补全选项。虽然这不是真正的路径别名,但它可以显著减少你手动输入路径的次数,从而提高你的开发效率。
5.购物车
1.1购物车-封装购物车接口 API
思路分析:
为了方便后续进行购物车模块的开发,我们在这一节将购物车所有的接口封装成接口 API 函数
// cartApi.js
const request = require('./request');
// 假设 API 接口如下
const baseUrl = 'https://your-api-domain.com/api';
const addToCartUrl = `${baseUrl}/cart/add`;
const getCartListUrl = `${baseUrl}/cart/list`;
const removeFromCartUrl = `${baseUrl}/cart/remove`;
// 添加商品到购物车
function addToCart(productId, quantity) {
return request(addToCartUrl, 'POST', { productId, quantity });
}
// 获取购物车商品列表
function getCartList() {
return request(getCartListUrl, 'GET');
}
// 从购物车移除商品
function removeFromCart(cartItemId) {
return request(removeFromCartUrl, 'POST', { cartItemId });
}
// 导出 API 函数
module.exports = {
addToCart,
getCartList,
removeFromCart
};
1.2加入购物车-模板分析和渲染
业务介绍:
点击加入购物车和立即购买的时候,展示购物弹框,在弹框中需要用户选择购买数量和祝福语
点击加入购物车和立即购买,触发的是同一个弹框。
因此点击弹框中的确定按钮时,我们需要区分当前是加入购物车操作还是立即购买操作
这时候定义一个状态 buyNow做区分, buyNoW等于 1 代表是立即购买,否则是加入购物车
产品需求
如果点击的是加入购物车,需要将当前商品加入到购物车1
2.如果点击的是立即购买,需要跳转到结算支付页面,立即购买该商品
3.如果是立即购买,不支持购买多个商品
Page({
data: {
product: {}, // 从其他地方(如页面跳转携带的参数)获取到的商品信息
cartList: [], // 假设这里只是一个示例,实际上你可能需要从 app.globalData 或其他地方获取
},
// 假设这是从页面跳转时带过来的商品数据
onLoad: function (options) {
this.setData({
product: {
id: options.id, // 商品ID
name: options.name, // 商品名称
price: options.price, // 商品价格
// ...其他商品信息
},
});
},
// 加入购物车方法
addToCart: function (e) {
const product = e.currentTarget.dataset.product; // 获取到商品数据
// 假设 cartList 是从 app.globalData 获取的,这里只是示例,你可能需要修改这部分代码
let cartList = this.data.cartList;
let inCart = false; // 标记商品是否已经在购物车中
// 检查商品是否已经在购物车中
for (let i = 0; i < cartList.length; i++) {
if (cartList[i].id === product.id) {
// 如果商品已经在购物车中,可以增加数量(这里只是示例,没有实现)
inCart = true;
break;
}
}
if (!inCart) {
// 如果商品不在购物车中,则添加到购物车
cartList.push(product);
this.setData({
cartList: cartList,
});
// 这里可以添加调用后端API的代码,用于同步购物车数据到服务器
// ...
wx.showToast({
title: '加入购物车成功',
icon: 'success',
duration: 2000
});
} else {
// 如果商品已经在购物车中,可以提示用户或进行其他操作
wx.showToast({
title: '商品已在购物车中',
icon: 'none',
duration: 2000
});
}
},
// ...其他方法和事件处理函数
});
1.3购物车-购物车关联 store 对象
思路分析:
当用户进入购物车页面时时,需要判断用户是否进行了登录来控制页面的展示效果这时候我们就需要使用Token 进行判断,因此需要让页面和store 对象建立关联。
因为购物车页面采用的Component方法进行构建这时候可以使用 Componentwithstore让页面和store对象建立关联。
// pages/cart/cart.js
Page({
data: {
cartList: [], // 初始为空,后面将从全局的 store 中获取
},
onLoad: function () {
const app = getApp();
this.setData({
cartList: app.globalData.store.cartList, // 从全局的 store 中获取购物车数据
});
},
// 其他方法...
});
1.4购物车-更新商品购买数量防抖
思路分析:
每次改变购物车购买数量的时候,都会触发 changeBuyNum 事件处理程序,这会频繁的向后端发送请求,给服务器造成压力我们希望用户在输入最终的购买数量,或者停止频繁点击加、减的以后在发送请求,在将购买数量同步到服务器。
这时候就需要使用 防抖 来进行代码优化。
JavaScript 工具库,该库目前拥有超过 400 个模块,同时支持浏览器、node 及小程序运行环境。可以极大Licia 是实用地提高开发效率。
Page({
// ... 其他数据和方法 ...
// 假设这是更新商品数量的原始方法
updateProductQuantity: function(productId, delta) {
// 从全局的 store 或其他地方获取购物车数据
const cartList = getApp().globalData.store.cartList;
const productIndex = cartList.findIndex(item => item.id === productId);
if (productIndex !== -1) {
const product = cartList[productIndex];
product.quantity += delta; // delta 可以是正数或负数,表示增加或减少的数量
// 更新全局的 store
getApp().globalData.store.cartList = cartList;
// 可能还有其他操作,比如发送网络请求到服务器
// ...
// 刷新当前页面的购物车数据(如果页面没有重新加载的话)
this.setData({
cartList: cartList,
});
}
},
// 使用防抖函数来包裹更新商品数量的方法
debouncedUpdateProductQuantity: debounce(function(productId, delta) {
this.updateProductQuantity(productId, delta);
}, 500), // 等待 500 毫秒再执行
// 在按钮的点击事件中使用防抖函数
handleQuantityChange: function(e) {
const productId = e.currentTarget.dataset.productId; // 假设这是从按钮的 data-set 中获取的
const delta = e.currentTarget.dataset.delta || 1; // 根据按钮的不同,可能是增加或减少
this.debouncedUpdateProductQuantity(productId, delta);
},
// ... 其他数据和方法 ...
});
1.5购物车-购物车商品合计
思路分析:
在订单提交栏位置,展示要购买商品的总金额。
需要判断购物车中哪些商品被勾选,然后将勾选商品的价格进行累加。
当用户更新了商品的状态,或者更新了商品的购买数量,我们都需要重新计算订单总金额。
我们需要基于购物车列表的数据,产生订单总金额,在这里我们使用依然使用 computed 来实现商品合计的功能
实现步骤:
在 computed 配置项,新增totalPrice 函数用来计算商品价格总和
<!-- 购物车列表结构 -->
<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 }}"
id="swipe-cell-{{ item.goodsId }}"
bind:open="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"
bindtap="delCartGoods"
data-id="{{ item.goodsId }}"
>删除</view
>
</van-swipe-cell>
</view>
</view>
<!-- 购物车列表为空展示的结构 -->
<van-empty wx:else description="{{ emptyDes }}">
<navigator
url="/pages/index/index"
open-type="switchTab"
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>
6.商品结算
// 导入接口 API 函数
import {
reqCartList,
reqUpdateChecked,
reqCheckAllStatus,
reqAddCart,
reqDelCartGoods
} from '@/api/cart'
// 导入 debounce 防抖方法
import { debounce } from 'miniprogram-licia'
// 导入让删除滑块自动弹回的 behavior
import { swipeCellBehavior } from '@/behaviors/swipeCell'
// 导入 miniprogram-computed 提供的 behavior
const computedBehavior = require('miniprogram-computed').behavior
ComponentWithStore({
// 注册 behavior
behaviors: [swipeCellBehavior, 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)
)
},
// 计算订单总金额
totalPrice(data) {
// 用来对订单总金额进行累加
let totalPrice = 0
data.cartList.forEach((item) => {
// 需要判断商品是否是选中的状态,isChecked 是否等于 1
if (item.isChecked === 1) {
totalPrice += item.price * item.count
}
})
return totalPrice
}
},
// 组件的初始数据
data: {
cartList: [],
emptyDes: '还没有添加商品,快去添加吧~'
},
// 组件的方法列表
methods: {
// 跳转到订单结算页面
toOrder() {
// 判断用户是否勾选了商品,如果没有勾选商品,不进行跳转
if (this.data.totalPrice === 0) {
wx.toast({
title: '请选择需要购买的商品'
})
return
}
wx.navigateTo({
url: '/modules/orderPayModule/pages/order/detail/detail'
})
},
// 更新购买的数量
changeBuyNum: debounce(async function (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
})
}
}, 500),
// 实现全选和全不选效果
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
})
}
},
// 更新商品的购买状态
async updateChecked(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
})
}
},
// 展示文案同时获取购物车列表数据
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 && '还没有添加商品,快去添加吧~'
})
}
},
// 删除购物车中的商品
async delCartGoods(event) {
// 获取需要删除商品的 id
const { id } = event.currentTarget.dataset
// 询问用户是否删除该商品
const modalRes = await wx.modal({
content: '您确认删除该商品吗 ?'
})
if (modalRes) {
await reqDelCartGoods(id)
this.showTipGetList()
}
},
// 如果使用 Component 方法来构建页面
// 生命周期钩子函数需要写到 methods 中才可以
onShow() {
this.showTipGetList()
},
onHide() {
// 在页面隐藏的时候,需要让删除滑块自动弹回
this.onSwipeCellCommonClick()
}
}
})
7.小程序支付
// 获取预付单信息、支付参数
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/order/list/list',
success: () => {
wx.toast({
title: '支付成功',
icon: 'success'
})
}
})
}
}
}
} catch (error) {
wx.toast({
title: '支付失败',
icon: 'error'
})
}
8.订单列表
// 导入封装的接口 API 函数
import { reqOrderList } from '../../../api/orderpay'
Page({
// 页面的初始数据
data: {
orderList: [], // 订单列表
page: 1, // 页码
limit: 10, // 每页展示的条数
total: 0, // 订单列表总条数
isLoading: false // 判断数据是否记载完毕
},
// 获取订单列表
async getOrderList() {
// 解构获取数据
const { page, limit } = this.data
// 数据正在请求中
this.data.isLoading = true
// 调用接口获取订单列表数据
const res = await reqOrderList(page, limit)
// 数据加载完毕
this.data.isLoading = false
if (res.code === 200) {
this.setData({
orderList: [...this.data.orderList, ...res.data.records],
total: res.data.total
})
}
},
// 页面上拉触底事件的处理函数
onReachBottom() {
// 解构数据
const { page, total, orderList, isLoading } = this.data
// 判断是否加载完毕,如果 isLoading 等于 true
// 说明数据还没有加载完毕,不加载下一页数据
if (isLoading) return
// 数据总条数 和 订单列表长度进行对比
if (total === orderList.length) {
wx.toast({ title: '数据加载完毕' })
return
}
// 更新 page
this.setData({
page: page + 1
})
// 重新发送请求
this.getOrderList()
},
// 生命周期函数--监听页面加载
onLoad() {
this.getOrderList()
}
})