第五周的前端培训
小程序开发实战
框架介绍
uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到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配置文件
重要配置文件
页面渲染
跨端兼容-条件编译
写法:以 #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% 可取值如下:
值 | 生效条件 |
---|---|
VUE3 | HBuilderX 3.2.0+ 详情 |
APP-PLUS | App |
APP-PLUS-NVUE或APP-NVUE | App nvue |
H5 | H5 |
MP-WEIXIN | 微信小程序 |
MP-ALIPAY | 支付宝小程序 |
MP-BAIDU | 百度小程序 |
MP-TOUTIAO | 字节跳动小程序 |
MP-LARK | 飞书小程序 |
MP-QQ | QQ小程序 |
MP-KUAISHOU | 快手小程序 |
MP-360 | 360小程序 |
MP | 微信小程序/支付宝小程序/百度小程序/字节跳动小程序/飞书小程序/QQ小程序/360小程序 |
QUICKAPP-WEBVIEW | 快应用通用(包含联盟、华为) |
QUICKAPP-WEBVIEW-UNION | 快应用联盟 |
QUICKAPP-WEBVIEW-HUAWEI | 快应用华为 |
支持的文件
.vue
.js
.css
pages.json
各预编译语言文件,如:.scss、.less、.stylus、.ts、.pug
组件
和vue的写法一致,需要注意组件文件存放规则,公共组件放在components文件夹下,私有组件放在页面文件夹下的components文件下
⚠️这里有一个坑点就是跨端应用不要使用动态组件,因为微信小程序暂时不支持动态组件
生命周期
有三种生命周期,分别是应用生命周期、页面生命周期、组件生命周期
应用生命周期:全局的生命周期,有特定时机触发,也有监听触发。只在App.vue文件里面有效
页面生命周期:有一套区别于vue的页面生命周期,有特定时机触发,也有集合用户交互、不同端的监听触发
组件生命周期:和vue一样
https://uniapp.dcloud.io/collocation/frame/lifecycle
页面跳转
框架以栈的形式管理当前所有页面, 当发生路由切换的时候,页面栈的表现如下:
路由方式 | 页面栈表现 | 触发时机 |
---|---|---|
初始化 | 新页面入栈 | uni-app 打开的第一个页面 |
打开新页面 | 新页面入栈 | 调用 API uni.navigateTo 、使用组件 <navigator open-type="navigate"/> |
页面重定向 | 当前页面出栈,新页面入栈 | 调用 API uni.redirectTo 、使用组件 <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"/> |
交互逻辑
事件映射表
处理业务
封装工具/方法
请求方法
基于Promise的uni.request、uni.login的再次封装,并添加其他业务处理,例如缓存特殊的数据(例如记录登录状态)、取消请求处理等
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
存放其他公共方法,例如时间格式转换等