一、uniapp项目目录
uniapp给我们的项目框架介绍,有一些文件夹是没有在模板中内置的,因此我们需要自己手动创建以下,例如最外层的components,用来放置我们的一些全局通用组件
┌─components 符合vue组件规范的uni-app组件目录
│ └─comp-a.vue 可复用的a组件
├─pages 业务页面文件存放的目录
│ ├─index
│ │ └─index.vue index页面
│ └─list
│ └─list.vue list页面
├─static 存放应用引用的本地静态资源(如图片、视频等)的目录,注意: 静态资源只能存放于此
├─uni_modules 存放uni_module规范的插件。
├─wxcomponents 存放小程序组件的目录,详见
├─main.js Vue初始化入口文件
├─App.vue 应用配置,用来配置App全局样式以及监听 应用生命周期
├─manifest.json 配置应用名称、appid、logo、版本等打包信息,详见
└─pages.json 配置页面路由、导航条、选项卡等页面类信息,详见
如下图,在开发的过程中,我依据vue项目的开发习惯,在pages中依照业务功能来创建功能分类文件夹,最常见的是按照首页的tabbar来区分功能模块,每一个文件夹中存放该功能涉及的所有页面,且每一个功能文件夹中还有一个单独的components文件夹用于放置该仅功能文件夹中的页面依赖的组件。
二、通用插件封装
uniapp选择使用vue.js作为开发框架,我们可以提供过插件封装提高uniapp使用的体验。vue 插件官方介绍链接
通过引入插件,我们可以极大的提升我们的开发效率。
在封装前我们需要写一个 config 文件,方便我们快速自定义一些颜色和请求路径等。
//congif.js
const config = {
baseUrl:'https://example.cn',//请求的基本路径
modalColor:'#5271FF', //弹窗颜色
}
module.exports = config
1 弹窗插件
在小程序中,如果我们没有自定义弹窗和拟态框组件的话一般都是使用官方的showModal或者showToast api来进行一些用户交互。这种非常频繁使用到的操作非常适合封装起来快速调用。具体代码如下
插件代码
const config = require('../config.js')
var message = {
toast(title, type = 'text') {
if (title.length > 15) {
console.error('toast长度超过15个字符,当前长度为' + title.length)
return
}
var icon = 'none'
if (type) {
switch (type) {
case 'text':
icon = 'none'
break
case 'suc':
icon = 'success'
break
case 'err':
icon = 'error'
break
}
}
uni.showToast({
title,
icon
})
},
confirm(title, confirmColor) {
return new Promise((res, rej) => {
uni.showModal({
title,
cancelColor: '#b6b6b6',
confirmColor: confirmColor || config.modalColor,
success: (result) => {
if (result.cancel) {
rej(result)
} else if (result.confirm) {
res(result)
}
}
})
})
},
async message(content, confrimText) {
return new Promise((res) => {
uni.showModal({
title: '提示',
content,
showCancel: false,
confirmColor: config.modalColor,
success: (result) => {
res(result)
}
})
})
}
}
module.exports = message
示例调用
this.message.toast('回答已删除')
2 请求插件
在uni-app中直接使用官方提供的 uni.request(OBJECT) 这个api来进行请求的调用,进行二次封装,以减少调用时的代码量以及进行一些全局配置。具体代码如下
插件代码
//http.js
const config = require('../config.js')
const message = require('./message.js')
var http = {
post(path, params, contentType = 'form', otherUrl, ) {
return new Promise((resolve, reject) => {
var url = (otherUrl || config.baseUrl) + path
if (!checkUrl(url)) {
rej('请求失败')
}
uni.request({
method: 'POST',
url,
header: {
"Content-Type": contentType === 'json' ? "application/json" :
"application/x-www-form-urlencoded"
},
data: params,
success: (res) => {
console.log('request:POST请求' + config.baseUrl + path + ' 成功', res.data)
resolve(res.data)
},
fail: (err) => {
message.toast('请求失败', 'err')
console.error('request:请求' + config.baseUrl + path + ' 失败', err)
reject('请求失败')
}
})
})
},
put(path, params, contentType = 'form', otherUrl, ) {
return new Promise((resolve, reject) => {
var url = (otherUrl || config.baseUrl) + path
if (!checkUrl(url)) {
rej('请求失败')
}
uni.request({
method: 'PUT',
url,
header: {
"Content-Type": contentType === 'json' ? "application/json" :
"application/x-www-form-urlencoded"
},
data: params,
success: (res) => {
console.log('request:PUT请求' + config.baseUrl + path + ' 成功', res.data)
resolve(res.data)
},
fail: (err) => {
message.toast('请求失败', 'err')
console.error('request:PUT请求' + config.baseUrl + path + ' 失败', err)
reject('请求失败')
}
})
})
},
get(path, params, otherUrl) {
return new Promise((resolve, reject) => {
var url = (otherUrl || config.baseUrl) + path
if (!checkUrl(url)) {
return
}
uni.request({
url,
data: params,
success: (res) => {
console.log('request:GET请求' + config.baseUrl + path + ' 成功', res.data)
resolve(res.data)
},
fail: (err) => {
message.toast('请求失败', 'err')
console.error('request:GET请求' + config.baseUrl + path + ' 失败', err)
reject(err)
}
})
})
},
delete(path, params, otherUrl) {
return new Promise((resolve, reject) => {
var url = (otherUrl || config.baseUrl) + path
if (!checkUrl(url)) {
return
}
uni.request({
url,
data: params,
method: "DELETE",
success: (res) => {
console.log('request:DELETE请求' + config.baseUrl + path + ' 成功', res.data)
resolve(res.data)
},
fail: (err) => {
message.toast('请求失败', 'err')
console.error('request:DELETE请求' + config.baseUrl + path + ' 失败', err)
reject(err)
}
})
})
},
async upload(path, fileArray, otherUrl) {
if (typeof fileArray !== 'object') {
console.error('request:参数错误,请传入文件数组')
return
}
var url = (otherUrl || config.baseUrl) + path
if (!checkUrl(url)) {
return
}
var arr = []
for (let i in fileArray) {
const res = await uni.uploadFile({
url: otherUrl || config.baseUrl + path,
filePath: fileArray[i],
name: 'file'
})
console.log(res)
if (res[0]) {
console.error('request:上传失败', res[0])
return
}
arr.push(JSON.parse(res[1].data).data)
}
return arr
},
}
function checkUrl(url) {
var urlReg = /^((ht|f)tps?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/;
if (!urlReg.test(url)) {
console.error('request:请求路径错误' + url)
return false
}
return true
}
module.exports = http
示例调用
async getAnswer() {
const res = await this.http.get('/applet/answerList', {
qId: this.question.id,
})
if (res.code === 200) {
return res.data
} else {
this.message.toast('请求失败')
return false
}
}
在上面的代码中我们可以看到,我们引入两个依赖文件分别是config和message 这个在下面会说的,在http对象中有五个方法,分别是post,get,update,delete对应着 restful api 的增删查改操作,还有一个upload 方法用来上传文件。在小程序中请求我们不需要考虑跨域的问题,我们的默认请求路径是从 config这个依赖文件中获取的。而这时候我们还可以利用第一个 弹窗插件 快速的进行消息提示。
3 存储插件
在uniapp中我们有两种全局数据的存储方式 一种是globalData,通过在App.vue中添加globalData这个值来进行数据全局化,例如我们可以用来保存我们的小程序版本。
export default {
globalData: {
version: '1.0.0'
}
}
但是globalData的数据不是持久化的,当我们退出小程序再进入的时候就重新初始化了,所以我们还有一种全局数据存储方式是storage,类比我们web端的localstroge,使用方式也几乎一样。通过官方提供的api实现本地存储的效果:uni.setStorage(OBJECT)、uni.setStorageSync(KEY,DATA)、uni.getStorage(OBJECT)、uni.getStorageSync(KEY)、uni.getStorageInfo(OBJECT)、uni.getStorageInfoSync()、uni.removeStorage(OBJECT)、uni.removeStorageSync(KEY)、uni.clearStorage()、uni.clearStorageSync()
在小程序中往往没有web端那么复杂的数据结构,不过,uniapp小程序也可以使用vuex或者pinia作为一个全局数据管理的库,不过本地存储实现简单,简单存储是可以使用的。如果想要省略setData操作可以用 proxy 劫持存储的值去存在本地。
插件代码
var store = {
_init(){
if(uni.getStorageSync('store')==''){
uni.setStorageSync('store',{})
return
}
const data = uni.getStorageSync('store')
for(let i in data){
if(!this[i]){
this[i] = data[i]
}
}
},
setData(key,value){
if(key == '_init'||key=='setData'){
console.error('store:非法key值',key)
return
}
this[key] = value
uni.setStorageSync('store',this)
console.log(uni.getStorageSync('store'))
}
}
module.exports = store
示例使用
this.store.setData('user',{name:'oil'}) // 赋值
console.log(this.store.user) // 读值
4 表单验证插件
表单验证是使用的是一个很轻量开源的表单验证库JSValidate。 JSValidate gitee链接
示例调用
// validate.js
const validate = {
userForm:{
intro: '@个人简介|require',
college: '@学校|require!请选择你的学校',
nickname:'@昵称|require'
}
}
module.exports = validate
在表单中写入一个表单验证方法,如果条件不符合的话就使用弹窗进行提示
//form.js
validateForm() {
// 表单验证方法
const validate = require("./validate")
const validator = new this.validator()
var result = validator.check(validate.userForm, this.form, false)
if (result !== true) {
this.message.message(result)
return false
}
return true
}
5 时间处理插件
关于时间处理插件我在项目中使用的也是一个开源的轻量级js库 day.js,具体的使用方式参考官方文档,day.js几乎包含了所有时间处理相关的操作,例如时间对比,时间转换等。 day.js官方文档链接
day.js其中有一项功能特别常用,就是一个相对时间转换的功能,相对时间转换就是将一个时间字符串例如年月日时分秒转换成几秒前,十分钟前,几个月前等等。使用的方式如下代码,我们使用vue的computed来进行时间字符串的处理,通过 闭包 向computed
方法内传入时间字符串参数,然后通过day.js的formNow
方法返回一个中文的相对时间。date就是我封装后引入的插件了。
示例代码
computed: {
relativeDate() {
return (date) => {
return this.date(date).fromNow()
}
}
}
6 插件统一出口
当我们拥有了很多个插件之后(如下图),我们的插件之间可能会共享一些配置,例如上文中的config.js
,接下来我们可以写一个入口文件index.js
用来将我们写的插件引入,然后在main.js
中安装我们的插件。
示例代码
// index.js
const http = require('./lib/http')
const message = require('./lib/message')
const router = require('./lib/router')
const validator = require('./lib/validator')
const date = require('./lib/date')
const store = require('./lib/store')
const to = require('./lib/to')
const myplugins = {
message,
http,
router,
validator,
date,
store,
to,
install(Vue) {
this.store._init()
for (let i in this) {
if (i == 'install') {
continue
}
Vue.prototype[i] = this[i]
}
delete this.install
}
}
export default myplugins
// main.js
import Vue from 'vue'
import myplugins from './oil-uni/index.js'
Vue.use(myplugins)
app.$mount()
我们在入门文件中定义了一个对象叫做myplugins
,myplugins
上除了我们写好的方法还有一个特殊的方法就是install
,install
的作用就是在我们使用Vue.use()
的时候会将Vue的构造器传入install
方法作为第一个参数,然后我们将所有方法都绑定在Vue的原型链上。
安装后,我们只需要在页面或组件的js中使用 this. + xx
插件 就可以访问到封装的插件了。
二 运行平台判断
判断使用平台,方便根据不同平台做出差异性适配
使用
// @/utils/Config.tsimport {EPlatform} from './EPlatform';
import {isH5, Platform} from '@/utils/Platform'
/**配置信息*/
export default class Config {
/**http请求根目录*/
static get httpBaseUrl(): string {
if (isH5) {
return '/'
} else {
return 'http://demo.cn/'
}
}
}
条件编译
/**枚举EPlatform*/
export enum EPlatform {
/**App*/
AppPlus = 'APP-PLUS',
/**App nvue*/
AppPlusNvue = 'APP-PLUS-NVUE',
/**H5*/
H5 = 'H5',
/**微信小程序*/
MpWeixin = 'MP-WEIXIN',
/**支付宝小程序*/
MpAlipay = 'MP-ALIPAY',
/**百度小程序*/
MpBaidu = 'MP-BAIDU',
/**字节跳动小程序*/
MpToutiao = 'MP-TOUTIAO',
/**QQ小程序*/
MpQq = 'MP-QQ',
/**360小程序*/
Mp360 = 'MP-360',
/**微信小程序/支付宝小程序/百度小程序/字节跳动小程序/QQ小程序/360小程序*/
Mp = 'MP',
/**快应用通用(包含联盟、华为)*/
QuickappWebview = 'quickapp-webview',
/**快应用联盟*/
QuickappWebviewUnion = 'quickapp-webview-union',
/**快应用华为*/
QuickappWebviewHuawei = 'quickapp-webview-huawei',
}
/**使用条件编译获取平台信息*/
export function ifDefPlatform(): EPlatform {
let platform: EPlatform
//#ifdef APP-PLUS
platform = EPlatform.AppPlus;
//#endif
//#ifdef APP-PLUS-NVUE
platform = EPlatform.AppPlusNvue;
//#endif
//#ifdef H5
platform = EPlatform.H5;
//#endif
//#ifdef MP-WEIXIN
platform = EPlatform.MpWeixin;
//#endif
//#ifdef MP-ALIPAY
platform = EPlatform.MpAlipay;
//#endif
//#ifdef MP-BAIDU
platform = EPlatform.MpBaidu;
//#endif
//#ifdef MP-TOUTIAO
platform = EPlatform.MpToutiao;
//#endif
//#ifdef MP-QQ
platform = EPlatform.MpQq;
//#endif
//#ifdef MP-360
platform = EPlatform.Mp360;
//#endif
//#ifdef MP
platform = EPlatform.Mp;
//#endif
//#ifdef quickapp-webview
platform = EPlatform.QuickappWebview;
//#endif
//#ifdef quickapp-webview-union
platform = EPlatform.QuickappWebviewUnion;
//#endif
//#ifdef quickapp-webview-huawei
platform = EPlatform.QuickappWebviewHuawei;
//#endif
return platform
}
/**平台类型*/
export const Platform: EPlatform = ifDefPlatform()
/**默认导出平台类型*/
export default Platform
/**App*/
export const isAppPlus = Platform == EPlatform.AppPlus
/**App nvue*/
export const isAppPlusNvue = Platform == EPlatform.AppPlusNvue
/**H5*/
export const isH5 = Platform == EPlatform.H5
/**微信小程序*/
export const isMpWeixin = Platform == EPlatform.MpWeixin
/**支付宝小程序*/
export const isMpAlipay = Platform == EPlatform.MpAlipay
/**百度小程序*/
export const isMpBaidu = Platform == EPlatform.MpBaidu
/**字节跳动小程序*/
export const isMpToutiao = Platform == EPlatform.MpToutiao
/**QQ小程序*/
export const isMpQq = Platform == EPlatform.MpQq
/**360小程序*/
export const isMp360 = Platform == EPlatform.Mp360
/**微信小程序/支付宝小程序/百度小程序/字节跳动小程序/QQ小程序/360小程序*/
export const isMp = Platform == EPlatform.Mp
/**快应用通用(包含联盟、华为)*/
export const isQuickappWebview = Platform == EPlatform.QuickappWebview
/**快应用联盟*/
export const isQuickappWebviewUnion = Platform == EPlatform.QuickappWebviewUnion
/**快应用华为*/
export const isQuickappWebviewHuawei = Platform == EPlatform.QuickappWebviewHuawei
/**是否开发环境*/
export const isDevelopment = process.env.NODE_ENV == 'development'
/**是否线上环境*/
export const isProduction = process.env.NODE_ENV == 'production'
/**抖音小程序*/
export const isMpDouyinApp = uni.getSystemInfoSync().appName == 'Douyin'
/**头条小程序*/
export const isMpToutiaoApp = uni.getSystemInfoSync().appName == 'Toutiao'