第五周的前端培训

小程序开发实战

框架介绍

uni-app 是一个使用 Vue.jsopen in new window 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝)、快应用等多个平台。

学会阅读官方文档

如何快速的学习一个新的技术栈?

首先一点是要清楚我们需要这个技术栈为我们解决什么问题:

1.页面渲染和数据渲染

2.页面跳转

3.交互逻辑

4.处理业务

目录介绍

公司uni-app模板

|---- dist                              build或者开发环境非h5环境下运行,编译后的各平台代码存放目录

|---- public                            内有一个 index.html,是h5页面模板,用于项目生成 html 代码

|---- src

|      |---- assets                     存放css、less、scss、fonts等资源

|      |       |---- css

|      |       |---- fonts

|      |      └---- scss

|      |---- components            组件目录,存放各种可复用组件

|      |---- pages                     业务页面文件存放的目录,最好按模块分好文件夹

|      |---- static                      存放引用静态资源(如:图片、音频、视频等)的目录,注意:静态资源只能存放于此

|      |       |---- app-plus         按环境区分资源,app环境

|      |       |---- h5                 按环境区分资源,h5环境

|      |       |---- mp-weixin          按环境区分资源,微信小程序环境

|      |      └---- platforms          按环境区分资源,全环境

|      |---- utils                      公共包存放目录,如共用的 ajax.js

|      |       └---- request.js         公共ajax请求方法,可在方法内加入ajax请求传入的公共参数,或者错误信息的全局处理等

|      |---- APP.vue                    应用配置,用来配置App全局样式以及监听

|      |---- config.js                  配置文件,用来配置小程序请求域名,也可用来存放一些公用的配置信息

|      |---- main.js                    Vue初始化入口文件

|      |---- manifest.json              配置应用名称、appid、logo、版本等打包信息,或h5端开发环境的proxy代理

|      |---- pages.json                 配置页面路由、导航条、选项卡等页面类信息

|      └---- uni.scss                   uni.scss 公共样式, 可以直接在页面的scss中使用变量无需引入该文件(暂时没用到)

|---- .env                              公用的环境配置,在所有的环境中被载入

|---- .env.h5-prod                      h5生产环境配置

|---- .env.h5-dev                       h5开发环境配置

|---- .env.weixin                       小程序开发环境配置

|---- .env.weixin-prod                  小程序生产环境配置

|---- .env.weixin-dev                   小程序开发环境配置

|---- .eslintrc.js                      eslint 配置文件

|---- .gitignore                        git 忽略列表

|---- package.json                      项目依赖 npm 包,启动指令

|---- README.md                         项目文档以及说明

|---- vue.config.js                     vue-cli4配置文件

重要配置文件

manifest.jsonopen in new window

pages.jsonopen in new window

页面渲染

跨端兼容-条件编译

写法:以 #ifdef 或 #ifndef 加 %PLATFORM% 开头,以 #endif 结尾。

  • #ifdef:if defined 仅在某平台存在

  • #ifndef:if not defined 除了某平台均存在

  • %PLATFORM%:平台名称

条件编译写法说明
#ifdef APP-PLUS
需条件编译的代码
#endif
仅出现在 App 平台下的代码
#ifndef H5
需条件编译的代码
#endif
除了 H5 平台,其它平台均存在的代码
#ifdef H5 || MP-WEIXIN
需条件编译的代码
#endif`
在 H5 平台或微信小程序平台存在的代码(这里只有||,不可能出现&&,因为没有交集)

%PLATFORM% 可取值如下:

生效条件
VUE3HBuilderX 3.2.0+ 详情open in new window
APP-PLUSApp
APP-PLUS-NVUE或APP-NVUEApp nvue
H5H5
MP-WEIXIN微信小程序
MP-ALIPAY支付宝小程序
MP-BAIDU百度小程序
MP-TOUTIAO字节跳动小程序
MP-LARK飞书小程序
MP-QQQQ小程序
MP-KUAISHOU快手小程序
MP-360360小程序
MP微信小程序/支付宝小程序/百度小程序/字节跳动小程序/飞书小程序/QQ小程序/360小程序
QUICKAPP-WEBVIEW快应用通用(包含联盟、华为)
QUICKAPP-WEBVIEW-UNION快应用联盟
QUICKAPP-WEBVIEW-HUAWEI快应用华为

支持的文件

  • .vue

  • .js

  • .css

  • pages.json

  • 各预编译语言文件,如:.scss、.less、.stylus、.ts、.pug

官方文档open in new window

组件

和vue的写法一致,需要注意组件文件存放规则,公共组件放在components文件夹下,私有组件放在页面文件夹下的components文件下

官方文档open in new window

⚠️这里有一个坑点就是跨端应用不要使用动态组件,因为微信小程序暂时不支持动态组件

