慕尚花坊项目笔记
一、项目初始化
重置app.js
中的代码
删除app.json
中pages
下的"pages/logs/logs"
路径,同时删除pages.logs
文件夹
删除app.json
中pages
下的"renderOptions"
以及"componentFrameword"
字段
重置app.wxss
中的代码
删除components
中的自定义组件
重置pages/index
文件夹下的index.js
、index.wxss
、index.html
以及index.json
文件
更新utils
下utils.js
的文件名为formatTime.js
二、自定义构建npm、集成sass
1、自定义构建npm
-
首先在
project.config.json
中配置miniprogramRoot
,指定小程序源码的目录 -
然后配置
project.config.json
的setting.packNpmManually
为true
,开启自定义 node_modules 和 miniprogram_npm 位置的构建 npm 方式 -
最后配置 project.config.json 的
setting.packNpmRelationList
项,指定packageJsonPath
和miniprogramNpmDistDir
的位置- packageJsonPath 表示 node_modules 源对应的 package.json相对地址
- miniprogramNpmDistDir 表示 node_modules 的构建结果目标地址
-
创建package.json并安装
vant
,然后进行npm 构建
,测试是否能够正常vant
构建成功npm init -y
npm i @vant/weapp
注意:因为npm init -y
命令会将当前目录名作为package.json中name字段的默认值,而name字段不允许使用中文。 如果目录名包含中文,就会导致初始化失败,提示类似 "Invalid name" 的错误信息。
2、集成sass
在 project.config.json
文件中,修改 setting
下的 useCompilerPlugins
字段为 ["sass"]
,即可开启工具内置的 sass 编译插件。
注意:sass需要用双引号包裹,JSON 规定字符串必须用 双引号 " 包裹,单引号 ' 或未闭合的引号会导致错误
三、使用vscode开发
-
在项目的根目录下创建
.vscode
文件夹,注意:文件夹名字前面带.
点 -
在
.vscode
文件夹下,创建settings.json
,用来对安装的插件属性进行设置,具体属性设置从下面复制即可注意:
.vscode
文件夹下的settings.json
文件只对当前一个项目生效 -
在【项目的根目录】下创建
.prettierrc
文件,进行Prettier
代码规则的配置,规则从下面复制 -
为了让
Prettier
配置项在微信开发者工具生效,需要在微信开发者工具中也安装Prettier
扩展插件。
.vscode/settings.json
json
{
// 保存文件时是否自动格式化
"editor.formatOnSave": true,
// ---------------- 以下是 [ prettier ] 插件配置 ----------------
// 指定 javascript、wxss、scss、less、json、jsonc 等类型文件使用 prettier 进行格式化
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[wxss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// Prettier 的一个配置项,用于指定哪些文件类型需要使用 Prettier 进行格式化
"prettier.documentSelectors": ["**/*.wxml", "**/*.wxss", "**/*.wxs"],
// ---------------- 以下是 [ WXML - Language Service ] 插件配置 ----------------
// wxml 文件使用 prettier 进行格式化
"[wxml]": {
// "qiu8310.minapp-vscode" 是 WXML - Language Service 插件提供的配置项
// 此插件主要是针对小程序的 wxml 模板语言,可以自动补全所有的组件、组件属性、组件属性值等等
// 如果是 VsCode 需要开启这个配置
"editor.defaultFormatter": "qiu8310.minapp-vscode"
// 如果是微信小程序,需要开启这个配置,通过 esbenp.prettier-vscode 对代码进行格式化
// "editor.defaultFormatter": "esbenp.prettier-vscode"
},
// 创建组件时使用的 css 后缀
"minapp-vscode.cssExtname": "scss", // 默认 wxss,支持 styl sass scss less css
// 指定 WXML 格式化工具
"minapp-vscode.wxmlFormatter": "prettier",
// 配置 prettier 代码规范
"minapp-vscode.prettier": {
"useTabs": false,
"tabWidth": 2,
"printWidth": 80
},
// ---------------- 以下是 [ 微信小程序助手-Y ] 插件配置 ----------------
// 新增、删除小程序页面时,是否自动同步 app.json pages 路径配置,默认为 false
"wechat-miniapp.sync.delete": true,
// 设置小程序页面 wxss 样式文件的扩展名
"wechat-miniapp.ext.style": "scss",
// ---------------- 其他配置项 ----------------
// 配置语言的文件关联,运行 .json 文件时写注释
// 但在 app.json 和 page.json 中无法使用
"files.associations": {
"*.json": "jsonc"
}
}
.prettierrc
json
{
"semi": false,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"printWidth": 180,
"trailingComma": "none",
"overrides": [
{
"files": "*.wxml",
"options": { "parser": "html" }
},
{
"files": "*.wxss",
"options": { "parser": "css" }
},
{
"files": "*.wxs",
"options": { "parser": "babel" }
}
]
}
配置项 | 配置项含义 |
---|---|
"semi": false | 不要有分号 |
"singleQuote": true | 使用单引号 |
"useTabs": false | 缩进不使用 tab,使用空格 |
"tabWidth": 2 | tab缩进为4个空格字符 |
"printWidth": 80 | 一行的字符数,如果超过会进行换行,默认为80 |
"trailingComma": "none" | 尾随逗号问题,设置为none 不显示 逗号 |
"overrides": [] | overrides 解析器:默认情况下,Prettier 会根据文件文件拓展名推断要使用的解析器 |
四、通用模块封装
封装后可以极大简化API的调用
1、消息提示模块封装
封装思路:
-
创建一个
toast
方法对wx.showToast()
方法进行封装 -
调用该方法时,传递对象作为参数
-
如果没有传递任何参数,设置一个空对象
{}
作为默认参数 -
从对象中包含
title
、icon
、duration
、mask
参数,并给参数设置默认值
-
-
在需要显示弹出框的时候调用
toast
方法,并传入相关的参数,有两种参数方式:- 不传递参数,使用默认参值
- 传入部分参数,覆盖默认的参数

调用方式:
-
模块化的方式导入使用
jsimport { toast } from './extendApi' toast() toast({ title: '数据加载失败....', mask: true })
-
将封装的模块挂载到
wx
全局对象身上jswx.toast() wx.toast({ title: '数据加载失败....', mask: true })
模块化方式导入使用
挂载到wx全局对象身上
2、模态对话框封装
封装思路:
- 对
wx.showModal()
方法进行封装, 封装后的新方法叫modal
- 调用该方法时,传递对象作为参数,对象的参数同
wx.showModal()
参数一致 - 封装的
modal
方法的内部通过Promise
返回用户执行的操作(确定和取消,都通过 resolve 返回) - 在需要显示模态对话框的时候调用
modal
方法,并传入相关的参数,有三种调用方式:- 不传递参数,使用默认参数
- 传递参数,覆盖默认的参数
调用方式:
新封装的本地存储模块,我们依然希望有两种调用的方式:
- 模块化的方式导入使用
- 将封装的模块挂载到
wx
全局对象身上
挂载到wx上且传入新的参数案例:
3、封装本地存储API
javascript
export const setStorage = (key,value) => {
try{
wx.setStorageSync(key,value)
}catch(error){
console.error(`存储指定 ${key} 数据发生错误:`, err)
}
}
export const getStorage = (key) => {
try{
const value = wx.getStorageSync(key)
if(value){
return value
}
}catch(error){
console.error(`读取指定 ${key} 数据发生错误:`, err)
}
}
export const removeStorage = (key) => {
try {
wx.removeStorageSync(key)
} catch (error) {
console.error(`移除指定 ${key} 数据发生错误:`, error)
}
}
export const clearStorage = () => {
try {
wx.clearStorageSync()
} catch (error) {
console.error("清空本地存储时发生错误:", error);
}
}
javascript
import {setStorage,getStorage,removeStorage,clearStorage} from './utils/storage'
App({
async onShow(){
setStorage('name','tom')
setStorage('age',10)
const name = getStorage('name')
console.log(name);
removeStorage('name')
clearStorage()
}
})
4、封装异步存储API+优化代码
javascript
export const asyncSetStorage = (key,data) => {
return new Promise((resolve) => {
wx.setStorage({
key,
data,
complete(res){
resolve(res)
}
})
})
}
export const asyncGetStorage = (key) => {
return new Promise((resolve) => {
wx.getStorage({
key,
complete(res){
resolve(res)
}
})
})
}
export const asyncRemoveStorage = (key) => {
return new Promise((resolve) => {
wx.removeStorage({
key,
complete(res){
resolve(res)
}
})
})
}
export const asyncClearStorage = () => {
return new Promise((resolve) => {
wx.clearStorage({
complete(res){
resolve(res)
}
})
})
}
javascript
import {asyncSetStorage,asyncGetStorage,asyncRemoveStorage,asyncClearStorage} from './utils/storage'
App({
async onShow(){
asyncSetStorage('name','Jerry').then((res)=>{
console.log(res);
})
asyncGetStorage('name').then((res)=>{
console.log(res);
})
asyncRemoveStorage('name').then((res)=>{
console.log(res);
})
asyncClearStorage().then((res)=>{
console.log(res);
})
}
})
五、网络请求封装
1、为什么要封装wx.request()
小程序大多数 API 都是异步 API,如 wx.request(),wx.login()等。这类 API 接口通常都接收一个 Object
对象类型的参数,参数中可以按需指定以下字段来接收接口调用结果:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
success | function | 否 | 调用成功的回调函数 |
fail | function | 否 | 调用失败的回调函数 |
complete | function | 否 | 调用结束的回调函数(调用成功、失败都会执行) |
2、请求封装-request方法
在封装网络请求模块的时候,采用class类
来进行封装,采用类的方式封装代码更具可复用性,也方便地添加新的方法和属性,提高代码的扩展性
在 WxRequest
类内部封装一个 request
实例方法
request
实例方法中需要使用 Promise
封装 wx.request,也就是使用 Promise
处理 wx.request
的返回结果
request
实例方法接收一个 options
对象作为形参,options
参数和调用 wx.request
时传递的请求配置项一致
- 接口调用成功时,通过
resolve
返回响应数据 - 接口调用失败时,通过
reject
返回错误原因
3、请求封装-设置请求参数
在发起网络请求时,需要配置一些请求参数,其中有一些参数我们可以设置为默认参数,例如:请求方法、超时时长等,因此在封装时我们要定义一些默认的参数。
我们还要允许在进行实例化的时候,传入参数,对默认的参数进行修改
js
// 对 WxRequest 进行实例化
const instance = new WxRequest({
baseURL: 'https://gmall-prod.atguigu.cn/mall-api', // 请求基准地址
timeout: 10000 // 微信小程序 timeout 默认值为 60000
})
在通过实例调用request实例方法时也会传入相关的请求参数
js
const res = await instance.request({
url: '/index/findBanner',
method: 'GET'
})
从而得出结论:请求参数的设置有三种方式:
- 默认参数:在
WxRequest
类中添加defaults
实例属性来设置默认值 - 实例化时参数:在对
WxRequest
类进行实例化时传入相关的参数,需要在constructor
构造函数形参进行接收 - 调用实例方法时传入请求参数
request.js
实例化时设置请求参数(被constructor形参接收)调用实例方法时设置请求参数(被request实例方法的形参options接收)
4、请求封装-封装请求快捷方法
在request()基础上封装一些快捷方法,简化request的调用
需要封装 4 个快捷方法,分别是 get
、delete
、post
、put
,他们的调用方式如下:
js
instance.get('请求地址', '请求参数', '请求配置')
instance.delete('请求地址', '请求参数', '请求配置')
instance.post('请求地址', '请求参数', '请求配置')
instance.put('请求地址', '请求参数', '请求配置')
这 4 个请求方法,都是通过实例化的方式进行调用,所以需要 Request
类中暴露出来 get
、delete
、post
、put
方法。每个方法接收三个参数,分别是:接口地址、请求参数以及其他参数。
这 4 个快捷方法,本质上其实还是调用 request
方法,我们只要在方法内部组织好参数,调用 request
发送请求即可
与request实例方法同级
5、wx.request注意事项
在使用 wx.request 发送网络请求时,只要成功接收到服务器返回,无论statusCode
是多少,都会进入 success
回调。开发者根据业务逻辑对返回值进行判断。
一般只有网络出现异常、请求超时等时候,才会走 fail
回调
6、请求封装-定义请求/响应拦截器
为了方便统一处理请求参数以及服务器响应结果,为WXRequest
添加拦截器功能,拦截器包括请求拦截器 和响应拦截器:
- 请求拦截器本质上是在请求之前调用的函数,用来对请求参数进行新增和修改
- 响应拦截器本质上是在响应之后调用的函数,用来对相响应数据做点什么
不管成功响应还是失败相应,都会执行响应拦截器
可以在WXRequest
类内部定义interceptors
实例属性,属性中需要包含request
以及response
方法
调用请求拦截器
调用响应拦截器
7、请求封装-完善请求/响应拦截器
目前不管请求成功 (success),还是请求失败(fail),都会执行响应拦截器
那么怎么判断是请求成功,还是请求失败呢 ?
封装需求:
- 如果请求成功,将响应成功的数据传递给响应拦截器,同时在传递的数据中新增
isSuccess: true
字段,表示请求成功 - 如果请求失败,将响应失败的数据传递给响应拦截器,同时在传递的数据中新增
isSuccess: false
字段,表示请求失败
在实例调用的响应拦截中,根据传递的数据进行以下的处理:
- 如果
isSuccess: true
表示服务器响应了结果,我们可以将服务器响应的数据简化以后进行返回 - 如果
isSuccess: false
表示是网络超时或其他网络问题,提示网络异常
,同时将返回即可
8、结合项目使用请求/响应拦截器
使用请求拦截器:
在发送请求时,购物车列表、收货地址、更新头像等接口,都需要进行权限验证,因此我们需要在请求拦截器中判断本地是否存在访问令牌 token
,如果存在就需要在请求头中添加 token
字段。
使用响应拦截器:
在使用 wx.request 发送网络请求时。只要成功接收到服务器返回,无论statusCode
是多少,都会进入 success
回调。
因此开发者根据业务逻辑对返回值进行判断。
后端返回的业务状态码如下:
- 业务状态码 === 200, 说明接口请求成功,服务器成功返回了数据
- 业务状态码 === 208, 说明没有 token 或者 token 过期失效,需要登录或者重新登录
- 业务状态码 === 其他,说明请求或者响应出现了异常
javascript
// 配置请求拦截器(会覆盖默认的请求拦截器)
instance.interceptors.request=(config)=>{
// 在请求发送之前做点什么
// 在发送请求之前,需要先判断本地是否存在访问令牌token
// 如果存在token,就需要在请求头中添加token字段
const token = getStorage('token')
if(token){
config.header['token']=token
}
return config
}
// 配置响应拦截器(会覆盖默认的响应拦截器)
instance.interceptors.response = async (response)=>{
// 对服务器响应的数据做点什么
// 从response中解构isSuccess,如果isSuccess为false,说明执行了fail回调函数,需要给用户一个提示
const {isSuccess,data} = response
if(!isSuccess){
wx.showToast({
title:'网络异常请重试',
icon:'error'
})
return response
}
// 判断服务器响应的业务状态码
switch(data.code){
case 200:
return data
case 208:
// 说明没有token或者token失效,需要让用户登录或重新登录
const res = await modal({
content:'鉴权失败,请重新登录',
showCancel:false // 不显示取消按钮
})
if(res){
// 清除之前失效的token,同时清除本地存储的全部信息
clearStorage()
wx.navigateTo({
url: '/pages/login/login',
})
}
return Promise.reject(response)
default:
toast({
title:'程序出现异常,请联系客服或稍后重试'
})
return Promise.reject(response)
}
}
9、请求封装-添加并发请求
前端并发请求是指在前端页面同时向后端发起多个请求的情况。当一个页面需要请求多个接口获取数据时,为了提高页面的加载速度和用户体验,可以同时发起多个请求,这些请求之间就是并发的关系。
我们通过两种方式演示发起多个请求:
- 使用
async
和await
方式 - 使用
Promise.all()
方式
async await方式
Promise.all()方法
在request.js中封装promise.all方法,在test.js中调用all()实例方法
10、添加loading
在封装时添加 loading
效果,从而提高用户使用体验
-
在请求发送之前,需要通过
wx.showLoading
展示loading
效果 -
当服务器响应数据以后,需要调用
wx.hideLoading
隐藏loading
效果
要不要把loading 添加到 WxRequest 内部 ?
- 在类内部进行添加,方便多个项目直接使用类提供的 loading 效果,也方便统一优化 wx.showLoading 使用体验。
但是不方便自己来进行 loading 个性化定制。 - 如果想自己来控制 loading 效果,带来更丰富的交互体验,就不需要将 loading 封装到类内部,但是需要开发者自己来优化 wx.showLoading 使用体验,每个项目都要写一份。
添加loading效果
11、完善loading效果
目前在发送请求时,请求发送之前会展示 loading
,响应以后会隐藏 loading
。
但是 loading 的展示和隐藏会存在以下问题:
- 每次请求都会执行
wx.showLoading()
,但是页面中只会显示一个,后面的loading
会将前面的覆盖 - 同时发起多次请求,只要有一个请求成功响应就会调用
wx.hideLoading
,导致其他请求还没完成,loading
也会隐藏 - 请求过快 或 一个请求在另一个请求后立即触发,这时候会出现
loading
闪烁问题
我们通过 队列 的方式解决这三个问题:首先在类中新增一个实例属性 queue
,初始值是一个空数组
- 发起请求之前,判断
queue
如果是空数组则显示loading
,然后立即向queue
新增请求标识 - 在
complete
中每次请求成功结束,从queue
中移除一个请求标识,queue
为空时隐藏loading
- 为了解决网络请求过快产生
loading
闪烁问题,可以使用定时器来做判断即可
解决第一个问题:判断queue是否为空,如果是空,就显示loading,如果不是空就不调用wx.showLoading()
解决第二个问题:在每个请求结束以后都会执行complete,每次从queue队列中删除一个标识 ,并判断queue数组是否为空,如果为空说明并发请求完成了,需要隐藏loading,如果不为空则说明还有请求未完成不需要隐藏loading
解决第三个问题:当一个②请求是依靠①请求后才能触发时
- ①请求完毕pop删除queue中最后一个request标识后,再向queue中添加一个request标识
- ②请求进来如果有定时器会首先清除定时器并添加一个request
- ②请求完毕,删除一个request,此时queue中还剩余一个request
- 接下来如果没有新的request了,就会执行定时器内操作,删除最后一个request,隐藏loading,最后关闭定时器
12、控制loading显示
在实际开发中,有的接口可能不需要显示loading效果,或者开发者希望自己来控制loading的样式与交互,那么就需要关闭默认的loading效果,这时候我们就需要一个开关来控制 loading
显示。
- 类内部设置默认请求参数
isLoading
属性,默认值是true
,在类内部根据isLoading
属性做判断即可
-
某个接口不需要显示
loading
效果,可以在发送请求的时候,可以新增请求配置isLoading
设置为false
-
整个项目都不需要显示
loading
效果,可以在实例化的时候(http.js中),传入isLoading
配置为false

若设置整个项目都不需要loading效果,但是希望某一个接口用到默认loading效果,只要为那个接口新增请求配置isLoading:true
13、封装uploadFile
wx.uploadFile也是我们在开发中常用的一个api,用来将本地资源上传到服务器
js
wx.uploadFile({
url: '', // 必填项,开发者服务器地址
filePath: '', // 必填项,要上传文件资源的路径 (本地路径)
name: '' // 必填项,文件对应的 key,开发者在服务端可以通过这个 key 获取文件的二进制内容
})
首先在 WxRequest
类内部创建 upload
实例方法,实例方法接收四个属性:
js
/**
* @description 文件上传接口封装
* @param { string } url 文件上传地址
* @param { string } filePath 要上传文件资源的路径
* @param { string } name 文件对应的 key
* @param { string } config 其他配置项
* @returns
*/
upload(url, filePath, name, config = {}) {
return this.request(
Object.assign({ url, filePath, name, method: 'UPLOAD' }, config)
)
}
这时候我们需要在 request
实例方法中,对 method
进行判断,如果是 UPLOAD
,则调用 wx.uploadFile
上传API
js
// request 实例方法接收一个对象类型的参数
// 属性值和 wx.request 方法调用时传递的参数保持一致
request(options) {
// coding...
// 需要使用 Promise 封装 wx.request,处理异步请求
return new Promise((resolve, reject) => {
if (options.method === 'UPLOAD') {
wx.uploadFile({
...options,
success: (res) => {
// 将服务器响应的数据通过 JSON.parse 转换为 JS 对象
res.data = JSON.parse(res.data)
const mergeRes = Object.assign({}, res, {
config: options,
isSuccess: true
})
resolve(this.interceptors.response(mergeRes))
},
fail: (err) => {
const mergeErr = Object.assign({}, err, {
config: options,
isSuccess: true
})
reject(this.interceptors.response(mergeErr))
},
complete: () => {
this.queue.pop()
this.queue.length === 0 && wx.hideLoading()
}
})
} else {
wx.request({
// coding...
})
}
})
}
创建upload实例方法
- url:文件的上传地址、接口地址
- filePath:要上传的文件资源路径
- name:文件对应的key
- config:其他配置项

14、npm包发送请求
shell
npm install mina-request

15、小程序设置环境变量
在实际开发中,不同的开发环境,调用的接口地址是不一样的。
例如:开发环境需要调用开发版的接口地址,生产环境需要调用正式版的接口地址
这时候,我们就可以使用小程序提供的 wx.getAccountInfoSync()
接口,用来获取当前账号信息,在账号信息中包含着 小程序 当前环境版本。
环境版本 | 合法值 |
---|---|
开发版 | develop |
体验版 | trial |
正式版 | release |
javascript
// 配置当前小程序项目的环境变量 utils/env.js
const {miniProgram} = wx.getAccountInfoSync()
const {envVersion} = miniProgram
let env = {
baseURL:'https://gmall-prod.atguigu.cn/mall-api'
}
switch(envVersion){
// 开发版
case 'develop':
env.baseURL = 'https://gmall-prod.atguigu.cn/mall-api'
break;
// 体验版
case 'trial':
env.baseURL = 'https://gmall-prod.atguigu.cn/mall-api'
break;
// 正式版
case 'release':
env.baseURL = 'https://gmall-prod.atguigu.cn/mall-api'
break;
default:
env.baseURL = 'https://gmall-prod.atguigu.cn/mall-api'
break;
}
export {env}
16、接口调用方式说明
在开发中,我们会将所有的网络请求方法放置在 api 目录下统一管理,然后按照模块功能来划分成对应的文件,在文件中将接口封装成一个个方法单独导出,例如:
这样做的有以下几点好处:
- 易于维护:一个文件就是一个模块,一个方法就是一个功能,清晰明了,查找方便
- 便于复用:哪里使用,哪里导入,可以在任何一个业务组件中导入需要的方法
- 团队合作:分工合作
六、项目首页
1、首页数据获取
api/index.js
javascript
import http from "../utils/http"
export const reqIndexData = () =>{
// 通过并发请求获取首页的数据,提升页面的渲染速度
// 方法一:直接使用promise.all并发请求
// return Promise.all([
// http.get('/index/findBanner'),
// http.get('/index/findCategory1'),
// http.get('/index/advertisement'),
// http.get('/index/findListGoods'),
// http.get('/index/findRecommendGoods')
// ])
// 方法二:使用自己封装的all方法并发请求
return http.all(
http.get('/index/findBanner'),
http.get('/index/findCategory1'),
http.get('/index/advertisement'),
http.get('/index/findListGoods'),
http.get('/index/findRecommendGoods')
)
}
Pages/index/index.js
javascript
import {reqIndexData} from "../../api/index"
Page({
data:{
bannerList:[],// 轮播图数据
categoryList:[],// 商品导航数据
activeList:[],// 活动渲染区域
hotList:[],// 人气推荐
guessList:[],// 猜你喜欢
},
// 获取首页数据
async getIndexData(){
// 调用接口api函数,获取数据
const res = await reqIndexData()
console.log(res);
this.setData({
bannerList:res[0].data,
categoryList:res[1].data,
activeList:res[2].data,
guessList:res[3].data,
hotList:res[4].data
})
},
// 监听页面加载
onLoad(){
// 页面加载后调用获取首页数据的方法
this.getIndexData()
}
})
2、分析轮播图区域并渲染
将轮播图制作成一个组件直接在index.wxml中引用
传入接口的参数
轮播图wxml解构,借助block标签
自定义指示点
3、轮播图和指示点的联动
获取当前展示轮播图的索引
动态添加active类名
4、商品导航+活动区域+猜你喜欢+人气推荐
(1)商品导航
(2)活动区域
javascript
<view class="adver-left">
<navigator url="/pages/goods/list/list?category2Id={{activeList[0].category2Id}}">
<image src="{{activeList[0].imageUrl}}" mode="widthFix" />
</navigator>
</view>
<view class="adver-right">
<view>
<navigator url="/pages/goods/list/list?category2Id={{activeList[1].category2Id}}">
<image src="{{activeList[1].imageUrl}}" mode="widthFix" />
</navigator>
</view>
<view>
<navigator url="/pages/goods/list/list?category2Id={{activeList[2].category2Id}}">
<image src="{{activeList[2].imageUrl}}" mode="widthFix" />
</navigator>
</view>
</view>
(3)猜你喜欢、人气推荐
把商品列表数据传递给商品列表组件
列表渲染
为商品卡片传递商品信息
渲染
javascript
<view class="goods_cart_container">
<navigator
class="navigator_nav"
url="/pages/goods/detail/detail?goodsId={{goodItem.id}}"
>
<!-- 商品图片 -->
<image class="good_img" src="{{goodItem.imageUrl}}" mode="widthFix" />
<!-- 商品详细信息 -->
<view class="goods_item_info">
<!-- 商品名称 -->
<text class="goods_item_info_name">{{goodItem.name}}</text>
<!-- 商品描述 -->
<text class="goods_item_info_promo"
>{{goodItem.floralLanguage}}</text
>
<!-- 商品价格 -->
<view class="goods_item_info_bottom">
<view class="goods_item_info_price">
<text class="text">¥</text>{{goodItem.price}}
</view>
<view class="goods_item_info_origin_price">
<text class="text">¥</text> 1{{goodItem.marketPrice}}
</view>
<!-- 加入购物车图片 -->
<view class="goods_item_info_btn">
<image class="goods_image" src="/assets/images/buybtn.png" mode="" />
</view>
</view>
</view>
</navigator>
</view>
5、首页骨架屏组件
骨架屏是页面的一个空白版本,开发者会使用 CSS
绘制一些灰色的区块,将页面内容大致勾勒出轮廓。
通常会在页面完全渲染之前,将骨架屏代码进行展示,待数据加载完成后,再替换成真实的内容。
骨架屏的设计旨在优化用户体验。
index.wxml
index.js
index.scss
七、商品分类
1、获取商品分类数据
api/category.js
javascript
import http from "../utils/http"
export const reqCategoryData = () => {
return http.get('/index/findCategoryTree')
}
Pages/category.js
javascript
import {reqCategoryData} from '../../api/category.js'
Page({
// 初始化数据
data:{
categoryList:[]
},
// 获取商品分类的数据
async getCategoryData(){
const res = await reqCategoryData()
console.log(res);
if(res.code === 200){
this.setData({
categoryList:res.data
})
}
},
// 监听页面加载
onLoad(){
this.getCategoryData()
}
})
2、渲染一级分类并实现切换功能
3、获取并渲染二级分类数据

八、框架扩展
1、mobx-miniprogram介绍
目前已经学习了 6 种小程序页面、组件间的数据通信方案,分别是:
- 数据绑定:
properties
- 获取组件实例:
this.selectComponent()
- 事件绑定:
this.triggerEvent()
- 获取应用实例:
getApp()
- 页面间通信:
EventChannel
- 事件总线:
pubsub-js
但是随着项目的业务逻辑越来越复杂,组件和页面间通信就会变的非常复杂。例如:有些状态需要在多个页面间进行同步使用,一个地方发生变更,所有使用的地方都需要发生改变,这时候如果使用前面的数据通信方案进行传递数据,给管理和维护将存在很大的问题。
为了方便进行页面、组件之间数据的传递,小程序官方提供了一个扩展工具库: mobx-miniprogram
介绍:
mobx-miniprogram
是针对微信小程序开发的一个简单、高效、轻量级状态管理库,它基于Mobx
状态管理框架实现。
使用 mobx-miniprogram
定义管理的状态是响应式的,当状态一旦它改变,所有关联组件都会自动更新相对应的数据
通过该扩展工具库,开发者可以很方便地在小程序中全局共享的状态,并自动更新视图组件,从而提升小程序的开发效率
使用方法:
在使用 mobx-miniprogram
需要安装两个包:mobx-miniprogram
和 mobx-miniprogram-bindings
mobx-miniprogram
的作用:创建Store
对象,用于存储应用的数据mobx-miniprogram-bindings
的作用:将状态和组件、页面进行绑定关联,从而在组件和页面中操作数据
安装:
shell
npm install mobx-miniprogram mobx-miniprogram-bindings
mobx-miniprogram 官方文档
mobx-miniprogram-bindings 官方文档
2、mobx-miniprogram创建Store对象
创建 Store 对象需要使用 mobx-miniprogram
, mobx-miniprogram
三个核心概念:
observable
:用于创建一个被监测的对象,对象的属性就是应用的状态(state),这些状态会被转换成响应式数据。action
:用于修改状态(state)的方法,需要使用 action 函数显式的声明创建。computed
:根据已有状态(state)生成的新值。计算属性是一个方法,在方法前面必须加上get
修饰符
mobx-miniprogram
使用步骤:
-
在项目的根目录下创建
stores
文件夹,然后在该文件夹下新建numstore.js
-
在
/store/numstore.js
导入observable
、action
方法jsimport { observable, action } from 'mobx-miniprogram'
observable
用于创建一个被监测的对象,对象的属性是应用状态,状态会被自动转换为响应式数据action
用来显示的定义action方法,action方法是用来修改、更新状态的
-
使用
observable
方法需要接受一个store
对象,存储应用的状态
js
// observable:用于创建一个被监测的对象,对象的属性就是应用的状态(state),这些状态会被转换成响应式数据。
// action:用于显式的声明创建更新 state 状态的方法
import { observable, action } from 'mobx-miniprogram'
// 使用 observable 创建一个被监测的对象
export const numStore = observable({
// 创建应用状态
numA: 1,
numB: 2,
// 使用 action 更新 numA 以及 numB
update: action(function () {
this.numA+=1
this.numB+=1
}),
// computed计算属性,前面必须使用 get 修饰符,是根据已有的状态产生新状态
get sum() {
// 计算属性内部必须要有返回值
return this.numA + this.numB;
}
})
3、在组件中使用Store数据
如果需要在组件中使用状态,需要mobx-miniprogram-bingings
库中导入ComponentWithStore
方法。在使用时:需要将Component
方法替换成ComponentWithStore
方法,原本组件配置项也需要写到该方法中。
在替换以后,就会新增一个 storeBindings
配置项,配置项常用的属性有以下三个:
store
: 指定要绑定的Store
对象fields
: 指定需要绑定的data
字段actions
: 指定需要映射的actions
方法
如果需要在组件中使用Store中的数据
第一步 需要从mobx-miniprogram-bindings里面引入ComponentWithStore方法
第二步 使用ComponentWithStore方法替换Component方法
第三步 导入当前组件需要使用的Store对象
第四步 配置当前组件需要与哪些Store相关联
在从store中引入数据和方法以后:
- 如果是数据,会被注入到data对象中
- 如果是方法,会被注入到methods对象中
第五步 使用
numstore.js中numA、numB、sum都是data,update是methods
4、在页面(pages)中使用数据
(1)方式一
Component方法用于创建自定义组件,小程序的页面也可以视为自定义组件,因此页面也可以使用Component方法(不使用Page方法)进行构建,从而实现复杂的页面逻辑开发。
使用Component方法来构建页面,那么在页面中享受使用Store中的数据,使用方式和组件的使用方式是一样的
js
// index/index.js
import { ComponentWithStore } from 'mobx-miniprogram-bindings'
import { numStore } from '../../stores/numstore'
ComponentWithStore({
data: {
someData: '...'
},
storeBindings: {
store: numStore,
fields: ['numA', 'numB', 'sum'],
actions: ['update']
}
})
(2)方式二
如果不想使用Component方法构造页面,这时候需要使用mobx-miniprogram-bindings
提供的BehaviorWithStore
方法和Store
建立关联。
小程序的behavior方法是一种代码复用的方式,可以将一些通用的逻辑和方法提取出来,然后在多个组件中复用,从而减少代码冗余,提高代码的可维护性,在页面中也可以使用behavior配置项。
使用方式如下:
- 新建
behavior
文件,从mobx-miniprogram-bindings
库中导入BehaviorWithStore
方法 - 在
BehaviorWithStore
方法中配置storeBindings
配置项从Store
中映射数据和方法 - 在
Page
方法中导入创建的behavior
,然后配置behavior
属性,并使用导入的behavior
第一步 在页面文件夹中新建behavior.js
第二步 在behavior.js中从mobx-miniprogram-bindings
中导入BehabviorWithStore
方法
第三步 使用BehabviorWithStore
方法,并向外暴露该方法
第四步 导入当前页面需要使用的Store对象
第五步 配置当前页面需要与哪些Store相关联
第六步 在页面.js中导入behavior对象
第七步 使用behaviors配置项注册提取的behavior
第八步 使用
5、fields、actions对象写法
fields、actions有两种写法:数组或者对象
fields对象写法:
- 映射形式:需要指定data中哪些字段来源于store,以及在store中的名字是什么。
属性是可以自定义的,如果对属性进行了自定义,模板中需要使用自定义以后的属性
javascript
fields:{
// 第一种映射形式写法:需要指定data中哪些字段来源于store,以及在store中的名字是什么
numA:'numA',
numB:'numB',
sum:'sum'
},
- 函数形式:
- key:需要指定data中哪些字段来源于store,、
- value是一个函数,该函数内部需要返回对应store数据的值
javascript
fields:{
// 第二种函数形式写法:key:需要指定data中哪些字段来源于store,value是一个函数,该函数内部需要返回对应store数据的值
numA:()=> numStore.numA,
numB:()=> numStore.numB,
sum:()=> numStore.sum
},
actions对象写法 :
只有映射形式的写法:指定模板中使用的哪些方法来源于store,并且在store中的名字是什么。同样的属性名也可以自定义,但是模板中也要相应发生变化
javascript
actions:{
update: 'update'
}
6、绑定多个store与命名空间
在实际开发中,一个页面或组件可能会绑定多个Store
,这时候我们可以将storeBindings
改造成数组。数组每一项就是一个个要绑定的Store
如果多个Store
中存在相同的数据,显示会出现异常。还可以通过namespace
属性给当前Store
开启命名空间,在开启命名空间以后,访问数据的时候,需要加上namespace
的名字才可以。
解决多个store中存在相同数据显示异常的办法:
- 第一种解决方法:利用对象写法,指定不同的属性名

- 第二种解决方案:添加命名空间,解决数据存在冲突的问题,方法冲突还需另外使用对象写法进行解决。

在添加命名空间以后,如果需要使用数据,需要加上命名空间进行使用
7、miniprogram-computed计算属性和监听器
小程序框架没有提供计算属性相关的api,但是官方为开发者提供了扩展工具库miniprogram-computed。
该工具库提供了两个功能:
- 计算属性
computed
- 监听器
watch
(1)computed计算属性
如果需要在组件中使用计算属性功能,需要从miniprogram-computed
库中导入ComponentWithComputed
方法。
在使用时:需要将 Component
方法替换成 ComponentWithComputed
方法,原本组件配置项也需要写到该方法中。
安装miniprogram-computed,在安装后点击构建npm
,进行本地构建
shell
npm install miniprogram-computed
第一步 在组件.js中从miniprogram-computed
库中导入ComponentWithComputed
方法
第二步 用ComponentWithComputed方法替换Component方法
第三步 定义计算属性
注意事项:
- 计算属性方法内部必须要有返回值
- 计算属性内部不能使用
this
来获取data中的数据,如果想要获取data中的数据需要使用形参 - 计算属性具有缓存特性,只执行一次,后续在使用的时候,只要依赖的数据没有发生改变,返回的始终是第一次执行的结果,如果依赖的数据发生改变,计算属性救护重新执行
(2)watch监听器
在使用时:需要将 Component
方法替换成 ComponentWithComputed
方法,原本组件配置项也需要写到该方法中。
第一步 在组件.js中从miniprogram-computed
库中导入ComponentWithComputed
方法
第二步 用ComponentWithComputed方法替换Component方法
第三步 定义监听器
watch同时监听多个数据
8、mobx和computed结合使用
两个框架扩展提供的 ComponentWithStore
与 ComponentWithComputed
方法无法结合使用。
如果需要在一个组件中既想使用 mobx-miniprogram-bindings
又想使用 miniprogram-computed
解决方案是:
-
使用旧版
API
-
自定义组件仍然使用
Component
方法构建组件,将两个扩展依赖包的使用全部改为旧版API
-
-
使用兼容写法
-
即要么使用
ComponentWithStore
方法构建组件,要么使用ComponentWithComputed
方法构建组件 -
如果使用了
ComponentWithStore
方法构建组件,计算属性写法使用旧版API
-
如果使用了
ComponentWithComputed
方法构建组件,Mobx写法使用旧版API
-
(1)ComponentWithStore+计算属性使用旧版api
注意:storeBindings配置项fields和actions不要忘记添加s
javascript
// components/custom03/custom03.js
import {ComponentWithStore} from 'mobx-miniprogram-bindings'
import {numStore} from '../../stores/numstore'
// 如果使用ComponentWithStore方法构造组件,计算属性扩展库需要使用旧版API
// 这是导入计算属性behavior
const computedBehavior = require('miniprogram-computed').behavior
ComponentWithStore({
// 注册behaviors
behaviors:[computedBehavior],
computed:{
total(data){
return data.a+data.b
}
},
watch:{
'a,b':function(a,b){
console.log(a,b);
}
},
data:{
a: 1,
b: 2
},
storeBindings:{
store:numStore,
fields:['numA','numB','sum'],
actions:['update']
},
methods:{
updateData(){
this.setData({
a: this.data.a + 1,
b: this.data.b + 1
})
}
}
})
html
<!--components/custom03/custom03.wxml-->
<view>{{numA}}+{{numB}}={{sum}}</view>
<view>{{total}}</view>
<button type="warn" plain bindtap="update">更新Store数据</button>
<button type="warn" plain bindtap="updateData">更新数据</button>
(2)ComponentWithComputed+mobx使用旧版api
js
// components/custom04/custom04.js
import {ComponentWithComputed} from 'miniprogram-computed'
import {numStore} from '../../stores/numstore'
// 目前组件使用的是ComponentWithComputed进行构建
// 如果想和store对象进行绑定,mobx需要使用旧版api
// 导入mobx的behavior
import { storeBindingsBehavior } from 'mobx-miniprogram-bindings'
ComponentWithComputed({
behaviors: [storeBindingsBehavior], // 添加这个 behavior(注册)
storeBindings:{
store:numStore,
fields:['numA','numB','sum'],
actions:['update']
},
computed:{
total(data){
return data.a+data.b
}
},
watch:{
'a,b':function(a,b){
console.log(a,b);
}
},
data:{
a: 1,
b: 2
},
methods:{
updateData(){
this.setData({
a: this.data.a + 1,
b: this.data.b + 1
})
}
}
})
九、用户登录
1、什么是token
Token
是服务器生成的一串字符串,用作客户端发起请求的一个身份令牌。当第一次登录成功后,服务器生成一个 Token
便将此 Token
返回给客户端,客户端在接收到 Token
以后,会使用某种方式将 Token
保存到本地。以后客户端发起请求,只需要在请求头上带上这个 Token
,服务器通过验证 Token
来确认用户的身份,而无需再次带上用户名和密码。
Token的具体流程
- 客户端向服务器发起登录请求,服务端验证用户名与密码
- 验证成功后,服务端会签发一个
Token
,并将Token
发送到客户端 - 客户端收到
token
以后,将其存储起来,比如放在localStorage
、sessionStorage
中 - 客户端每次向服务器请求资源的时候需要带着服务端签发的
Token
,服务端收到请求,然后去验证客户端请求里面带着的Token
,如果验证成功,就向客户端返回请求的数据
2、小程序登录流程介绍
业务介绍:
传统的登录功能,需要用户先注册,注册完成以后,使用注册的账号、密码进行登录。
小程序的登录操作则比较简单,小程序可以通过微信提供的登录能力,便捷地获取微信提供的用户身份标识进行登录。
免去了注册和输入账号密码的步骤,从而提高了用户体验。

3、实现小程序登录功能