慕尚花坊项目笔记

慕尚花坊项目笔记

一、项目初始化

重置app.js中的代码

删除app.jsonpages下的"pages/logs/logs"路径,同时删除pages.logs文件夹

删除app.jsonpages下的"renderOptions"以及"componentFrameword"字段

重置app.wxss中的代码

删除components中的自定义组件

重置pages/index文件夹下的index.jsindex.wxssindex.html以及index.json文件

更新utilsutils.js的文件名为formatTime.js

二、自定义构建npm、集成sass

1、自定义构建npm

  1. 首先在project.config.json中配置miniprogramRoot,指定小程序源码的目录

  2. 然后配置 project.config.jsonsetting.packNpmManuallytrue,开启自定义 node_modules 和 miniprogram_npm 位置的构建 npm 方式

  3. 最后配置 project.config.json 的 setting.packNpmRelationList 项,指定 packageJsonPathminiprogramNpmDistDir 的位置

    • packageJsonPath 表示 node_modules 源对应的 package.json相对地址
    • miniprogramNpmDistDir 表示 node_modules 的构建结果目标地址
  4. 创建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开发

  1. 在项目的根目录下创建 .vscode 文件夹,注意:文件夹名字前面带 .

  2. .vscode 文件夹下,创建 settings.json,用来对安装的插件属性进行设置,具体属性设置从下面复制即可

    注意:.vscode 文件夹下的 settings.json 文件只对当前一个项目生效

  3. 在【项目的根目录】下创建 .prettierrc 文件,进行 Prettier 代码规则的配置,规则从下面复制

  4. 为了让 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、消息提示模块封装

封装思路:

  1. 创建一个 toast 方法对 wx.showToast() 方法进行封装

  2. 调用该方法时,传递对象作为参数

    • 如果没有传递任何参数,设置一个空对象 {} 作为默认参数

    • 从对象中包含 titleicondurationmask 参数,并给参数设置默认值

  3. 在需要显示弹出框的时候调用 toast 方法,并传入相关的参数,有两种参数方式:

    • 不传递参数,使用默认参值
    • 传入部分参数,覆盖默认的参数

调用方式:

  1. 模块化的方式导入使用

    js 复制代码
    import { toast } from './extendApi'
    
    toast()
    toast({ title: '数据加载失败....', mask: true })
  2. 将封装的模块挂载到 wx 全局对象身上

    js 复制代码
    wx.toast()
    wx.toast({ title: '数据加载失败....', mask: true })

模块化方式导入使用

挂载到wx全局对象身上

2、模态对话框封装

封装思路:

  1. wx.showModal() 方法进行封装, 封装后的新方法叫 modal
  2. 调用该方法时,传递对象作为参数,对象的参数同 wx.showModal() 参数一致
  3. 封装的 modal 方法的内部通过 Promise 返回用户执行的操作(确定和取消,都通过 resolve 返回)
  4. 在需要显示模态对话框的时候调用 modal 方法,并传入相关的参数,有三种调用方式:
    • 不传递参数,使用默认参数
    • 传递参数,覆盖默认的参数

调用方式:

新封装的本地存储模块,我们依然希望有两种调用的方式:

  1. 模块化的方式导入使用
  2. 将封装的模块挂载到 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'
})

从而得出结论:请求参数的设置有三种方式:

  1. 默认参数:在 WxRequest 类中添加 defaults 实例属性来设置默认值
  2. 实例化时参数:在对 WxRequest 类进行实例化时传入相关的参数,需要在 constructor 构造函数形参进行接收
  3. 调用实例方法时传入请求参数

request.js

实例化时设置请求参数(被constructor形参接收)调用实例方法时设置请求参数(被request实例方法的形参options接收)

4、请求封装-封装请求快捷方法

在request()基础上封装一些快捷方法,简化request的调用

需要封装 4 个快捷方法,分别是 getdeletepostput,他们的调用方式如下:

js 复制代码
instance.get('请求地址', '请求参数', '请求配置')
instance.delete('请求地址', '请求参数', '请求配置')
instance.post('请求地址', '请求参数', '请求配置')
instance.put('请求地址', '请求参数', '请求配置')

这 4 个请求方法,都是通过实例化的方式进行调用,所以需要 Request 类中暴露出来 getdeletepostput 方法。每个方法接收三个参数,分别是:接口地址、请求参数以及其他参数。

这 4 个快捷方法,本质上其实还是调用 request 方法,我们只要在方法内部组织好参数,调用 request 发送请求即可

与request实例方法同级

5、wx.request注意事项

在使用 wx.request 发送网络请求时,只要成功接收到服务器返回,无论statusCode是多少,都会进入 success 回调。开发者根据业务逻辑对返回值进行判断。

一般只有网络出现异常、请求超时等时候,才会走 fail 回调

6、请求封装-定义请求/响应拦截器

为了方便统一处理请求参数以及服务器响应结果,为WXRequest添加拦截器功能,拦截器包括请求拦截器响应拦截器

  • 请求拦截器本质上是在请求之前调用的函数,用来对请求参数进行新增和修改
  • 响应拦截器本质上是在响应之后调用的函数,用来对相响应数据做点什么

