axios封装,统一处理loading,以及页面出错提示等
想要实现的效果:
- 可创建多个实例,自定义配置(跨域携带 cookie,token,超时设置)
- 请求、响应拦截器
- 请求成功,业务状态码200,解析result
- http请求200, 业务状态码非200,全局提示服务端的报错
- http请求非200, 说明http请求都有问题,全局提示报错
- http请求或者业务状态码401都做注销操作
- 统一处理接口
loading
、refresh
、加载更多,以及三种情况对应出错封装到
项目 > src > http
文件夹下
基础封装
封装为一个类,可以创建多个实例,把可配置的属性作为变量传入我们封装的类中,例如:当我们需要用到不同的后端域名时,可以用创建实例的方式去调用,不同的域名用不同的实例一一对应
http/request.ts
文件:
import axios ,{ AxiosInstance, AxiosRequestConfig } from 'axios'
class Request {
// axios 实例
instance: AxiosInstance
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config)
}
request(config: AxiosRequestConfig) {
return this.instance.request(config)
}
}
export default Request
复制代码
http/index.ts
文件:
import Request from './request'
const http = new Request({
baseURL: baseURL, // 配置参数
timeout: timeout // 配置参数
})
export default http
复制代码
拦截器封装
- 全局拦截器
- 实例拦截器
- 接口拦截器
全局拦截器
// 封装类
class Request {
instance: AxiosInstance // axios 实例
interceptors?: RequestInterceptors
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config)
//全局拦截器
this.instance.interceptors.request.use(
(config: RequestConfig) => {
console.log('全局请求拦截器')
return config
},
(error) => {
return error
}
)
this.instance.interceptors.response.use(
(response) => {
console.log('全局响应拦截器')
return response;
},
(error) => {
return error;
}
)
}
}
export default Request
复制代码
实例拦截器
http/index.ts
文件:
import Request from './request'
const http = new Request({
timeout: 10000, // 配置参数
interceptors: {
requestInterceptor: config => {
console.log('实例请求拦截器')
return config
},
responseInterceptor: result => {
console.log('实例响应拦截器')
return result
},
},
})
export default http
复制代码
http/request.ts
文件:
// 封装类
class Request {
instance: AxiosInstance // axios 实例
interceptors?: RequestInterceptors
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config)
//实例拦截器
this.interceptors = config.interceptors
this.instance.interceptors.request.use(this.interceptors?.requestInterceptor)
this.instance.interceptors.response.use(this.interceptors?.responseInterceptor)
//全局拦截器
。。。
}
}
export default Request
复制代码
接口拦截器
我们需要改造我们传入的参数的类型,因为axios提供的AxiosRequestConfig
是不允许我们传入拦截器的,所以自定义RequestConfig
继承AxiosRequestConfig
// types.ts
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
export interface RequestInterceptors {
// 请求拦截
requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestInterceptorsCatch?: (err: any) => any
// 响应拦截
responseInterceptors?: (config: AxiosResponse) => AxiosResponse
responseInterceptorsCatch?: (err: any) => any
}
// 自定义传入的参数
export interface RequestConfig extends AxiosRequestConfig {
interceptors?: RequestInterceptors
}
复制代码
修改http/request.ts
封装文件:
// 封装类
class Request {
instance: AxiosInstance // axios 实例
interceptors?: RequestInterceptors
constructor(config: RequestConfig) { // 换成 RequestConfig
this.instance = axios.create(config)
//实例拦截器
。。。
//全局拦截器
。。。
}
// 换成 RequestConfig
request<T = any>(config: RequestConfig): Promise<T> {
return new Promise((resolve, reject) => {
// 对某个接口请求单独的拦截
if (config.interceptors?.requestInterceptor) {
config = config.interceptors?.requestInterceptor(config)
}
this.instance
.request<any, T>(config)
.then((response: any) => {
// 对某个接口返回单独的拦截
if (config.interceptors?.responseInterceptor) {
response = config.interceptors?.responseInterceptor(response)
}
resolve(response)
})
.catch((error) => {
reject(error)
})
})
}
get<T = any>(config: RequestConfig): Promise<T> {
return this.request<T>({ ...config, method: 'GET' })
}
post<T = any>(config: RequestConfig): Promise<T> {
return this.request<T>({ ...config, method: 'POST' })
}
delete<T = any>(config: RequestConfig): Promise<T> {
return this.request<T>({ ...config, method: 'DELETE' })
}
}
export default Request
复制代码
api 调用:
import http from "@/utils/http";
enum Api {
HomeData = '/xx/xx/xx/xxxx.do',
}
export const getHomeData = (param1: String) =>
http.get({
url: `${Api.HomeData}?param1=${param1}`,
interceptors: {
requestInterceptor(res) {
console.log('接口请求拦截')
return res
},
responseInterceptor(result) {
console.log('接口响应拦截')
return result
},
},
})
复制代码
执行顺序
拦截器的执行顺序为:
接口请求拦截
全局请求拦截器
实例请求拦截器
实例响应拦截器
全局响应拦截器
接口响应拦截
复制代码
全局 loading 以及 错误处理
pinia
store
保存 loading
以及 refresh
状态统计等,store/base.ts
文件:
import { defineStore } from "pinia";
export const useBaseStore = defineStore("base", {
state: () => {
return {
loadingFlag: "", // Loading 加载中,Refresh 刷新中
loadCount: 0, // 加载中:loadCount 不为0
refreshCount: 0, // 刷新中:refreshing 不为0
loadMore: false, // 是否处于加载更多
loadMoreErr: false, // 是否加载更多失败
refreshing: false, // 是否处于刷新中
errTip: "", // 错误提示,加载出错 需页面展示出错,点击可重试
};
},
getters: {
},
actions: {
plusLoading(params?: { tip: string }) {
++this.loadCount;
this.loadingFlag = "Loading";
},
minusLoading(params?: { tip: string }) {
this.loadCount > 0 ? --this.loadCount : this.loadCount = 0;
this.loadingFlag = "";
params?.tip ? this.errTip = params.tip : this.errTip = "";
},
plusRefresh(params?: { tip: string }) {
++this.refreshCount;
this.loadingFlag = "Refresh";
this.refreshing = true;
},
minusRefresh(params?: { tip: string }) {
this.refreshCount > 0 ? --this.refreshCount : (this.refreshCount = 0);
this.loadingFlag = "";
this.refreshing = false;
params?.tip ? this.errTip = params.tip : this.errTip = "";
},
plusLoadMore(params?: { tip: string }) {
this.loadMore = true;
},
minusLoadMore(params?: { tip: string }) {
this.loadMore = false;
this.loadMoreErr = !!params?.tip;
},
},
});
复制代码
全局拦截更新 store
// 异常处理
const errorHandler = (error: any) => {
console.error('异常处理', error)
// 增加状态码错误处理 -- 略
Toast.fail(`${error}`);
};
// 统一处理 loading refresh loadmore 以及出错
export const updateLoadingRefresh = (config: RequestConfig, flag: string, tip?: string) => {
if (!config.loadingFlag) return
setTimeout(function () {
let baseStore = useBaseStore()
let funName=`${flag}${config.loadingFlag}`;
baseStore[funName](tip);
}, 0);
};
// 封装类
class Request {
instance: AxiosInstance // axios 实例
interceptors?: RequestInterceptors
constructor(config: RequestConfig) {
this.instance = axios.create(config)
//实例拦截器
。。。
//全局拦截器
this.instance.interceptors.request.use(
(config: RequestConfig) => {
console.log('全局请求拦截器')
updateLoadingRefresh(config, 'plus')
return config
},
(error) => {
errorHandler(error);
return error
}
)
this.instance.interceptors.response.use(
(response) => {
console.log('全局响应拦截器')
updateLoadingRefresh(response.config as RequestConfig, 'minus');
return response;
},
(error) => {
errorHandler(error);
return error;
}
)
}
}
复制代码
使用
页面:
const loadData = (loadingFlag?: LoadingFlag) => {
let params={
config: {
loadingFlag
}
}
homeStore.fetchData(params)
}
const refreshData = () => {
loadData('Refresh');
}
onMounted(() => {
loadData('Loading');
});
复制代码
api:
export const homeApi = (params1: String, config?: RequestConfig) =>
http.get({
...config,
url: `${Api.homeApi}?params1=${params1}`,
interceptors: {
requestInterceptor(res) {
console.log('------接口请求拦截')
return res
},
responseInterceptor(result) {
console.log('------接口响应拦截')
return result
},
},
})
复制代码
加载交互
展示loading,出错展示出错且可以重新加载
// 全局 layout 增加判断
<div >
<Loading
type="spinner"
size="32px"
vertical
v-if="baseStore.loadCount != 0"
class="loading"
>
加载中...
</Loading>
<div v-else="baseStore.loadingFlag == 'Loading' && baseStore.errTip" @click="reload()">
{{ baseStore.errTip }}
</div>
<slot name="content" v-else></slot>
</div>
复制代码
刷新交互
<PullRefresh v-model="baseStore.refreshing" @refresh="refreshData">
</PullRefresh>
复制代码
对于出错进行轻提示
加载更多交互
<List
class="list"
v-model:loading="baseStore.loadMore"
v-model:error="baseStore.loadMoreErr"
:finished="xxxStore.noMoreData"
:immediate-check="false"
finished-text="没有更多了"
error-text="请求失败,点击重新加载"
@load="loadMoreData"
>
</List>
复制代码