商品管理
01. 配置商品管理分包
思路分析:
随着项目功能的增加,项目体积也随着增大,从而影响小程序的加载速度,影响用户的体验。
因此我们需要将 商品列表
和 商品详情
功能配置成一个分包,
当用户在访问设置页面时,还预先加载 商品列表
和 商品详情
所在的分包
在分包后,通过查看代码依赖查看是否分包完成
📌 注意事项:
- 在配置好
商品列表
和商品详情
的分包以后,需要更改页面中的跳转路径- PS:可以利用项目全局搜索的功能,进行批量更改
实现步骤:
- 在
modules
目录下创建goodModule
文件夹,用来存放商品管理分包 - 在
app.json
的subpackages
进行商品管理分包配置 - 在
app.json
的preloadRule
进行商品管理分包配置
落地代码:
➡️ app.json
json
{
"subPackages": [
{
"root": "modules/settingModule",
"name": "settingModule",
"pages": [
"pages/address/add/index",
"pages/address/list/index",
"pages/profile/profile"
]
},
+ {
+ "root": "modules/goodModule",
+ "name": "goodModule",
+ "pages": ["pages/goods/list/list", "pages/goods/detail/detail"]
}
],
"preloadRule": {
"pages/settings/settings": {
"network": "all",
"packages": ["settingModule"]
},
+ "pages/category/category": {
+ "network": "all",
+ "packages": ["goodModule"]
+ }
}
}
02. 封装商品模块接口 API
思路分析:
为了方便后续进行商品管理模块的开发,我们在这一节将商品管理所有的接口封装成接口 API 函数
落地代码:
➡️ api/goods.js
js
import http from '../utils/http'
/**
* @description 获取商品列表
* @return Promise
*/
export const reqGoodsList = ({ limit, page, ...reset }) => {
return http.get(`/mall-api/goods/list/${page}/${limit}`, reset)
}
/**
* @description 获取商品详情
* @param {*} goodsId 商品Id
* @returns Promise
*/
export const reqGoodsInfo = (goodsId) => {
return http.get(`/mall-api/goods/${goodsId}`)
}
03. 商品列表-准备列表请求参数
思路分析:
当用户点击了商品分类以后,需要获取对应分类的商品列表信息,因此我们需要先获取到该分类的 id
,只要获取到 id
以后,才能向服务器获取对应分类的商品列表信息。同时我们需要查看接口文档,查看是否需要使用其他参数,我们提前将参数准备好。
首先熟悉接口文档:获取商品分页列表
参数名称 | 参数说明 | 是否必须 |
---|---|---|
limit | 每页记录数 | true |
page | 当前页码 | true |
category1Id | 一级分类的 Id (从首页导航分类区域点击进入) | false |
category2Id | 二级分类的 Id (从分类页面点击进入二级分类进入) | false |
通过接口文档得知,我们需要以上的参数,我们先将参数提前声明,然后在发起请求获取商品列表的数据
实现步骤:
- 在商品列表的
data
字段中,根据接口文档,定义商品列表接口需要使用的字段 - 在商品列表的
onLoad
钩子函数中接收请求的参数,并将请求参数进行合并
落地代码:
➡️ /modules/goodsModule/pages/list/list.js
js
Page({
// 页面的初始数据
data: {
goodsList: [], // 商品列表数据
isFinish: false, // 判断数据是否加载完毕
+ // 接口请求参数
+ requestData: {
+ page: 1, // 页码
+ limit: 10, // 每页请求多少条数据
+ category1Id: '', // 一级分类 id
+ category2Id: '' // 二级分类 id
+ }
},
+ // 生命周期函数--监听页面加载
+ onLoad(options) {
+ // 接收传递的参数
+ Object.assign(this.data.requestData, options)
+ }
})
04. 商品列表-获取商品列表数据并渲染
思路分析:
在准备商品列表的请求参数以后,
在页面调用 API
函数获取商品列表的数据,在获取到数据以后,使用后端返回的数据对页面进行渲染。
实现步骤:
- 在
/pages/goods/list/list.js
中导入封装好的获取商品列表的API
函数 - 页面数据在页面加载的时候进行调用,在
onLoad
钩子函数中调用reqGoodsList
方法 - 在获取到数据以后,使用后端返回的数据对页面进行渲染
落地代码:
➡️ /modules/goodsModules/pages/list/list.js
js
+ import { reqGoodsList } from '../../../../../api/good'
Page({
// 页面的初始数据
data: {
goodsList: [], // 商品列表数据
+ total: 0, // 数据总条数
isFinish: false, // 判断数据是否加载完毕
// 接口请求参数
requestData: {
page: 1, // 页码
limit: 10, // 每页请求多少条数据
category1Id: '', // 一级分类 id
category2Id: '' // 二级分类 id
}
},
+ // 获取商品列表的数据
+ async getGoodsList() {
+ // 调用 API 获取数据
+ const { data } = await reqGoodsList(this.data.requestData)
+
+ // 将返回的数据赋值给 data 中的变量
+ this.setData({
+ goodsList: data.records,
+ total: data.total
+ })
+ },
// 生命周期函数--监听页面加载
onLoad(options) {
// 接收传递的参数
Object.assign(this.data.requestData, options)
+ // 获取商品列表的数据
+ this.getGoodsList()
}
})
➡️ /modules/goodsModule/pages/list/list.wxml
html
<view class="container">
<!-- 商品列表功能 -->
+ <view class="goods-list" wx:if="{{ goodsList.length }}">
+ <block wx:for="{{ goodsList }}" wx:key="id">
+ <goods-card goodItem="{{ item }}"></goods-card>
+ </block>
<!-- 数据是否加载完毕 -->
<view class="finish" hidden="{{ !isFinish }}">数据加载完毕~~~</view>
</view>
+ <!-- 商品为空的时候展示的结构 -->
+ <van-empty wx:else description="该分类下暂无商品,去看看其他商品吧~">
+ <van-button round type="danger" class="bottom-button" bindtap="gotoBack">
+ 查看其他商品
+ </van-button>
+ </van-empty>
</view>
05. 商品列表-实现上拉加载更多功能
思路分析:
当用户从下向上滑动屏幕时,需要加载更多的商品数据。
首先需要在 .js
文件中声明 onReachBottom
方法监听用户是否进行了上拉
当用户上拉时,需要对 page
页码进行加 1,代表要请求下一页的数据
当参数发生改变后,需要重新发送请求,拿最新的 page
向服务器发送请求获取数据。
在下一页的商品数据返回以后,将最新的数据和之前的数据进行合并
实现步骤:
list.js
文件中声明onReachBottom
事件处理函数,监听用户的上拉行为- 在
onReachBottom
函数中加page
进行加 1 的操作,同时发送请求获取下一页数据 - 在
getGoodsList
函数中,实现参数的合并
落地代码:
➡️ /modules/goodsModule/pages/list/list.js
js
import { reqGoodsList } from '../../../api/goods'
Page({
// coding...
// 获取商品列表的数据
async getGoodsList() {
// 调用 API 获取数据
const { data } = await reqGoodsList(this.data.params)
// 将返回的数据赋值给 data 中的变量
this.setData({
+ goodsList: [...this.data.goodsList, ...data.records],
total: data.total
})
},
// coding...
+ // 监听页面的上拉操作
+ onReachBottom() {
+ let { page } = this.data.requestData
+
+ // 页码 + 1
+ this.setData({
+ requestData: { ...this.data.requestData, page: page + 1 }
+ })
+
+ // 重新发送请求
+ this.getGoodsList()
+ }
})
06. 商品列表-判断数据是否加载完毕
思路分析:
上一节我们实现了上拉加载功能。
在这一节,我们需要相关的优化:判断数据是否已经加载完,如果加载已经加载完毕,需要给用户进行提示。
如何判断数据是否加载完成 ❓
可以使用后端返回的 total
和 goodsList
进行对比,如果 total 大于等于 goodsList
,说明商品列表数据没有加载完,可以继续上拉加载更多。
在模板中,我们通过 total
和 goodsList
进行对比,决定是否展示对应的文案
实现步骤:
- 在数据返回以后,将数据中的
total
赋值给data
中的变量total
- 在
onReachBottom
中进行total
和goodsList
进行对比 - 模板中使用
total
和goodsList
进行对比
落地代码:
➡️ /modules/goodsModule/pages/list/list.js
js
import { reqGoodsList } from '../../../api/goods'
Page({
// coding...
// 监听页面的上拉操作
onReachBottom() {
+ // 从 data 中解构数据
+ const { total, goodsList, requestData } = this.data
+ let { page } = requestData
+
+ // 判断数据是否加载完毕
+ if (total === goodsList.length) {
+ // 如果相等,数据数据加载完毕
+ // 如果数据加载完毕,需要给用户提示,同时不继续加载下一个数据
+ this.setData({
+ isFinish: true
+ })
+
+ return
+ }
// 页码 + 1
this.setData({
requestData: { ...this.data.requestData, page: (page += 1) }
})
// 重新发送请求
this.getGoodsList()
}
})
07. 商品列表-节流阀进行列表节流
在用户网速很慢的情况下,如果用户在距离底部来回的进行多次滑动,可能会发送一些无意义的请求、造成请求浪费的情况,因此需要给上拉加载添加节流功能。
我们使用节流阀来给商品列表添加节流功能。
在 data
中定义节流阀状态 isLoading
,默认值是 false
。
在请求发送之前,将 isLoading
设置为 true
,表示请求正在发送。
在请求结束以后,将 isLoading
设置为 false
,表示请求已经完成。
在 onReachBottom
事件监听函数中,对 isLoading
进行判断,如果数据正在请求中,不请求下一页的数据。
落地代码:
➡️ /modules/goodsModule/pages/list/list.js
js
import { reqGoodsList } from '../../../../../api/good'
Page({
// 页面的初始数据
data: {
goodsList: [], // 商品列表数据
isFinish: false, // 判断数据是否加载完毕
+ isLoading: false, // 判断数据是否记载完毕
total: 0, // 列表总数据量
// 接口请求参数
requestData: {
page: 1, // 页码
limit: 10, // 每页请求多少条数据
category1Id: '', // 一级分类 id
category2Id: '' // 二级分类 id
}
},
// 获取商品列表的数据
async getGoodsList() {
+ // 数据真正请求中
+ this.data.isLoading = true
// 调用 API 获取数据
const { data } = await reqGoodsList(this.data.requestData)
+ // 数据加载完毕
+ this.data.isLoading = false
// 将返回的数据赋值给 data 中的变量
this.setData({
goodsList: [...this.data.goodsList, ...data.records],
total: data.total
})
},
// 监听页面的上拉操作
onReachBottom() {
// 从 data 中解构数据
const { total, goodsList, requestData, isLoading } = this.data
let { page } = requestData
+ // 判断是否加载完毕,如果 isLoading 等于 true
+ // 说明数据还没有加载完毕,不加载下一页数据
+ if (isLoading) return
// 判断数据是否加载完毕
// coding...
}
})
08. 商品列表-实现下拉刷新功能
下拉刷新是小程序中常见的一种刷新方式,当用户下拉页面时,页面会自动刷新,以便用户获取最新的内容。
小程序中实现上拉加载更多的方式:
-
在
页面.json
中开启允许下拉,同时可以配置 窗口、loading 样式等 -
在
页面.js
中定义onPullDownRefresh
事件监听用户下拉刷新
落地代码:
➡️ /modules/goodsModule/pages/list/list.json
json
{
"usingComponents": {
"goods-card": "/components/goods-card/goods-card"
},
"navigationBarTitleText": "商品列表",
"enablePullDownRefresh": true,
"backgroundColor": "#f7f4f8",
"backgroundTextStyle": "dark"
}
➡️ /modules/goodsModule/pages/list/list.js
js
// 监听页面的下拉刷新
onPullDownRefresh() {
// 将数据进行重置
this.setData({
goodsList: [],
total: 0,
isFinish: false,
requestData: { ...this.data.requestData, page: 1 }
})
// 重新获取列表数据
this.getGoodsList()
}
09. 商品详情-获取并渲染商品详情
思路分析:
点击首页轮播图以及点击商品列表商品的时候,需要跳转到商品详情页面
在跳转时将商品的id
传递到了商品详情页面,只需要使用 id
向后端服务器请求数据,获取对应商品的详情数据
在获取到数据以后,使用后端返回的数据对页面进行渲染。
实现步骤:
- 在
/pages/goods/detail/detail.js
中导入封装好的获取商品列表的API
函数 - 页面数据在页面加载的时候进行调用,在
onLoad
钩子函数中调用reqGoodsInfo
方法 - 在获取到数据以后,使用后端返回的数据对页面进行渲染
落地代码:
➡️ /modules/goodsModule/pages/detail/detail.js
js
+ import { reqGoodsInfo } from '../../../api/goods'
Page({
// 页面的初始数据
data: {
goodsInfo: {}, // 商品详情
show: false, // 控制加入购物车和立即购买弹框的显示
count: 1, // 商品购买数量,默认是 1
blessing: '' // 祝福语
},
+ // 获取商品的详情
+ async getGoodsInfo() {
+ // 调用接口、传入参数、获取商品详情
+ const { data: goodsInfo } = await reqGoodsInfo(this.goodsId)
+
+ // 将商品详情数据赋值给 data 中的变量
+ this.setData({
+ goodsInfo
+ })
+ },
+ // 生命周期函数--监听页面加载
+ onLoad(options) {
+ // 将商品 id 挂载到页面实例上
+ this.goodsId = options.goodsId ? options.goodsId : ''
+
+ // 获取商品详情的数据
+ this.getGoodsInfo()
+ }
// coding...
})
➡️ /modules/goodsModule/pages/detail/detail.html
html
<view class="container goods-detail">
<!-- 商品大图 -->
<view class="banner-img">
+ <image class="img" src="{{ goodsInfo.imageUrl }}" />
</view>
<!-- 商品的基本信息 -->
<view class="content">
<view class="price">
+ <view class="price-num">¥{{ goodsInfo.price }}</view>
+ <view class="price-origin-num">¥{{ goodsInfo.marketPrice }}</view>
</view>
+ <view class="title">{{ goodsInfo.name }}</view>
+ <view class="desc">{{ goodsInfo.material }}</view>
</view>
<!-- 商品的详细信息 -->
<view class="detail">
<image
+ wx:for="{{ goodsInfo.detailList}}"
+ wx:key="index"
+ src="{{ item }}"
class="img"
mode="widthFix"
/>
</view>
<!-- 商品的底部商品导航 -->
<van-goods-action>
<!-- 代码略... -->
</van-goods-action>
<!-- 加入购物车、立即购买弹框 -->
<!-- show 控制弹框的隐藏和展示 -->
<!-- bind:close 点击关闭弹框时触发的回调 -->
<van-action-sheet show="{{ show }}" bind:close="onClose">
<view class="sheet-wrapper">
<view class="goods-item">
<!-- 需要购买的商品图片 -->
<view class="mid">
+ <image class="img" src="{{ goodsInfo.imageUrl }}" />
</view>
<!-- 商品基本信息 -->
<view class="right">
<!-- 商品名字 -->
+ <view class="title"> {{ goodsInfo.name }} </view>
<!-- 商品价格 -->
<view class="buy">
<view class="price">
<view class="symbol">¥</view>
+ <view class="num">{{ goodsInfo.price }}</view>
</view>
<!-- 购买数量弹框 -->
<view class="buy-btn">
<!-- Stepper 步进器,由增加按钮、减少按钮和输入框组成,控制购买数量 -->
<van-stepper value="{{ count }}" bind:change="onChangeGoodsCount" />
</view>
</view>
</view>
</view>
<!-- 祝福语输入框 -->
<view class="time-wraper">
<!-- 代码略... -->
</view>
<!-- 取消、确定弹框 -->
<view class="sheet-footer-btn">
<van-button block type="primary" round> 确定 </van-button>
</view>
</view>
</van-action-sheet>
</view>
10. 商品详情-详情图片预览功能
思路分析:
当点击商品的图片时,需要将图片进行全屏预览
如果想实现该功能,需要使用小程序提供的 API
:wx.previewImage()
,用来在新页面中全屏预览图片。预览的过程中用户可以进行保存图片、发送给朋友等操作。语法如下:
js
wx.previewImage({
current: '', // 当前显示图片的 http 链接
urls: [] // 需要预览的图片 http 链接列表
})
实现步骤:
- 给展示大图的
image
组件绑定点击事件,同时通过自定义属性的方式,传递当前需要显示的图片http 链接 - 同时商品详情的数组数据传递给
urls
数组即可
落地代码:
➡️ /pages/goods/detail/detail.html
html
<!-- 商品大图 -->
<view class="banner-img">
<image
class="img"
src="{{ goodsInfo.imageUrl }}"
bindtap="previewImg"
/>
</view>
➡️ /pages/goods/detail/detail.js
js
// 预览商品图片
previewImg() {
// 调用预览图片的 API
wx.previewImage({
urls: this.data.goodsInfo.detailList
})
}
优化:配置 @ 路径别名优化访问路径
在对小程序进行分包时,如果访问小程序根目录下的文件,那么访问的路径就会很长。
在 Vue 中,可以使用 @ 符号指向源码目录,简化路径,小程序也给提供了配置的方式。
在小程序中可以在 app.json
中使用 resolveAlias
配置项用来自定义模块路径的映射规则。
json
{
"resolveAlias": {
"@/*": "/*"
}
}
📌:注意事项:
resolveAlias
进行的是路径匹配,其中的 key 和 value 须以/*
结尾如果在 project.config.json 中指定了 miniprogramRoot,则
/*
指代的根目录是 miniprogramRoot 对应的路径,而不是开发者工具项目的根目录