不管成功响应还是失败相应,都会执行响应拦截器

可以在WXRequest类内部定义interceptors实例属性,属性中需要包含request以及response方法

调用请求拦截器

调用响应拦截器

7、请求封装-完善请求/响应拦截器

目前不管请求成功 (success),还是请求失败(fail),都会执行响应拦截器

那么怎么判断是请求成功,还是请求失败呢 ?

封装需求:

  1. 如果请求成功,将响应成功的数据传递给响应拦截器,同时在传递的数据中新增 isSuccess: true 字段,表示请求成功
  2. 如果请求失败,将响应失败的数据传递给响应拦截器,同时在传递的数据中新增 isSuccess: false 字段,表示请求失败

在实例调用的响应拦截中,根据传递的数据进行以下的处理:

  • 如果isSuccess: true 表示服务器响应了结果,我们可以将服务器响应的数据简化以后进行返回
  • 如果isSuccess: false 表示是网络超时或其他网络问题,提示 网络异常,同时将返回即可


8、结合项目使用请求/响应拦截器

使用请求拦截器:

在发送请求时,购物车列表、收货地址、更新头像等接口,都需要进行权限验证,因此我们需要在请求拦截器中判断本地是否存在访问令牌 token ,如果存在就需要在请求头中添加 token 字段。

使用响应拦截器:

在使用 wx.request 发送网络请求时。只要成功接收到服务器返回,无论statusCode是多少,都会进入 success 回调。

因此开发者根据业务逻辑对返回值进行判断。

后端返回的业务状态码如下:

  1. 业务状态码 === 200, 说明接口请求成功,服务器成功返回了数据
  2. 业务状态码 === 208, 说明没有 token 或者 token 过期失效,需要登录或者重新登录
  3. 业务状态码 === 其他,说明请求或者响应出现了异常
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、请求封装-添加并发请求

前端并发请求是指在前端页面同时向后端发起多个请求的情况。当一个页面需要请求多个接口获取数据时,为了提高页面的加载速度和用户体验,可以同时发起多个请求,这些请求之间就是并发的关系。

我们通过两种方式演示发起多个请求:

  1. 使用 asyncawait 方式
  2. 使用 Promise.all() 方式

async await方式

Promise.all()方法

在request.js中封装promise.all方法,在test.js中调用all()实例方法

10、添加loading

在封装时添加 loading 效果,从而提高用户使用体验

  1. 在请求发送之前,需要通过 wx.showLoading 展示 loading 效果

  2. 当服务器响应数据以后,需要调用 wx.hideLoading 隐藏 loading 效果

要不要把loading 添加到 WxRequest 内部 ?

  1. 在类内部进行添加,方便多个项目直接使用类提供的 loading 效果,也方便统一优化 wx.showLoading 使用体验。
    但是不方便自己来进行 loading 个性化定制。
  2. 如果想自己来控制 loading 效果,带来更丰富的交互体验,就不需要将 loading 封装到类内部,但是需要开发者自己来优化 wx.showLoading 使用体验,每个项目都要写一份。

添加loading效果

11、完善loading效果

目前在发送请求时,请求发送之前会展示 loading,响应以后会隐藏 loading

但是 loading 的展示和隐藏会存在以下问题:

  1. 每次请求都会执行 wx.showLoading(),但是页面中只会显示一个,后面的 loading会将前面的覆盖
  2. 同时发起多次请求,只要有一个请求成功响应就会调用 wx.hideLoading,导致其他请求还没完成, loading也会隐藏
  3. 请求过快 或 一个请求在另一个请求后立即触发,这时候会出现 loading 闪烁问题

我们通过 队列 的方式解决这三个问题:首先在类中新增一个实例属性 queue,初始值是一个空数组

  1. 发起请求之前,判断 queue 如果是空数组则显示 loading ,然后立即向queue新增请求标识
  2. complete 中每次请求成功结束,从 queue 中移除一个请求标识,queue 为空时隐藏 loading
  3. 为了解决网络请求过快产生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 显示。

  1. 类内部设置默认请求参数 isLoading 属性,默认值是 true,在类内部根据 isLoading 属性做判断即可



  1. 某个接口不需要显示 loading 效果,可以在发送请求的时候,可以新增请求配置 isLoading 设置为 false

  2. 整个项目都不需要显示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. 易于维护:一个文件就是一个模块,一个方法就是一个功能,清晰明了,查找方便
  2. 便于复用:哪里使用,哪里导入,可以在任何一个业务组件中导入需要的方法
  3. 团队合作:分工合作

六、项目首页

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 种小程序页面、组件间的数据通信方案,分别是:

  1. 数据绑定:properties
  2. 获取组件实例:this.selectComponent()
  3. 事件绑定:this.triggerEvent()
  4. 获取应用实例:getApp()
  5. 页面间通信:EventChannel
  6. 事件总线:pubsub-js

但是随着项目的业务逻辑越来越复杂,组件和页面间通信就会变的非常复杂。例如:有些状态需要在多个页面间进行同步使用,一个地方发生变更,所有使用的地方都需要发生改变,这时候如果使用前面的数据通信方案进行传递数据,给管理和维护将存在很大的问题。

