解决了 “在每个页面 / 按钮上手动绑定 loading” 的问题,大幅减少了重复代码
核心代码如下:
import axios from 'axios';
import { Loading } from 'element-ui';
// 定义不同API类型的超时时间(单位:毫秒)
const TIMEOUT_CONFIG = {
default: 3000, // 默认超时时间
fast: 1000, // 快速API,简单查询等
normal: 5000, // 普通API,大多数业务接口
slow: 10000, // 慢速API,文件上传、大数据量处理
critical: 15000 // 关键API,支付、重要业务处理
}
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: TIMEOUT_CONFIG.default
});
/**
* loading 计数
* loadingCount //请求次数,为0时,结束loading
**/
let loadingCount = 0
let isLoading = false
let loadingInstance = null
const noLoadingApi = ['a','b','c'] // 禁止触发全局loading的路由
const addLoading = (url) => {
const result = noLoadingApi.includes(url)
if(result){
return;
}
loadingCount++
if (!isLoading){
loadingInstance = Loading.service({
lock: true,
background: 'rgba(0, 0, 0, 0.9)'
})
isLoading = true
}
}
const closeLoading = (url) => {
const result = url && noLoadingApi.includes(url)
if(result){
return;
}
loadingCount--
if (loadingCount <= 0) {
loadingInstance && loadingInstance.close()
isLoading = false
}
}
service.interceptors.request.use(
config => {
// 定义关键API路径数组
const CRITICAL_API_PATHS = ['a','b','c'];
// 检查是否是关键API
if (CRITICAL_API_PATHS.some(path => config.url.includes(path))) {
config.timeout = TIMEOUT_CONFIG.critical
}
addLoading(config.url)
//....根据需求自定义封装请求头
return config;
},
error => {
return Promise.reject(error);
}
);
service.interceptors.response.use(
response => {
closeLoading(response.config.url)
//...根据需求自定义封装响应头
},
error => {
closeLoading()
return Promise.reject(error);
}
);
export default service;
1. 全局统一管理 loading,无需页面 / 按钮单独处理
代码通过 axios 的请求 / 响应拦截器,对所有经过 service 实例的请求进行统一拦截:
- 请求发起时:自动调用
addLoading显示全局 loading(除非接口在noLoadingApi白名单中); - 请求完成时(成功 / 失败):自动调用
closeLoading关闭全局 loading(当所有并发请求都完成时)。
这种方式下,无论哪个页面、哪个按钮触发的请求,只要使用了这个 service 实例,都不需要在页面中手动写 loading.show() 或 loading.hide(),完全由拦截器自动处理。
2. 避免了 “重复编写 loading 控制逻辑” 的冗余
如果没有这段代码,通常的做法是:
- 在每个按钮点击事件中,先手动显示 loading;
- 在请求的
then/catch中手动隐藏 loading; - 还要处理多个请求并发时,loading 被提前关闭的问题(比如两个请求同时发起,第一个完成就关 loading,导致第二个请求无 loading)。
而这段代码通过 loadingCount 计数和拦截器统一控制,一次性解决了 “显示 / 隐藏时机”“并发请求 loading 管理” 等问题,所有页面 / 按钮都能复用这套逻辑,无需重复编写。
3. 特殊场景通过配置排除,灵活性兼顾
代码中通过 noLoadingApi 数组定义了 “不需要 loading 的接口”,对于这些特殊接口,无需在页面中单独处理,只需在全局配置中维护这个数组即可,进一步减少了页面级的重复配置。
4. 流程图
graph TD
%% 定义核心节点样式
classDef core fill:#ffd700,stroke:#333,stroke-width:2px,color:#000
A["调用导出的service发起请求"]:::core --> B["进入axios请求拦截器"]:::core
%% 请求拦截器逻辑
B --> C["定义CRITICAL_API_PATHS数组['a','b','c']"]
C --> D{"判断请求URL是否包含关键API路径"}:::core
D -- 是 --> E["设置超时时间为critical(15000ms)"]
D -- 否 --> F["使用默认超时时间(3000ms)"]
E & F --> G["调用addLoading(config.url)"]:::core
%% addLoading逻辑
G --> H{"URL是否在noLoadingApi['a','b','c']中"}:::core
H -- 是 --> I["不添加全局loading"]
H -- 否 --> J["loadingCount计数+1"]
J --> K{"isLoading是否为false"}:::core
K -- 是 --> L["创建Loading实例(lock:true,背景0.9透明)"]:::core
K -- 否 --> M["不创建新Loading实例"]
L & M --> N["设置isLoading为true"]
I & N --> O["自定义封装请求头"]
O --> P["发送HTTP请求"]:::core
%% 响应处理逻辑
P --> Q{"请求是否成功"}:::core
Q -- 成功 --> R["进入axios响应拦截器"]:::core
Q -- 失败 --> S["进入响应错误拦截器"]:::core
%% 响应拦截器-关闭loading
R --> T["调用closeLoading(response.config.url)"]:::core
T --> U{"URL是否在noLoadingApi中且存在"}:::core
U -- 是 --> V["不关闭loading"]
U -- 否 --> W["loadingCount计数-1"]
W --> X{"loadingCount是否<=0"}:::core
X -- 是 --> Y["关闭Loading实例,isLoading设为false"]:::core
X -- 否 --> Z["不关闭Loading实例"]
V & Y & Z --> AA["自定义封装响应数据"]
AA --> AB["返回响应结果"]:::core
%% 响应错误拦截器-关闭loading
S --> AC["调用closeLoading()"]:::core
AC --> AD{"URL是否在noLoadingApi中且存在"}:::core
AD -- 是 --> AE["不关闭loading"]
AD -- 否 --> AF["loadingCount计数-1"]
AF --> AG{"loadingCount是否<=0"}:::core
AG -- 是 --> AH["关闭Loading实例,isLoading设为false"]:::core
AG -- 否 --> AI["不关闭Loading实例"]
AE & AH & AI --> AJ["返回Promise.reject(error)"]:::core
总结
这段代码通过 “拦截器 + 全局配置” 的方式,实现了 loading 的 “一次编写,全项目复用”,彻底避免了在每个页面、每个按钮中重复编写 loading 控制逻辑的工作,显著减少了冗余代码,同时还解决了并发请求下的 loading 显示问题,是一种高效的全局状态管理方案。