生命周期

有三种生命周期,分别是应用生命周期、页面生命周期、组件生命周期

应用生命周期:全局的生命周期,有特定时机触发,也有监听触发。只在App.vue文件里面有效

页面生命周期:有一套区别于vue的页面生命周期,有特定时机触发,也有集合用户交互、不同端的监听触发

组件生命周期:和vue一样

https://uniapp.dcloud.io/collocation/frame/lifecycleopen in new window

页面跳转

框架以栈的形式管理当前所有页面, 当发生路由切换的时候,页面栈的表现如下:

路由方式页面栈表现触发时机
初始化新页面入栈uni-app 打开的第一个页面
打开新页面新页面入栈调用 API uni.navigateToopen in new window 、使用组件 <navigator open-type="navigate"/>
页面重定向当前页面出栈,新页面入栈调用 API uni.redirectToopen in new window 、使用组件 <navigator open-type="redirectTo"/>
页面返回页面不断出栈,直到目标返回页调用 API uni.navigateBack 、使用组件 <navigator open-type="navigateBack"/> 、用户按左上角返回按钮、安卓用户点击物理back按键
Tab 切换页面全部出栈,只留下新的 Tab 页面调用 API uni.switchTab 、使用组件 <navigator open-type="switchTab"/> 、用户切换 Tab
重加载页面全部出栈,只留下新的页面调用 API uni.reLaunch 、使用组件 <navigator open-type="reLaunch"/>

路由跳转open in new window

交互逻辑

事件映射表

事件映射表open in new window

处理业务

封装工具/方法

请求方法

基于Promise的uni.request、uni.login的再次封装,并添加其他业务处理,例如缓存特殊的数据(例如记录登录状态)、取消请求处理等

requestopen in new window

import store from '@/store'

export const login = () => {
  return new Promise((resolve, reject) => {
    uni.login({
      success(res) {
        resolve(res)
      },
      fail(err) {
        reject(err)
      },
    })
  })
}
class CancelController {
  constructor() {
    this.requestTaskMap = {}
  }

  // 缓存上一次的请求
  push(uniqueKey, requestTask) {
    if (!this.requestTaskMap[uniqueKey]) {
      this.requestTaskMap[uniqueKey] = []
    }
    this.requestTaskMap[uniqueKey].push(requestTask)
  }

  // 终止请求
  cancel(uniqueKey) {
    if (!this.requestTaskMap[uniqueKey]) return true
    this.requestTaskMap[uniqueKey].forEach(requestTask => {
      requestTask.abort()
    })
  }

  start(url, method, requestTask) {
    const uniqueKey = url + method
    this.cancel(uniqueKey)
    this.push(uniqueKey, requestTask)
    return requestTask
  }
}

const cancelController = new CancelController()

class RetryController {
  constructor(request, options) {
    if (typeof request !== 'function') throw new Error('request参数仅支持函数类型')
    if (!options) throw new Error('options参数不允许为空')
    this.request = request
    this.options = options
  }

  async tryRequest() {
    try {
      return await this.request(this.options)
    } catch (e) {
      return this.start(e)
    }
  }

  // 网络请求超时,发起重试
  // 默认重试3次
  async start(response) {
    if (response.errMsg !== 'request:fail timeout') throw response

    if (!this.options.config) {
      this.options.config = { retry: 3 }
    }
    if (this.options.config.retry === undefined) {
      this.options.config.retry = 3
    }
    if (this.options.config.retry > 0) {
      this.options.config.retry--
      return this.tryRequest()
    }
    throw response
  }
}

export const request = (options) => {
  let { url, data, method, header, timeout, dataType, responseType, complete, config, isNotJoinDomain = false } = options
  // 超时时间,单位 ms,超时时间默认为3s
  timeout = timeout || parseInt(process.env.VUE_APP_TIMEOUT)
  // 如果设为 json,会尝试对返回的数据做一次 JSON.parse
  dataType = dataType || 'json'
  // 设置响应的数据类型。合法值:text、arraybuffer
  responseType = responseType || 'text'
  // 有效值: GET POST PUT DELETE,详情见https://uniapp.dcloud.io/api/request/request?id=request
  method = method || 'GET'
  // 设置请求的 header,header 中不能设置 Referer。
  header = header || { 'Content-Type': 'application/json;charset=utf-8;' }
  if (!url) throw new Error('url不允许为空')
  // isNotJoinDomain 是否不需要拼接域名
  if (!url.startsWith('http') && !url.startsWith('//') && !isNotJoinDomain) {
    url = url.startsWith('/') ? url : `/${url}`
    url = `${process.env.VUE_APP_DOMAIN}${url}`
  }
  console.info('请求参数', url, data)
  return new Promise((resolve, reject) => {
    const requestTask = uni.request({
      url,
      data,
      method,
      header,
      timeout,
      dataType,
      responseType,
      success({ data, statusCode, header, cookies }) {
        console.log('success', data)
        // eslint-disable-next-line eqeqeq
        if (!data || data.code != 200) return reject(data)
        resolve(data)
      },
      fail(res) {
        console.log('fail ', res)
        reject(res)
      },
      complete(res) {
        complete && complete(res)
      },
    })
    // 是否配置了cancel参数,即取消上一次未完成的请求
    if (config && config.cancel) {
      cancelController.start(url, method, requestTask)
    }

  })
}

