** U N I A P P UNIAPP UNIAPP**的一些优化总结
uniapp 使用过程中的一些总结
浏览器 js ECMAScript + windows ,docuement 等封装
uniapp 是什么 ECMAScript + uni api 封装
使用场景:安卓 + h5
-
节流/防抖
-
节流防抖函数:
节流的意思是,规定时间内,只触发一次。比如我们设定500ms,在这个时间内,无论点击按钮多少次,它都只会触发一次。 -
防抖的意思是,在连续的操作中,无论进行了多长时间,只有某一次的操作后在指定的时间内没有再操作,这一次才被判定有效。
uniapp 节流/防抖 函数的实现
let timer; let
flag
/**
* 节流原理:在一定时间内,只能触发一次
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function throttle(func, wait = 500, immediate = true) {
if (immediate) {
if (!flag) {
flag = true
// 如果是立即执行,则在wait毫秒内开始时执行
typeof func === 'function' && func()
timer = setTimeout(() => {
flag = false
}, wait)
}
} else if (!flag) {
flag = true
// 如果是非立即执行,则在wait毫秒内的结束处执行
timer = setTimeout(() => {
flag = false
typeof func === 'function' && func()
}, wait)
}
}
export default throttle
let timeout = null
/**
* 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function debounce(func, wait = 500, immediate = false) {
// 清除定时器
if (timeout !== null) clearTimeout(timeout)
// 立即执行,此类情况一般用不到
if (immediate) {
const callNow = !timeout
timeout = setTimeout(() => {
timeout = null
}, wait)
if (callNow) typeof func === 'function' && func()
} else {
// 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
timeout = setTimeout(() => {
typeof func === 'function' && func()
}, wait)
}
}
export default debounce
loading状态
给按钮添加loadding状态,不可点击
- vuex 状态管理
import {login,userInfo,register} from "../../config/api/others.js"
import { getTheme, getCurrentCoin, Encrypt, getToken, getUser, getResToken, getLoginTime, getAgent, getRole } from '@/utils/auth'
let huianUser = {}
// 需要永久存储,且下次APP启动需要取出的,在state中的变量名
let saveStateKeys = ['vuex_theme','vuex_favoriteCoin','vuex_user','vuex_token','vuex_loginTime','vuex_refreshToken','vuex_agent','vuex_role']
// 保存变量到本地存储中
const saveMopUser = function (key, value) {
// 判断变量名是否在需要存储的数组中
if (saveStateKeys.indexOf(key) != -1) {
// 获取本地存储的 huianUser 对象,将变量添加到对象中
let tmp = JSON.parse(uni.getStorageSync(uni.myConfig.localStorageName) || '{}')
tmp[key] = Encrypt(JSON.stringify(value))
// 执行这一步后,所有需要存储的变量,都挂载在本地的 huianUser 对象中
uni.setStorageSync(uni.myConfig.localStorageName, JSON.stringify(tmp))
}
}
const state = {
vuex_user: getUser(),
vuex_token: getToken(),
vuex_refreshToken: getResToken(),
vuex_loginTime: getLoginTime(),
vuex_agent: getAgent(),
vuex_role: getRole(),
vuex_theme: getTheme(), // 獲取主題
vuex_current_colin: getCurrentCoin() // 自選幣種
}
const mutations = {
setStore(state, payload) {
// 判断是否多层级调用,state中为对象存在的情况,诸如user.info.score = 1
let nameArr = payload.name.split('.');
let saveKey = '';
let len = nameArr.length;
if (len >= 2) {
let obj = state[nameArr[0]];
for (let i = 1; i < len - 1; i++) {
obj = obj[nameArr[i]];
}
obj[nameArr[len - 1]] = payload.value;
saveKey = nameArr[0];
} else {
// 单层级变量,在state就是一个普通变量的情况
state[payload.name] = payload.value;
saveKey = payload.name;
}
// 保存变量到本地,见顶部函数定义
saveMopUser(saveKey, state[saveKey])
},
// 登出
logout(state) {
state.vuex_user = {}
state.vuex_token = ''
state.vuex_refreshToken = ''
state.vuex_loginTime = ''
state.vuex_agent = ''
state.vuex_role = ''
uni.removeStorageSync("huianUser");
}
}
const actions = {
// 登录
mLogin({ commit }, userInfo) {
return new Promise((resolve, reject) => {
api.login(userInfo).then(response => {
if(response.data.code ==200){
const result = response.data.result
const userInfo = result.userInfo
uni.setStorageSync(ACCESS_TOKEN,result.token);
uni.setStorageSync(USER_INFO,userInfo);
commit('SET_TOKEN', result.token)
commit('SET_AVATAR', userInfo.avatar)
commit('SET_NAME', { username: userInfo.username,realname: userInfo.realname})
resolve(response)
}else{
resolve(response)
}
}).catch(error => {
console.log("catch===>response",response)
reject(error)
})
})
},
//手机号登录
PhoneLogin({ commit }, userInfo) {
return new Promise((resolve, reject) => {
api.phoneNoLogin(userInfo).then(response => {
if(response.data.code ==200){
const result = response.data.result
const userInfo = result.userInfo
uni.setStorageSync(ACCESS_TOKEN,result.token);
uni.setStorageSync(USER_INFO,userInfo);
commit('SET_TOKEN', result.token)
commit('SET_NAME', { username: userInfo.username,realname: userInfo.realname})
commit('SET_AVATAR', userInfo.avatar)
resolve(response)
}else{
reject(response)
}
}).catch(error => {
reject(error)
})
})
},
// 登出
Logout({ commit, state }) {
return new Promise((resolve) => {
let logoutToken = state.token;
commit('SET_TOKEN', '')
uni.removeStorageSync(ACCESS_TOKEN)
api.logout(logoutToken).then(() => {
resolve()
}).catch(() => {
resolve()
})
})
},
};
export default {
namespaced: true,
state,
mutations,
actions
};
- 请求拦截器代码
// 拦截器配置文件
import store from '@/store'
let startLoading = false
let isResedding = false
function jumpPage() {
uni.$u.route({
url: 'pages/login/login',
params: {
url: uni.$u.page()
}
})
}
function handleErrorCode(errorCode){
switch(errorCode){
case 'email_already_exist':
return "Account aleady exists";break;
...
...
...
default:
if(typeof(errorCode) == 'string'){
return errorCode.replace(/_/g, " ");break;
}
return "Data Error";
}
}
module.exports = (vm) => {
uni.$u.http.setConfig((config) => {
config.baseURL = uni.myConfig.baseUrl
config.custom = {
auth: true,
toast: false
}
config.timeout = uni.myConfig.timeout
config.header = {
'content-type': 'application/json;charset=UTF-8'
}
return config
})
uni.$u.http.interceptors.request.use((config) => {
config.data = config.data || {}
if (config?.custom?.auth) {
config.header.token = uni.myConfig.tokenPrefix + store.getters.vuex_token
}
//携带刷新token 换取用户信息
if(config?.custom?.isRefresh){
config.header['refresh-token'] = store.getters.vuex_refreshToken
}
return config
}, config => {
return Promise.reject(config)
})
// 响应拦截
uni.$u.http.interceptors.response.use((response) => {
const data = response.data
// 隐藏 loading
if (startLoading) {
startLoading = false
}
if (data.code === uni.myConfig.defaultSuccessCode) {
//成功返回
return data.data === undefined ? {} : data.data;
} else if (data.code === uni.myConfig.expireTokenCode) {
//重新发送请求
let lastTry = store.getters.last_resend;
let now =new Date().getTime();
if(now - lastTry> 60000){//60秒后可重试
//40006 普通token过期,通过本地存储换取用户信息
store.dispatch('user/refreshUserInfo');
store.commit('publicdata/setStore',{
name:'last_resend',
value:now
})
return uni.$u.http.request(response.config);
}
} else if(data.code == uni.myConfig.validationCode){
//验证错误处理,找到错误信息并返回
return Promise.reject({error:handleErrorCode(data.msg)});
}else if(data.code == 40008){
//数据不存在错误
return Promise.reject({error:'no data exist'})
} else {
//其他错误
return Promise.reject(data)
}
}, (response) => {
// 对响应错误做点什么 (statusCode !== 200)
// 隐藏 loading
if (startLoading) {
startLoading = false
// plus.nativeUI.closeToast()
}
return Promise.reject(response)
})
}
- 组件封装
页面级别的封装
页面功能已经测试过,尽量少修改,差异性功能区分页面
- 多种注册方式
8. 手机注册
9. 带验证码的手机注册
10. 不带验证码的手机注册
11. 带邀请code的注册
12. 不带code的注册
13. 邮箱注册
14. 其他注册
- 多语言
多语言层级尽量少,可以使用前缀
"redeem": {
"buy_for": "Betrag der gekauften Institution ",
"buy_type": "Girokonto ",
},
//修改后
"redeem_buy_for": "Betrag der gekauften Institution ",
"redeem_buy_type": "Girokonto ",
- eventbus 事件总线
//使用
//注册监听
uni.$on('login', e => {
console.log(e);
this.usnerinfo = e;
});
//注册事件
uni.$emit('login', {
avatarUrl: '',
token: 'user123456',
userName: 'unier',
login: true
});
//注销事件(beforeDestroy中一定要注销)
uni.$off('login');
应用场景:
-
下单之后的订单列表刷新
-
登陆之后的用户数据拉取
-
页面之前的参数传递
-
热更新
// #ifdef APP-PLUS
// 检测升级
appUpdate()
// #endif
// #ifndef MP
//APP更新
export default function appUpdate() {
uni.request({
url: 'http://app.jeecg.com/update.json', //检查更新的服务器地址
data: {
appid: plus.runtime.appid,
version: plus.runtime.version,
imei: plus.device.imei
},
success: (res) => {
plus.runtime.getProperty(plus.runtime.appid, function(wgtinfo) {
let client_version = wgtinfo.version
var flag_update = client_version.split(".").splice(0, 2).join(".") != res.data.version.split(".").splice(0, 2)
.join(".")
var flag_hot = (Number(client_version.split(".")[2]) < Number(res.data.version.split(".")[2])) & !flag_update
console.log(client_version)
console.log(flag_update)
console.log(flag_hot)
if (flag_update) {
// 提醒用户更新
uni.showModal({
title: '更新提示',
content: res.data.note,
success: (showResult) => {
if (showResult.confirm) {
plus.nativeUI.toast("正在准备环境,请稍后!");
console.log(res.data.url, )
var dtask = plus.downloader.createDownload(res.data.url, {
method: 'GET',
filename: '_doc/update/'
}, function(d, status) {
if (status == 200) {
var path = d.filename; //下载apk
plus.runtime.install(path); // 自动安装apk文件
} else {
plus.nativeUI.alert('版本更新失败:' + status);
}
});
dtask.start();
}
}
})
} else if (flag_hot) {
uni.downloadFile({
url: res.data.wgtUrl,
success: (downloadResult) => {
console.log(downloadResult.tempFilePath)
if (downloadResult.statusCode === 200) {
plus.nativeUI.toast(`正在热更新!${res.data.versionCode}`);
plus.runtime.install(downloadResult.tempFilePath, {
force: false
}, function() {
plus.nativeUI.toast("热更新成功");
plus.runtime.restart();
}, function(e) {
console.log(e)
plus.nativeUI.toast(`热更新失败:${e.message}`);
});
}
}
});
}
});
}
})
}
其他:
text标签下面不要出现其他标签,文字最好包裹在text标签下面
img标签app显示问题,使用image标签
vue 中 使用 echarts,lottie 等扩展,由于这些插件需要操作dom,所以需要使用 renderjs
长页面使用 scroll-view 包裹
trycahch的使用,接口请求,数据解包等操作尽量使用trycatch 包裹下
mixin 混入
import mixin from './common/mixin'; //公共方法混入
Vue.mixin(mixin);
flex布局 https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
如何创建全局唯一对象
//1app.vue中创建
globalData:{
socket:{}
},
//使用 getApp() 函数用于获取当前应用实例,一般用于获取globalData 。
getApp().globalData.socket
/**
* 验证电子邮箱格式
*/
function email(value) {
return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value)
}
/**
* 验证手机格式
*/
function mobile(value) {
return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value)
}
/**
* 验证URL格式
*/
function url(value) {
return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/
.test(value)
}
/**
* 验证日期格式
*/
function date(value) {
if (!value) return false
// 判断是否数值或者字符串数值(意味着为时间戳),转为数值,否则new Date无法识别字符串时间戳
if (number(value)) value = +value
return !/Invalid|NaN/.test(new Date(value).toString())
}
/**
* 验证ISO类型的日期格式
*/
function dateISO(value) {
return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value)
}
/**
* 验证十进制数字
*/
function number(value) {
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
}
/**
* 验证字符串
*/
function string(value) {
return typeof value === 'string'
}
/**
* 验证整数
*/
function digits(value) {
return /^\d+$/.test(value)
}
/**
* 验证身份证号码
*/
function idCard(value) {
return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(
value
)
}
/**
* 是否车牌号
*/
function carNo(value) {
// 新能源车牌
const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/
// 旧车牌
const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/
if (value.length === 7) {
return creg.test(value)
} if (value.length === 8) {
return xreg.test(value)
}
return false
}
/**
* 金额,只允许2位小数
*/
function amount(value) {
// 金额,只允许保留两位小数
return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value)
}
/**
* 中文
*/
function chinese(value) {
const reg = /^[\u4e00-\u9fa5]+$/gi
return reg.test(value)
}
/**
* 只能输入字母
*/
function letter(value) {
return /^[a-zA-Z]*$/.test(value)
}
/**
* 只能是字母或者数字
*/
function enOrNum(value) {
// 英文或者数字
const reg = /^[0-9a-zA-Z]*$/g
return reg.test(value)
}
/**
* 验证是否包含某个值
*/
function contains(value, param) {
return value.indexOf(param) >= 0
}
/**
* 验证一个值范围[min, max]
*/
function range(value, param) {
return value >= param[0] && value <= param[1]
}
/**
* 验证一个长度范围[min, max]
*/
function rangeLength(value, param) {
return value.length >= param[0] && value.length <= param[1]
}
/**
* 是否固定电话
*/
function landline(value) {
const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/
return reg.test(value)
}
/**
* 判断是否为空
*/
function empty(value) {
switch (typeof value) {
case 'undefined':
return true
case 'string':
if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true
break
case 'boolean':
if (!value) return true
break
case 'number':
if (value === 0 || isNaN(value)) return true
break
case 'object':
if (value === null || value.length === 0) return true
for (const i in value) {
return false
}
return true
}
return false
}
/**
* 是否json字符串
*/
function jsonString(value) {
if (typeof value === 'string') {
try {
const obj = JSON.parse(value)
if (typeof obj === 'object' && obj) {
return true
}
return false
} catch (e) {
return false
}
}
return false
}
/**
* 是否数组
*/
function array(value) {
if (typeof Array.isArray === 'function') {
return Array.isArray(value)
}
return Object.prototype.toString.call(value) === '[object Array]'
}
/**
* 是否对象
*/
function object(value) {
return Object.prototype.toString.call(value) === '[object Object]'
}
/**
* 是否短信验证码
*/
function code(value, len = 6) {
return new RegExp(`^\\d{${len}}$`).test(value)
}
/**
* 是否函数方法
* @param {Object} value
*/
function func(value) {
return typeof value === 'function'
}
/**
* 是否promise对象
* @param {Object} value
*/
function promise(value) {
return object(value) && func(value.then) && func(value.catch)
}
/** 是否图片格式
* @param {Object} value
*/
function image(value) {
const newValue = value.split('?')[0]
const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i
return IMAGE_REGEXP.test(newValue)
}
/**
* 是否视频格式
* @param {Object} value
*/
function video(value) {
const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i
return VIDEO_REGEXP.test(value)
}
/**
* 是否为正则对象
* @param {Object}
* @return {Boolean}
*/
function regExp(o) {
return o && Object.prototype.toString.call(o) === '[object RegExp]'
}
export default {
email,
mobile,
url,
date,
dateISO,
number,
digits,
idCard,
carNo,
amount,
chinese,
letter,
enOrNum,
contains,
range,
rangeLength,
empty,
isEmpty: empty,
jsonString,
landline,
object,
array,
code,
func,
promise,
video,
image,
regExp,
string
}