我们的目的是想实现一个基于axios封装的函数,可以传入一个参数loading,当参数为true时,显示加载动画。不传时则不显示loading。
我们来实现
第一个版本:
首先我们写一个简单的node服务:
import express from 'express';
import cors from "cors"
const app=new express()
app.use(cors())
app.use(express.static("public"))
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.post('/api/error',(req,res)=>{
console.log("accpet_beab--req---",req.url)
console.log("accpet_beab--req--data-",req.body)
res.send({
message:"ok"
})
})
app.get("/api/list",(req,res)=>{
console.log("query",req.query)
setTimeout(()=>{
res.json({ message: 'Protected route', data:[
{id:"11",name:"aaa"},
{id:"22",name:"bbb"},
{id:"33",name:"ccc"},
] });
},4000)
})
app.post("/api/marks",(req,res)=>{
console.log("query",req.query)
setTimeout(()=>{
res.json({ message: 'Protected route', data:[
{id:"11",method:"post"},
{id:"22",method:"post"},
{id:"33",method:"post"},
] });
},4000)
})
app.get("/api/log",(req,res)=>{
res.send({
msg:"log",
data:{
name:"asage",
age:30
},
status:"ok"
})
})
app.listen(4200,()=>{
console.log("服务启动成功")
})
基于axios的封装
import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios'
import { ElLoading, ElMessage } from 'element-plus'
import type { IResponseData, ExAxiosRequestConfig } from '@/types/core/axios'
import { ENV_DEV, TIMEOUT_TIME } from '@/config/project'
import { checkReturnStatus } from './fun'
import { userStore } from '@/stores/user'
import router from '@/router'
let loadingInstance: any = null
export const service = axios.create({
baseURL: MODE === ENV_DEV ? '' : VITE_BASE_URL, //这里也可以使用变量
timeout: TIMEOUT_TIME, //超时设置
//withCredentials: true, //异步请求携带cookie
headers: {
}
})
//request interceptor 请求拦截器
service.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
const { token } = userStore()
//针对post/put等请求处理
if (config?.data?.showLoading) {
loadingInstance = ElLoading.service({
fullscreen: true,
text: '正在加载',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.3)'
})
}
//针对get/delete等请求处理
if (config?.params?.showLoading) {
loadingInstance = ElLoading.service({
fullscreen: true,
text: '正在加载',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.3)'
})
}
config.headers.token = `${token || ''}`
return config
},
(error) => {
console.log('request--error', error)
return Promise.reject(error)
}
)
//响应拦截器
service.interceptors.response.use(
(response: AxiosResponse): AxiosResponse<IResponseData> => {
if (loadingInstance) {
loadingInstance.close()
}
if (response.status !== 200) {
const err = {
data: {},
errcode: -99999,
errmsg: '请求失败!'
}
// Promise.reject(err);
throw err
}
if (response.headers.token) setToken(response.headers.token)
const res = response.data
const status = checkReturnStatus(res.errcode)
switch (status) {
case -1:
ElMessage.error('登录信息已发生改变,请重新登录!')
throw res
case 0:
ElMessage.error('系统繁忙,请稍后再试!')
throw res
case 1:
console.log('case----1-', res)
return res
case -2:
ElMessage.error(res.errmsg)
throw res
case 70003:
ElMessage({
message: res.errmsg,
type: 'error',
duration: 1500
})
throw res
}
},
(error) => {
// 在响应错误的时候的逻辑处理
if (error.code === 'ECONNABORTED' || error.message === 'Network Error' || error.message.includes('timeout')) {
ElMessage({ type: 'error', message: '当前网络错误' })
}
return Promise.reject(error)
}
)
// class HTTP {
export const http = {
get(url: string, config: InternalAxiosRequestConfig & any = {}, showLoading = false) {
config = { ...config.params, showLoading }
return new Promise((resolve, reject) => {
service
.get(url, config)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
},
post(url: string, config: InternalAxiosRequestConfig & any = {}, showLoading = false) {
return new Promise((resolve, reject) => {
config = { ...config, showLoading }
service
.post(url, config)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
},
delete(url: string, config: InternalAxiosRequestConfig & any = {}, showLoading = false) {
config = { ...config.params, showLoading }
return new Promise((resolve, reject) => {
service
.delete(url, config)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
},
put(url: string, config: InternalAxiosRequestConfig & any = {}, showLoading = false) {
return new Promise((resolve, reject) => {
config = { ...config, showLoading }
service
.put(url, config)
.then((res) => {
resolve(res)
})
.catch((err) => {
reject(err)
})
})
}
}
再看一看具体的http请求
export function getAllRolesData(params): Promise<IResponseData> {
return http.post('/api/permission/getAllMenu', params, false)
}
以上基本上就可以实现了我们的自定义loading效果。我们传入的第三个参数可以自主的控制当前请求是否需要实现loading。
但是这里有一个问题:就是我们的所有请求。都会在参数列表中带上了一个loading的无用参数。
那我们是否有办法实现我们的业务请求中依然带上这个参数loading,但是我们真实的http请求中
却没有出现呢?
答案是有的。这里我们要针对post/get请求单独做处理。改进后的代码
第二版
import axios from 'axios';
import { ElLoading } from 'element-plus';
// 创建 axios 实例
const service = axios.create({
baseURL: '', // 根据你的需要设置基础URL
timeout: 5000 // 请求超时时间
});
// 请求拦截器
service.interceptors.request.use(
config => {
// 在这里可以进行一些请求前的操作,例如添加token等
return config;
},
error => {
// 请求错误时的操作
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
response => {
// 请求成功后的操作
return response;
},
error => {
// 响应错误时的操作
return Promise.reject(error);
}
);
// 请求转换函数,用于移除请求配置中的 loading 参数
service.defaults.transformRequest = [
function (data) {
debugger
// 检查 data 是否是对象,并且有 loading 属性
if (data && typeof data === 'object' && 'loading' in data) {
// 移除 data 中的 loading 属性
const { loading, ...configWithoutLoading } = data;
// debugger
return configWithoutLoading;
}
// 如果 data 不是对象或者没有 loading 属性,则直接返回
return data;
}
];
// 封装请求函数
function request(config) {
// 处理 loading 动画
let loadingInstance = null;
if (config.loading) {
loadingInstance = ElLoading.service({ fullscreen: true });
}
//针对get进行处理
if (config.method === 'get' && config.params) {
// 对 params 做一些额外处理
const { loading, ...paramsWithoutLoading } = config.params || {};
config.params = paramsWithoutLoading;
// debugger
}
// 发送请求
return service(config).finally(() => {
// 请求完成后关闭加载动画(如果存在)
if (loadingInstance) {
loadingInstance.close();
}
});
}
// 导出封装后的请求函数
export default request;
在具体页面中进行使用:
import {request} from "../utils/http2"
request(
"/api/list",
'get',
{
pageNo:1,
pageSize:10
},
true
).then(res=>{
console.log("list---res",res)
}).catch(err=>{
console.log("err",err)
})
这里的核心就是单独针对post和get请求做处理。针对post,我们通过transformRequest对post请求中的data进行改造,去掉loading参数。而针对get请求,则通过config.params去掉loading。这样就可以实现自主可控的loading
提醒:这里有个地方要特别注意。
// 请求转换函数,用于移除请求配置中的 loading 参数
service.defaults.transformRequest = [
function (data) {
debugger
// 检查 data 是否是对象,并且有 loading 属性
if (data && typeof data === 'object' && 'loading' in data) {
// 移除 data 中的 loading 属性
const { loading, ...configWithoutLoading } = data;
// debugger
return configWithoutLoading;
}
// 如果 data 不是对象或者没有 loading 属性,则直接返回
return data;
}
];
这里如果我们在真实的http请求中发现对于post/put请求的request payload是[object,object],是因为我们没有把post请求参数序列化。所以我们要做一点修正
//针对post/put请求处理loading参数问题
service.defaults.transformRequest = [
function (data) {
if (data && typeof data === 'object' && 'loading' in data) {
// 移除 data 中的 loading 属性
const configWithoutLoading = JSON.parse(JSON.stringify(data))
delete configWithoutLoading.loading
return JSON.stringify(configWithoutLoading)
}
// 如果 data 不是对象或者没有 loading 属性,则直接返回
return JSON.stringify(data)
}
]
这样就正常得到参数了