最开始我们的前端也没有用uni-app的,因为自己做的一个独立的APP所以最开始使用的MUI+H5,因为自己开发了很多组件,所有的业务功能都放这个APP里面的,后来发现APP的体积越来越大撑不下了。所以就来到了uni-app的世界,可能主要为了节约成本,毕竟开源的东西,有问题自己也可以优化。
前面我写过一篇我们APP架构变化的文章 利用微服务做一个APP架构 也说明了这一点。随着业务体系越来越庞大,后端微服务,前端也跟着拆开所以选用了uni-app。uni-app即使在使用过程中,基础组件偶尔会给你留个坑,但是总体来说用着方便的。能一套代码打包多端,一定程度上可以解决成本。
主要解决我们几个业务场景
1、跨平台:我们的APP某个业务可能会和不通的第三方集成,或者放在国网平台,或者是自己独立的APP,基于前面这些集成起来,毫无疑问没有什么比发一个打包出来的H5静态页面集成更简单的了。
2、开发小程序:这可能是现在是这个框架运用最多的情况了,由于运用的人多积累了一定的经验以及封装了较多业务组件,下面就分享一下uni-app项目的整体架构、方法封装组件库选择以及注意事项。
常用开发软件HBuilderX:hx.dcloud.net.cn/Tutorial/in…
下面开始我们的搭建步骤:
创建项目
uni-app提供了两种创建项目的方式:
⚠️需要注意的是,一定要根据项目需求来选择项目的创建方式;如果只是单独的开发小程序或App,且开发环境单一,可以使用HBuilderX可视化工具创建。如果多端开发,以及同一套代码可能会打包生成多个小程序建议使用vue-cli进行创建,不然后期想搞自动化构建以及按指定条件进行编译比较痛苦。关于按条件编译,文章后面会有详细说明。
使用vue-cli安装和运行:
1.全局安装 vue-cli
bash代码解读复制代码npm install -g @vue/cli
2.创建 uni-app
bash代码解读复制代码vue create -p dcloudio/uni-preset-vue 项目名称
3.进入项目文件夹
bash 代码解读复制代码cd 项目名称
4.运行项目,如果是已微信小程序为主,可以在package.json中的命令改为:
json 代码解读复制代码"scripts": {
"serve": "npm run dev:mp-weixin"
}
然后执行
arduino代码解读复制代码npm run serve
复制代码
使用cli创建项目默认不带css预编译,需要手动安装一下,这里已sass为例:
css 代码解读复制代码npm i sass --save-dev
npm i sass-loader --save-dev
整体项目架构
通过HBuilderX或者vue-cli创建的项目,目录结构有稍许不同,但基本没什么差异,这里就按vue-cli创建的项目为例,整体架构配置如下:
arduino 代码解读复制代码├──dist 编译后的文件路径
├──package.json 配置项
├──src 核心内容
├──api 项目接口
├──components 全局公共组件
├──config 项目配置文件
├──pages 主包
├──static 全局静态资源
├──store vuex
├──mixins 全局混入
├──utils 公共方法
├──App.vue 应用配置,配置App全局样式以及监听
├──main.js Vue初始化入口文件
├──manifest.json 配置应用名称、appid等打包信息
├──pages.json 配置页面路由、导航条、选项卡等页面类信息
└──uni.scss 全局样式
封装方法
工欲善其事,必先利其器。在开发之前,我们可以把一些全局通用的方法进行封装,以及把uni-app提供的api进行二次封装,方便使用。全局的公共方法我们都会放到/src/utils文件夹下。
封装常用方法
下面这些方法都放在/src/utils/utils.js中,文章末尾会提供github链接方便查看。如果项目较大,建议把方法根据功能定义不同的js文件。
小程序Toast提示
javascript 代码解读复制代码/**
* 提示方法
* @param {String} title 提示文字
* @param {String} icon icon图片
* @param {Number} duration 提示时间
*/
export function toast(title, icon = 'none', duration = 1500) {
if(title) {
uni.showToast({
title,
icon,
duration
})
}
}
缓存操作(设置/获取/删除/清空)
javascript 代码解读复制代码/**
* 缓存操作
* @param {String} val
*/
export function setStorageSync(key, data) {
uni.setStorageSync(key, data)
}
export function getStorageSync(key) {
return uni.getStorageSync(key)
}
export function removeStorageSync(key) {
return uni.removeStorageSync(key)
}
export function clearStorageSync() {
return uni.clearStorageSync()
}
页面跳转
typescript 代码解读复制代码/**
* 页面跳转
* @param {'navigateTo' | 'redirectTo' | 'reLaunch' | 'switchTab' | 'navigateBack' | number } url 转跳路径
* @param {String} params 跳转时携带的参数
* @param {String} type 转跳方式
**/
export function useRouter(url, params = {}, type = 'navigateTo') {
try {
if (Object.keys(params).length) url = `${url}?data=${encodeURIComponent(JSON.stringify(params))}`
if (type === 'navigateBack') {
uni[type]({ delta: url })
} else {
uni[type]({ url })
}
} catch (error) {
console.error(error)
}
}
图片预览
javascript 代码解读复制代码/**
* 预览图片
* @param {Array} urls 图片链接
*/
export function previewImage(urls, itemList = ['发送给朋友', '保存图片', '收藏']) {
uni.previewImage({
urls,
longPressActions: {
itemList,
fail: function (error) {
console.error(error,'===previewImage')
}
}
})
}
图片下载
javascript 代码解读复制代码/**
* 保存图片到本地
* @param {String} filePath 图片临时路径
**/
export function saveImage(filePath) {
if (!filePath) return false
uni.saveImageToPhotosAlbum({
filePath,
success: (res) => {
toast('图片保存成功', 'success')
},
fail: (err) => {
if (err.errMsg === 'saveImageToPhotosAlbum:fail:auth denied' || err.errMsg === 'saveImageToPhotosAlbum:fail auth deny') {
uni.showModal({
title: '提示',
content: '需要您授权保存相册',
showCancel: false,
success: (modalSuccess) => {
uni.openSetting({
success(settingdata) {
if (settingdata.authSetting['scope.writePhotosAlbum']) {
uni.showModal({
title: '提示',
content: '获取权限成功,再次点击图片即可保存',
showCancel: false
})
} else {
uni.showModal({
title: '提示',
content: '获取权限失败,将无法保存到相册哦~',
showCancel: false
})
}
},
fail(failData) {
console.log('failData', failData)
}
})
}
})
}
}
})
}
更多函数就不在文章中展示了,已经放到/src/utils/utils,js里面,具体可以到 github 查看。
请求封装
为了减少在页面中的请求代码,所以我们要对uni-app提供的请求方式进行二次封装,在/src/utils文件夹下建立request.js,具体代码如下:
javascript 代码解读复制代码import {toast, clearStorageSync, getStorageSync, useRouter} from './utils'
import {BASE_URL} from '@/config/index'
const baseRequest = async (url, method, data, loading = true) =>{
header.token = getStorageSync('token') || ''
return new Promise((reslove, reject) => {
loading && uni.showLoading({title: 'loading'})
uni.request({
url: BASE_URL + url,
method: method || 'GET',
header: header,
timeout: 10000,
data: data || {},
success: (successData) => {
const res = successData.data
uni.hideLoading()
if(successData.statusCode == 200){
// 这里根据自己的业务逻辑去调整
if(res.resultCode == 'PA-G998'){
clearStorageSync()
useRouter('/pages/login/index', 'reLaunch')
}else{
reslove(res.data)
}
}else{
toast('网络连接失败,请稍后重试')
reject(res)
}
},
fail: (msg) => {
uni.hideLoading()
toast('网络连接失败,请稍后重试')
reject(msg)
}
})
})
}
const request = {};
['options', 'get', 'post', 'put', 'head', 'delete', 'trace', 'connect'].forEach((method) => {
request[method] = (api, data, loading) => baseRequest(api, method, data, loading)
})
export default request
请求封装好以后,我们在/src/api文件夹下按业务模块建立对应的api文件,拿获取用户信息接口举例子:
在/src/api文件夹下建立user.js,然后引入request.js
javascript 代码解读复制代码import request from '@/utils/request'
//个人信息
export const info = data => request.post('/v1/api/info', data)
在页面中直接使用:
javascript 代码解读复制代码import {info} from '@/api/user.js'
export default {
methods: {
async getUserinfo() {
let info = await info()
console.log('用户信息==', info)
}
}
}
自定义tabBar
写uni-app或者小程序基本避不开这个话题了,很多情况下,官方提供的tabBar方案并不能满足产品需求/ui 要求,官方也提供了自定义tabBar的方案,但此方案有很多弊端,比如:切换时候会tabBar会有明显的闪动。
版本切换
很多场景下,需要根据不同的环境去切换不同的请求域名、APPID等字段,这时候就需要通过环境变量来进行区分。下面案例我们就分为三个环境:开发环境(dev)、测试环境(test)、生产环境(prod)。
建立env文件
在项目根目录建立下面三个文件并写入内容(常量名要以 VUE 开头命名):
.env.dev(开发环境)
ini 代码解读复制代码VUE_APP_MODE=dev
VUE_APP_ID=wxbb53ae105735a06b
VUE_APP_BASE=https://www.baidu.dev.com
.env.test(测试环境)
ini 代码解读复制代码VUE_APP_MODE=test
VUE_APP_ID=wxbb53ae105735a06c
VUE_APP_BASE=https://www.baidu.test.com
.env.prod(生产环境)
ini 代码解读复制代码VUE_APP_MODE=wxbb53ae105735a06d
VUE_APP_ID=prod
VUE_APP_BASE=https://www.baidu.prod.com
修改package.json文件
json 代码解读复制代码"scripts": {
"dev:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch --mode dev",
"build:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch --mode prod"
},
然后执行
arduino代码解读复制代码npm run dev:mp-weixin
在/src/pages/index/index.vue下,打印:
arduino 代码解读复制代码onLoad() {
console.log(process.env.VUE_APP_MODE, '====VUE_APP_BASE')
console.log(process.env.VUE_APP_BASE, '====VUE_APP_BASE')
},
此时输出结果就是
ini 代码解读复制代码// dev ====VUE_APP_BASE// https://www.baidu.dev.com ====VUE_APP_BASE
动态修改 appid
如果同一套代码,需要打包生成多个小程序,就需要动态修改appid了;文章开头说过 appid 在/src/manifest.json文件中配置,但json文件又不能直接写变量,这时候就可以参考官方 提出的解决方案:建立vue.config.js文件,具体操作如下。
在根目录下建立vue.config.js文件写入以下内容:
javascript 代码解读复制代码// 读取 manifest.json ,修改后重新写入
const fs = require('fs')
const manifestPath = './src/manifest.json'
let Manifest = fs.readFileSync(manifestPath, { encoding: 'utf-8' })
function replaceManifest(path, value) {
const arr = path.split('.')
const len = arr.length
const lastItem = arr[len - 1]
let i = 0
let ManifestArr = Manifest.split(/\n/)
for (let index = 0; index < ManifestArr.length; index++) {
const item = ManifestArr[index]
if (new RegExp(`"${arr[i]}"`).test(item)) ++i
if (i === len) {
const hasComma = /,/.test(item)
ManifestArr[index] = item.replace(
new RegExp(`"${lastItem}"[\s\S]*:[\s\S]*`),
`"${lastItem}": ${value}${hasComma ? ',' : ''}`
)
break
}
}
Manifest = ManifestArr.join('\n')
}
// 读取环境变量内容
replaceManifest('mp-weixin.appid', `"${process.env.VUE_APP_ID}"`)
fs.writeFileSync(manifestPath, Manifest, {
flag: 'w'
})
如果是通过HBuilderX可视化工具创建的项目,则无法去自动根据环境去修改appid,只能去手动修改。
组件库
uni-app最受欢迎的可能就是插件市场了,插件市场提供了很多优秀的插件/组件库供我们选择,比较火的就是自家的uni-ui以及uView UI,大部分组件还是比较好用的,如果做中大型项目以及 UI 要求较高的情况下,还是比较推荐自己搭一套组件库,方便扩展以及维护。
评论记录:
回复评论: