uni 封装request请求
- utils/interceptors.js
import { simpleHttp } from './request/simple'
import { baseUrl } from '../config'
import store from '@/store'
import { authNav } from './checkAuth'
// 1.请求拦截器
// 2.在响应的时候,处理data数据
// 3.统一的错误处理
// 这里的vm,就是我们在vue文件里面的this,所以我们能在这里获取vuex的变量,比如存放在里面的token变量
const install = (Vue, vm) => {
// 此为自定义配置参数,具体参数见上方说明
Vue.prototype.$u.http.setConfig({
baseUrl,
dataType: 'json',
showLoading: true, // 是否显示请求中的loading
loadingText: '请求中...', // 请求loading中的文字提示
loadingTime: 800, // 在此时间内,请求还没回来的话,就显示加载中动画,单位ms
originalData: false, // 是否在拦截器中返回服务端的原始数据
loadingMask: true, // 展示loading的时候,是否给一个透明的蒙层,防止触摸穿透
// 配置请求头信息
header: {
'content-type': 'application/json;charset=UTF-8'
},
// options - 即为上一次请求的所有的config配置
// instance - 即为原先的request实例 -> token已经被更新回来了,所以可以继续使用
errorHandle: async (err, { options, instance }) => {
if (err.statusCode === 401) {
// todo 4.业务 —> refreshToken -> 请求响应401 -> 刷新token
try {
const { code, token } = await simpleHttp({
method: 'POST',
url: baseUrl + '/login/refresh'
}, {
header: {
Authorization: 'Bearer ' + uni.getStorageSync('refreshToken')
}
})
// console.log('🚀 ~ file: interceptors.js ~ line 30 ~ errorHandle: ~ token', token)
if (code === 200) {
// refreshToken请求成功
// 1.设置全局的token
store.commit('setToken', token)
// 2.重新发起请求
const newResult = await instance.request(options)
return newResult
}
} catch (error) {
// 代表refreshToken已经失效
// 清除本地的token
store.dispatch('logout')
// 导航到用户的登录页面
authNav()
}
uni.showToast({
icon: 'none',
title: '鉴权失败,请重新登录',
duration: 2000
})
} else {
// 其他的错误
// showToast提示用户
// 3.对错误进行统一的处理 -> showToast
const { data: { msg } } = err
uni.showToast({
icon: 'none',
title: msg || '请求异常,请重试',
duration: 2000
})
}
}
})
// 请求拦截,配置Token等参数
Vue.prototype.$u.http.interceptor.request = (config) => {
// 引用token
// 1.在头部请求的时候,token带上 -> 请求拦截器
const publicArr = [/\/public/, /\/login/]
// local store -> uni.getStorageSync('token')
let isPublic = false
publicArr.forEach(path => {
isPublic = isPublic || path.test(config.url)
})
const token = uni.getStorageSync('token')
if (!isPublic && token) {
config.header.Authorization = 'Bearer ' + token
}
// 最后需要将config进行return
return config
// 如果return一个false值,则会取消本次请求
// if(config.url === '/user/rest') return false; // 取消某次请求
}
// 响应拦截,判断状态码是否通过
Vue.prototype.$u.http.interceptor.response = (res) => {
// console.log('🚀 ~ file: request.js ~ line 46 ~ install ~ res', res)
return res
}
}
export default {
install
}
- utils/request/simple.js
export const simpleHttp = (options, { header = {}, callback }) => {
const result = new Promise((resolve, reject) => {
uni.request(Object.assign({
timeout: 10 * 1000
}, options, {
header,
success: (res) => {
// 请求成功
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(res.data)
} else {
reject(res)
}
},
fail: (err) => {
reject(err)
},
complete: () => {
callback && callback()
}
}))
})
return result
}
- utils/request/index.js
import deepMerge from '../function/deepMerge'
class Request {
// 设置全局默认配置
setConfig (customConfig) {
// 深度合并对象,否则会造成对象深层属性丢失
this.config = deepMerge(this.config, customConfig)
}
// 主要请求部分
request (options = {}) {
// 检查请求拦截
if (this.interceptor.request && typeof this.interceptor.request === 'function') {
const interceptorRequest = this.interceptor.request(options)
if (interceptorRequest === false) {
// 返回一个处于pending状态中的Promise,来取消原promise,避免进入then()回调
return new Promise(() => { })
}
this.options = interceptorRequest
}
options.dataType = options.dataType || this.config.dataType
options.responseType = options.responseType || this.config.responseType
options.url = options.url || ''
options.params = options.params || {}
options.header = Object.assign({}, this.config.header, options.header)
options.method = options.method || this.config.method
const errCallback = { options, instance: this }
return new Promise((resolve, reject) => {
options.complete = (response) => {
const { errorHandle } = this.config
// 请求返回后,隐藏loading(如果请求返回快的话,可能会没有loading)
uni.hideLoading()
// 清除定时器,如果请求回来了,就无需loading
clearTimeout(this.config.timer)
this.config.timer = null
// 判断用户对拦截返回数据的要求,如果originalData为true,返回所有的数据(response)到拦截器,否则只返回response.data
if (this.config.originalData) {
// 判断是否存在拦截器
if (this.interceptor.response && typeof this.interceptor.response === 'function') {
const resInterceptors = this.interceptor.response(response)
// 如果拦截器不返回false,就将拦截器返回的内容给this.$u.post的then回调
if (resInterceptors !== false) {
resolve(resInterceptors)
} else {
// 如果拦截器返回false,意味着拦截器定义者认为返回有问题,直接接入catch回调
errorHandle(response, errCallback)
reject(response)
}
} else {
// 如果要求返回原始数据,就算没有拦截器,也返回最原始的数据
resolve(response)
}
} else {
if (response.statusCode === 200) {
if (this.interceptor.response && typeof this.interceptor.response === 'function') {
const resInterceptors = this.interceptor.response(response.data)
if (resInterceptors !== false) {
resolve(resInterceptors)
} else {
errorHandle(response, errCallback)
reject(response.data)
}
} else {
// 如果不是返回原始数据(originalData=false),且没有拦截器的情况下,返回纯数据给then回调
resolve(response.data)
}
} else {
// 不返回原始数据的情况下,服务器状态码不为200,modal弹框提示
// if(response.errMsg) {
// uni.showModal({
// title: response.errMsg
// });
// }
errorHandle(response, errCallback)
reject(response)
}
}
}
// 判断用户传递的URL是否/开头,如果不是,加上/,这里使用了uView的test.js验证库的url()方法
// 情景一:如果是使用的官方的uview组件库 -> 创建新的reg -> url
// 情况二:如果是自己定义的request工具js -> 替换正则
options.url = /^https?:\/\/.*/.test(options.url)
? options.url
: (this.config.baseUrl + (options.url.indexOf('/') === 0
? options.url
: '/' + options.url))
// console.log('🚀 ~ file: index.js ~ line 82 ~ Request ~ returnnewPromise ~ options.url', options.url, /^https?:\/\/.*/.test(options.url))
// 是否显示loading
// 加一个是否已有timer定时器的判断,否则有两个同时请求的时候,后者会清除前者的定时器id
// 而没有清除前者的定时器,导致前者超时,一直显示loading
if (this.config.showLoading && !this.config.timer) {
this.config.timer = setTimeout(() => {
uni.showLoading({
title: this.config.loadingText,
mask: this.config.loadingMask
})
this.config.timer = null
}, this.config.loadingTime)
}
uni.request(options)
})
// .catch(res => {
// // 如果返回reject(),不让其进入this.$u.post().then().catch()后面的catct()
// // 因为很多人都会忘了写后面的catch(),导致报错捕获不到catch
// return new Promise(()=>{});
// })
}
constructor () {
this.config = {
baseUrl: '', // 请求的根域名
// 默认的请求头
header: {},
method: 'POST',
// 设置为json,返回后uni.request会对数据进行一次JSON.parse
dataType: 'json',
// 此参数无需处理,因为5+和支付宝小程序不支持,默认为text即可
responseType: 'text',
showLoading: true, // 是否显示请求中的loading
loadingText: '请求中...',
loadingTime: 800, // 在此时间内,请求还没回来的话,就显示加载中动画,单位ms
timer: null, // 定时器
originalData: false, // 是否在拦截器中返回服务端的原始数据,见文档说明
loadingMask: true // 展示loading的时候,是否给一个透明的蒙层,防止触摸穿透
}
// 拦截器
this.interceptor = {
// 请求前的拦截
request: null,
// 请求后的拦截
response: null
}
// get请求
this.get = (url, data = {}, header = {}) => {
return this.request({
method: 'GET',
url,
header,
data
})
}
// post请求
this.post = (url, data = {}, header = {}) => {
return this.request({
url,
method: 'POST',
header,
data
})
}
// put请求,不支持支付宝小程序(HX2.6.15)
this.put = (url, data = {}, header = {}) => {
return this.request({
url,
method: 'PUT',
header,
data
})
}
// delete请求,不支持支付宝和头条小程序(HX2.6.15)
this.delete = (url, data = {}, header = {}) => {
return this.request({
url,
method: 'DELETE',
header,
data
})
}
}
}
export default new Request()
- utils/checkAuth.js
import store from '@/store'
export const authNav = (title = '登录已失效', duration = 2000) => {
global.ctrl && clearTimeout(global.ctrl)
// 1.给用户一个轻提示
uni.showToast({
icon: 'none',
title,
duration
})
// 2.2s之后跳转到登录页面
global.ctrl = setTimeout(() => {
uni.navigateTo({
url: '/subcom-pkg/auth/auth'
})
}, 2000)
}
export const checkSession = async () => {
try {
await uni.checkSession()
return true
} catch (error) {
return false
}
}
export const checkToken = async () => {
let flag = true
const token = uni.getStorageSync('token')
const checked = await checkSession()
if (!store.state.token || !token || !checked) {
flag = false
uni.showModal({
title: '您未登录',
content: '需要登录才能操作,确定登录吗?',
success: function (res) {
if (res.confirm) {
uni.navigateTo({
url: '/subcom-pkg/auth/auth'
})
}
}
})
}
return flag
}
export const checkAuth = async () => {
let flag = true
const token = uni.getStorageSync('token')
const checked = await checkSession()
if (!store.state.token || !token || !checked) {
flag = false
}
return flag
}
export const gotoGuard = async (path) => {
const flag = await checkAuth()
if (!flag) {
await authNav()
} else {
uni.navigateTo({
url: path
})
}
}
- utils/funciton/deepMerge.js
import deepClone from "./deepClone";
// JS对象深度合并
function deepMerge(target = {}, source = {}) {
target = deepClone(target);
if (typeof target !== 'object' || typeof source !== 'object') return false;
for (var prop in source) {
if (!source.hasOwnProperty(prop)) continue;
if (prop in target) {
if (typeof target[prop] !== 'object') {
target[prop] = source[prop];
} else {
if (typeof source[prop] !== 'object') {
target[prop] = source[prop];
} else {
if (target[prop].concat && source[prop].concat) {
target[prop] = target[prop].concat(source[prop]);
} else {
target[prop] = deepMerge(target[prop], source[prop]);
}
}
}
} else {
target[prop] = source[prop];
}
}
return target;
}
export default deepMerge;
- utils/function/deepClone.js
// 判断arr是否为一个数组,返回一个bool值
function isArray (arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
// 深度克隆
function deepClone (obj) {
// 对常见的“非”值,直接返回原来值
if([null, undefined, NaN, false].includes(obj)) return obj;
if(typeof obj !== "object" && typeof obj !== 'function') {
//原始类型直接返回
return obj;
}
var o = isArray(obj) ? [] : {};
for(let i in obj) {
if(obj.hasOwnProperty(i)){
o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
}
}
return o;
}
export default deepClone;
使用方式
import store from '@/store'
import Vue from 'vue'
const axios = Vue.prototype.$u
const getList = (options) => {
return axios.get('/public/list', options)
}
export {
getList
}
const req = require.context('./modules', false, /\.js$/)
const install = (Vue) => {
let api = Vue.prototype.$u.api || {}
req.keys().forEach(item => {
const module = req(item)
// 1.取得所有的方法 -> key值,
const keys = Object.keys(module)
keys.forEach(key => {
api = {
...api,
// 2.取得所有方法对应的function -> value值
// 3.把上面的对象 -> $u.api
[key]: module[key]
}
})
// 4.把它封装成为一个插件,以便在后面的方法中使用 this.$u.api.方法名(参数).then(res) ....
})
Vue.prototype.$u.api = api
}
export default {
install
}
import Vue from 'vue'
import interceptors from '@/common/interceptors'
import apis from '@/api'
Vue.use(interceptors)
Vue.use(apis)