慕尚花坊项目笔记

慕尚花坊项目笔记

一、项目初始化

重置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

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

  2. 然后配置 project.config.json 的 setting.packNpmManually 为 true,开启自定义 node_modules 和 miniprogram_npm 位置的构建 npm 方式

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

    • 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. 调用该方法时,传递对象作为参数

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

    • 从对象中包含 title、icon、duration、mask 参数,并给参数设置默认值

  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 个快捷方法,分别是 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),都会执行响应拦截器

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

封装需求:

  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. 使用 async 和 await 方式
  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-miniprogram 和 mobx-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-miniprogram, mobx-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结合使用

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

如果需要在一个组件中既想使用 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 以后,将其存储起来,比如放在 localStorage 、sessionStorage 中
  4. 客户端每次向服务器请求资源的时候需要带着服务端签发的 Token,服务端收到请求,然后去验证客户端请求里面带着的 Token ,如果验证成功,就向客户端返回请求的数据

2、小程序登录流程介绍

业务介绍:

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

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

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

3、实现小程序登录功能



相关推荐
启观川6 小时前
数据结构与算法 -第 3 章 常用算法-动态规划
数据结构·笔记·python·算法
一木 之林6 小时前
Dify学习笔记 01 · 平台入门与实战:部署、五类应用、RAG与MCP(第1-16集)
人工智能·笔记·学习·计算机视觉
weixin_448119947 小时前
Datawhale Easy Data × AI:构建知识与记忆驱动的 Agent笔记8
人工智能·笔记·react.js
陈年老古董7 小时前
Hive 函数学习笔记(下):窗口函数与经典案例
hive·笔记·学习
yi0118 小时前
LeetCode 643. 子数组最大平均数 I|从暴力枚举到滑动窗口
人工智能·笔记·python·算法·leetcode·滑动窗口
罗西的思考8 小时前
[Agent Memory / 强化学习] MemPO源码学习笔记 — (6)— 奖励机制
人工智能·笔记·学习
拾饵9429 小时前
第六周第二个项目笔记:FinInsRAG 项目学习笔记(零基础版)以及简历书写
笔记·学习
han68899 小时前
selenium之实战
笔记·python·selenium·测试工具·自动化
Lost_the_wind9 小时前
MySQL 学习笔记三
笔记·学习·mysql
北京海得康10 小时前
Atebrioz(zilurgisertib):靶向ALK2通路,FOP超罕见病全新口服治疗方案
笔记