const showLoading = (ifHideLoading) => {
  !ifHideLoading && uni.showLoading({
    title: '加载中'
  })
}

const hideLoading = () => {
  uni.hideLoading()
}

export const getOpenId = async () => {
  const cacheOpenId = store.getters.openId
  console.log('cacheOpenId', cacheOpenId)
  if (cacheOpenId) return cacheOpenId

  const { code } = await login()
  const res = await request({
    ifHideLoading: true,
    // url: '/survey/minimall/wechat/get-openid-by-jscode.do',
    url: '/survey/minimall/wechat/get-userInfo-by-jscode.do',
    data: {
      jsCode: code,
    },
  })
  const openId = res.result.openId

  console.log('store', store)
  store.commit('refreshState', res.result)
  return openId
}

export const post = async ({ url, data, timeout, isNotJoinDomain = false, ifHideLoading }) => {
  showLoading(ifHideLoading)
  const options = {
    method: 'POST',
    url,
    data,
    timeout,
    isNotJoinDomain
  }
  console.log('POST', options)
  try {
    const res = await request(options)
    hideLoading()
    return res
  } catch (e) {
    hideLoading()
    throw e
  }

}

export const get = async ({ url, data, timeout, ifHideLoading }) => {
  showLoading(ifHideLoading)
  const options = {
    url,
    timeout,
    data,
    config: {
      // 取消上次未完成的请求
      cancel: true,
      // 默认重试3次
      // retry: 3
    },
  }
  // #ifndef H5
  if (data && typeof (data.openId) !== 'undefined') {
    data.openId = await getOpenId()
  }
  // #endif
  let res = null
  try {
    res = await request(options)
  } catch (e) {
    // 默认重试3次
    const retryController = new RetryController(request, options)
    res = await retryController.start(e)
  }
  hideLoading()
  return res
}

export const getCommon = async ({ url, data, header, timeout, isNotJoinDomain = false, ifHideLoading }) => {
  showLoading(ifHideLoading)
  let res = null
  try {
    const options = {
      url,
      data,
      header,
      timeout,
      isNotJoinDomain
    }
    res = await request(options)
    hideLoading()
    return res
  } catch (error) {
    hideLoading()
    return Promise.reject(error)
  }
}

storage.js 缓存方法封装

// 参考:https://uniapp.dcloud.io/api/storage/storage?id=setstorage

// 处理从缓存提取出来的数据
function handleStr(objectStr) {
  let returnObj = null

  if (objectStr.indexOf('obj-') === 0) {
    returnObj = JSON.parse(objectStr.slice(4)) || {}
  } else if (objectStr.indexOf('str-') === 0) {
    returnObj = objectStr.slice(4) || ''
  }
  return returnObj
}

// 处理存入缓存的数据
function handleData(data) {
  let value = ''
  if (typeof (data) === 'object') {
    value = 'obj-' + JSON.stringify(data)
  } else {
    value = 'str-' + data
  }
  return value
}

// 存入缓存
export const setStorage = (key, data) => {
  const value = handleData(data)
  try {
    uni.setStorageSync(key, value)
  } catch (e) {
    // error
    console.error(`设置缓存${key}出错了`, e)
  }
}

// 读取缓存
export const getStorage = (key) => {
  try {
    const value = uni.getStorageSync(key)
    return handleStr(value)
  } catch (e) {
    // error
    console.error(`获取缓存${key}出错了`, e)
  }
}

// 删除缓存
export const removeStorage = (key) => {
  try {
    uni.removeStorageSync(key)
  } catch (e) {
    // error
    console.error(`删除缓存${key}出错了`, e)
  }
}

// 清除所有缓存
export const clearStorage = () => {
  try {
    uni.clearStorageSync()
  } catch (e) {
    // error
    console.error('清空缓存出错了', e)
  }
}

util.js

存放其他公共方法,例如时间格式转换等

拓展

根据需求修改框架

information-pages小程序框架确定文档open in new window

遇到的坑点

遇到的坑点open in new window

社区求助

https://ask.dcloud.net.cn/explore/category-12open in new window