慕尚花坊项目笔记
慕尚花坊项目笔记
一、项目初始化
重置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
{// 保存文件时是否自动格式化"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
{"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
方法,并传入相关的参数,有两种参数方式:- 不传递参数,使用默认参值
- 传入部分参数,覆盖默认的参数
调用方式:
-
模块化的方式导入使用
import { toast } from './extendApi'toast() toast({ title: '数据加载失败....', mask: true })
-
将封装的模块挂载到
wx
全局对象身上wx.toast() wx.toast({ title: '数据加载失败....', mask: true })
模块化方式导入使用
挂载到wx全局对象身上
2、模态对话框封装
封装思路:
- 对
wx.showModal()
方法进行封装, 封装后的新方法叫modal
- 调用该方法时,传递对象作为参数,对象的参数同
wx.showModal()
参数一致 - 封装的
modal
方法的内部通过Promise
返回用户执行的操作(确定和取消,都通过 resolve 返回) - 在需要显示模态对话框的时候调用
modal
方法,并传入相关的参数,有三种调用方式:- 不传递参数,使用默认参数
- 传递参数,覆盖默认的参数
调用方式:
新封装的本地存储模块,我们依然希望有两种调用的方式:
- 模块化的方式导入使用
- 将封装的模块挂载到
wx
全局对象身上
挂载到wx上且传入新的参数案例:
3、封装本地存储API
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);}
}
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+优化代码
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)}})})
}
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、请求封装-设置请求参数
在发起网络请求时,需要配置一些请求参数,其中有一些参数我们可以设置为默认参数,例如:请求方法、超时时长等,因此在封装时我们要定义一些默认的参数。
我们还要允许在进行实例化的时候,传入参数,对默认的参数进行修改
// 对 WxRequest 进行实例化
const instance = new WxRequest({baseURL: 'https://gmall-prod.atguigu.cn/mall-api', // 请求基准地址timeout: 10000 // 微信小程序 timeout 默认值为 60000
})
在通过实例调用request实例方法时也会传入相关的请求参数
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
,他们的调用方式如下:
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 过期失效,需要登录或者重新登录
- 业务状态码 === 其他,说明请求或者响应出现了异常
// 配置请求拦截器(会覆盖默认的请求拦截器)
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} = responseif(!isSuccess){wx.showToast({title:'网络异常请重试',icon:'error'})return response}// 判断服务器响应的业务状态码switch(data.code){case 200:return datacase 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
属性做判断即可
2. 某个接口不需要显示 loading
效果,可以在发送请求的时候,可以新增请求配置 isLoading
设置为 false
3. 整个项目都不需要显示loading
效果,可以在实例化的时候(http.js中),传入 isLoading
配置为 false
若设置整个项目都不需要loading效果,但是希望某一个接口用到默认loading效果,只要为那个接口新增请求配置isLoading:true
13、封装uploadFile
wx.uploadFile也是我们在开发中常用的一个api,用来将本地资源上传到服务器
wx.uploadFile({url: '', // 必填项,开发者服务器地址filePath: '', // 必填项,要上传文件资源的路径 (本地路径)name: '' // 必填项,文件对应的 key,开发者在服务端可以通过这个 key 获取文件的二进制内容
})
首先在 WxRequest
类内部创建 upload
实例方法,实例方法接收四个属性:
/**
* @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
// 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包发送请求
npm install mina-request
15、小程序设置环境变量
在实际开发中,不同的开发环境,调用的接口地址是不一样的。
例如:开发环境需要调用开发版的接口地址,生产环境需要调用正式版的接口地址
这时候,我们就可以使用小程序提供的 wx.getAccountInfoSync()
接口,用来获取当前账号信息,在账号信息中包含着 小程序 当前环境版本。
环境版本 | 合法值 |
---|---|
开发版 | develop |
体验版 | trial |
正式版 | release |
// 配置当前小程序项目的环境变量 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 目录下统一管理,然后按照模块功能来划分成对应的文件,在文件中将接口封装成一个个方法单独导出,例如:
这样做的有以下几点好处:
- 易于维护:一个文件就是一个模块,一个方法就是一个功能,清晰明了,查找方便
- 便于复用:哪里使用,哪里导入,可以在任何一个业务组件中导入需要的方法
- 团队合作:分工合作