实现功能:
- 取消掉重复的请求
- 路由跳转可以取消上个页面没发送的请求
- 统一请求方式 post get put delect格式统一
- 封装上传下载
- 统一提示实现(提供配置自定义提示)
- 非200状态码 错误信息只提示一次
- 大文件分片上传的封装,某一片上传失败 自动上传2次
用法
import { axios } from '@/common/request/index'
// noMessage 是否提示
axios.post('/proxyapi/api/sys/auth/login', data, { headers: { Authorization: ''}, noMessage: true })
axios.get('/proxyapi/api/sys/auth/login-user', data, { headers: { Authorization: ''} })
// 上传 不需要加请求头配置已经自己加了 不需要formData 配置自己加了
// chuckSize有的话就是分片上传 没有的话就是普通一次上传
// chuckSize值的单位是字节(一次上传多大)
axios.requestFormData('url', {file:file},{chuckSize:300})
// 下载
axios.requestExcelStream('url', {})
代码封装
import axios from 'axios'
import qs from 'qs'
import { router } from "@/router/index";
import { message } from "ant-design-vue";
import { store } from '@/store/index'
class request {
http = {}
pending = new Map()
errorStatus = {
'400': (Message = '系统错误') => { message.error(Message); },
'401': (Message = '登录过期', router) => {
message.error(Message);
const timeout = setTimeout(() => {
clearTimeout(timeout);
store.commit({ type: 'logOut/logOut' })
router.replace({ name: "login" });
}, 500);
},
'403': (Message = '没有权限') => {
message.error(Message)
const timeout = setTimeout(() => {
clearTimeout(timeout);
router.replace({ path: "/error", query: { title: '没有权限', status: 403 } });
}, 500);
},
'404': (Message = '地址错误') => { message.error(Message) },
'500': (Message = '系统错误') => { message.error(Message) },
'504': (Message = '网关超时') => { message.error(Message) },
}
constructor(baseURL = '', timeout = 10000, token, header = 'application/json;charset=UTF-8') {
if (!request.instance) {
request.instance = this
this.pending = new Map(); // 正在进行的请求
this.init(baseURL, timeout, token, header)
}
return request.instance
}
// 处理pending的url
dealUrl(config = {}, type = 'request') {
const method = config.method.toLocaleLowerCase(),
url = config.url
// 请求的config拼装(pending的key)
if (type === 'request') {
if (['get', 'delect'].includes(method)) return `${url}?params=${JSON.stringify(config?.params)}?method=${method}`
if (['post', 'put'].includes(method)) return `${url}?data=${JSON.stringify(config?.data)}?method=${method}`
return ''
}
// 响应的config拼装(pending的key)
if (['get', 'delect'].includes(method)) return `${url}?params=${JSON.stringify(config?.params)}?method=${method}`
if (['post', 'put'].includes(method)) return `${url}?data=${JSON.stringify(JSON.parse(config?.data))}?method=${method}`
return ''
}
// 添加peding
addPending(config = {}) {
config.cancelToken = new axios.CancelToken(cancle => {
const url = this.dealUrl(config, 'request')
this.pending.set(url, cancle)
})
}
// 取消pending
canclePending(config = {}, type = 'request') {
const url = this.dealUrl(config, type)
// 去寻找pending里是否有和本次相同的请求 有的话就取消上一次请求 并且删除pending的重复记录
if (!this.pending.has(url)) return
// 取消上次请求
this.pending.get(url)?.()
// 删除上次记录
this.pending.delete(url)
}
init(baseURL, timeout, token, header) {
// 创建一个axios实例
this.http = axios.create({
baseURL,
timeout,
headers: {
'Content-type': header,
Authorization: token, // token
}
});
this.request()
this.response()
}
// 添加请求拦截器
request() {
// 添加请求拦截器
this.http.interceptors.request.use(config => {
debugger
if (!config.headers.Authorization) {
config.headers.Authorization = store.getters['user/getToken'] ?? ''
}
if (config?.ifAddPending) {
// 取消重复请求
this.canclePending(config)
// 添加pending
this.addPending(config)
}
// 去除请求参数中的前后空格
const params = config?.data ?? {}
for (let key in params) {
if (params[key] && typeof params[key] === 'string') params[key] = params[key]?.trim()
}
// 普通表单形式(键值对)
if (config?.headers?.['Content-type'] === 'application/x-www-form-urlencoded') {
config.data = qs.stringify(config.data)
}
// // formData格式
// if (config?.headers?.['Content-type'] === 'multipart/form-data') {
// const params = { ...config.data }
// const formData = new FormData()
// for (let key in params) {
// formData.append(key, params[key])
// }
// config.data = formData
// }
return config
}, error => {
return Promise.reject(error);
});
}
// 添加响应拦截器
response() {
this.http.interceptors.response.use(response => {
const { statusText, data, config } = response
if (config?.ifAddPending) {
// 取消pending
this.canclePending(config, 'response')
}
if (statusText === "OK") {
if (!config?.noMessage) {
if (!data?.Success) message.error(data?.Message)
if (data?.Success) message.success(data?.Message)
}
return data
}
return {}
}, error => {
// 取消pending
for (let cb of this.pending.values()) {
cb?.()
}
this.pending?.clear()
if (error?.code === 'ERR_CANCELED') return
if (!error?.response) return message.error('服务器断开连接');
const {
data: { Success = true, Code, Message },
status
} = error?.response;
if (!error?.config?.noMessage) this.errorStatus?.[status + '']?.(Message, router)
return Promise.reject(error?.response ?? {});
});
}
}
export default request
import request from "./axios";
import { message } from "ant-design-vue";
import { store } from '@/store/index'
// import { watch } from 'vue'
let instance = new request('', 5000, store.getters['user/getToken'], 'application/json;charset=UTF-8')
// watch(() => store.getters['user/getToken'], (token) => {
// instance = new request('', 5000, token, 'application/json;charset=UTF-8')
// })
export class axios {
// 暴露出去方便 1. 切换导航时取消掉未结束的请求 2. 添加全局loading防止请求未完成时切换导航 (任选一种)
static pending() {
return instance.pending;
}
static async commonFnData(url = '', data, config = {}, method) {
if (!url || config?.constructor?.name !== 'Object') {
message.error("请输入正确参数");
return Promise.reject({ Success: false })
}
try {
const res = await instance.http({
url,
method,
data,
...config
})
if (res?.Success) return Promise.resolve(res)
return Promise.reject({ Success: false, error: res })
} catch (error) {
return Promise.reject({ Success: false, error })
}
}
static async commonFnParams(url = '', params, config = {}, method) {
if (!url || config?.constructor?.name !== 'Object') {
message.error("请输入正确参数");
return Promise.reject({ Success: false })
}
try {
const res = await instance.http({
url,
method,
params,
...config
})
if (res?.Success) return Promise.resolve(res)
return Promise.reject({ Success: false, error: res })
} catch (error) {
return Promise.reject({ Success: false, error })
}
}
static async post(url, data, config) {
return axios.commonFnData(url, data, config, 'post')
}
static async put(url, data, config) {
return axios.commonFnData(url, data, config, 'put')
}
static async get(url, params, config) {
return axios.commonFnParams(url, params, config, 'get')
}
static async delect(url, params, config) {
return axios.commonFnParams(url, params, config, 'delect')
}
// 上传
static async requestFormData(url, data, config = {}, method = 'post') {
const { name, size } = data?.file,
{ file } = data,
{ chuckSize } = config,
params = { ...data }
// 不需要分片上传
if (!chuckSize || chuckSize > size) {
// formData格式
const formData = new FormData()
for (let key in params) {
formData.append(key, params[key])
}
return axios.commonFnData(url, formData, { ...config, headers: { 'Content-type': 'multipart/form-data' } }, method)
}
// 分片上传 大文件上传
const chuckLen = Math.ceil(size / chuckSize)
const uploadArr = Array.from({ length: chuckLen }).map((item, index) => {
const result = file.slice(index * chuckSize, (index + 1) * chuckSize),
blob = new Blob([result], { type: file?.type }),
formData = new FormData()
blob.name = file.name
params.index = index
for (let key in params) {
if (key === 'file') formData.append('file', blob)
else formData.append(key, params[key])
}
return () => axios.commonFnData(url, formData, { ...config, headers: { 'Content-type': 'multipart/form-data' } }, method)
})
const res = await Promise.allSettled(uploadArr?.map(item => item()))
// 上传失败 重复上传2次
res?.map((item, index) => {
if (item?.status === 'rejected') {
uploadArr[index]().catch(() => {
uploadArr[index]().then(() => {
res[index].status = 'fulfilled'
})
})
}
})
return res
}
// 获取文件流
static async requestExcelStream(url = '', params = {}, config = {}, method = 'get') {
const res = await axios.commonFnParams(url, params, { responseType: 'blob', ...config }, method)
if (res.status === 200) {
var blob = res.data
var reader = new FileReader()
reader.readAsDataURL(blob) // 转换为base64,可以直接放入a表情href
reader.onload = function (e) {
// 转换完成,创建一个a标签用于下载
var a = document.createElement('a')
a.download = (params && params.getStreamPage || '表格') + Date.now()
a.href = e.target.result
a.click()
a.remove();
}
}
}
}