为了方便进行页面、组件之间数据的传递,小程序官方提供了一个扩展工具库: mobx-miniprogram

介绍:
mobx-miniprogram 是针对微信小程序开发的一个简单、高效、轻量级状态管理库,它基于Mobx状态管理框架实现。

使用 mobx-miniprogram 定义管理的状态是响应式的,当状态一旦它改变,所有关联组件都会自动更新相对应的数据

通过该扩展工具库,开发者可以很方便地在小程序中全局共享的状态,并自动更新视图组件,从而提升小程序的开发效率

使用方法:

在使用 mobx-miniprogram 需要安装两个包:mobx-miniprogrammobx-miniprogram-bindings

  1. mobx-miniprogram 的作用:创建 Store 对象,用于存储应用的数据
  2. mobx-miniprogram-bindings 的作用:将状态和组件、页面进行绑定关联,从而在组件和页面中操作数据

安装:

shell 复制代码
npm install mobx-miniprogram mobx-miniprogram-bindings

mobx-miniprogram 官方文档
mobx-miniprogram-bindings 官方文档

2、mobx-miniprogram创建Store对象

创建 Store 对象需要使用 mobx-miniprogrammobx-miniprogram 三个核心概念:

  1. observable:用于创建一个被监测的对象,对象的属性就是应用的状态(state),这些状态会被转换成响应式数据。
  2. action:用于修改状态(state)的方法,需要使用 action 函数显式的声明创建。
  3. computed:根据已有状态(state)生成的新值。计算属性是一个方法,在方法前面必须加上 get 修饰符

mobx-miniprogram 使用步骤:

  1. 在项目的根目录下创建 stores 文件夹,然后在该文件夹下新建 numstore.js

  2. /store/numstore.js 导入 observable action 方法

    js 复制代码
    import { observable, action } from 'mobx-miniprogram'
    • observable用于创建一个被监测的对象,对象的属性是应用状态,状态会被自动转换为响应式数据
    • action用来显示的定义action方法,action方法是用来修改、更新状态的
  3. 使用 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 配置项,配置项常用的属性有以下三个:

  1. store: 指定要绑定的 Store 对象
  2. fields: 指定需要绑定的 data 字段
  3. 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配置项。

使用方式如下:

  1. 新建 behavior 文件,从 mobx-miniprogram-bindings 库中导入 BehaviorWithStore 方法
  2. BehaviorWithStore 方法中配置 storeBindings 配置项从 Store 中映射数据和方法
  3. 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中存在相同数据显示异常的办法:

  1. 第一种解决方法:利用对象写法,指定不同的属性名
  1. 第二种解决方案:添加命名空间,解决数据存在冲突的问题,方法冲突还需另外使用对象写法进行解决。

在添加命名空间以后,如果需要使用数据,需要加上命名空间进行使用

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结合使用

两个框架扩展提供的 ComponentWithStoreComponentWithComputed 方法无法结合使用。

如果需要在一个组件中既想使用 mobx-miniprogram-bindings 又想使用 miniprogram-computed

解决方案是:

  1. 使用旧版 API

  2. 使用兼容写法

    • 即要么使用 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的具体流程

  1. 客户端向服务器发起登录请求,服务端验证用户名与密码
  2. 验证成功后,服务端会签发一个 Token,并将 Token 发送到客户端
  3. 客户端收到 token 以后,将其存储起来,比如放在 localStoragesessionStorage
  4. 客户端每次向服务器请求资源的时候需要带着服务端签发的 Token,服务端收到请求,然后去验证客户端请求里面带着的 Token ,如果验证成功,就向客户端返回请求的数据

2、小程序登录流程介绍

业务介绍:

传统的登录功能,需要用户先注册,注册完成以后,使用注册的账号、密码进行登录。

小程序的登录操作则比较简单,小程序可以通过微信提供的登录能力,便捷地获取微信提供的用户身份标识进行登录。

免去了注册和输入账号密码的步骤,从而提高了用户体验。

3、实现小程序登录功能



相关推荐
WarPigs4 小时前
游戏框架笔记
笔记·游戏·架构
金心靖晨5 小时前
redis汇总笔记
数据库·redis·笔记
遇见尚硅谷6 小时前
C语言:20250714笔记
c语言·开发语言·数据结构·笔记·算法
Norvyn_76 小时前
LeetCode|Day11|557. 反转字符串中的单词 III|Python刷题笔记
笔记·python·leetcode
逼子格6 小时前
权电阻网络DAC实现电压输出型数模转换Multisim电路仿真——硬件工程师笔记
笔记·嵌入式硬件·硬件工程·硬件工程师·adc·硬件工程师真题·权电阻网络dac
xd000027 小时前
ethers.js-5–和solidity的关系
笔记
星辰大海14128 小时前
AI Linux 运维笔记
运维·笔记
charlie1145141918 小时前
我的Qt八股文笔记2:Qt并发编程方案对比与QPointer,智能指针方案
笔记·qt·面试·刷题·并发编程·异步
柳鲲鹏9 小时前
git异常退出,应该是内存不足
笔记