1-安装Axios
npm install axios
2-封装request.js文件
为什么要创建Axios实例?
1. 隔离配置,避免全局污染
- 场景:当项目需要与多个后端服务交互时,不同服务可能有不同的
baseURL
、headers
或timeout
设置。 - 解决方案:通过创建实例,每个实例可以独立配置参数(如
baseURL: "https://api.service.com/v1"
),避免修改默认实例的全局配置,防止不同服务间的配置冲突。 -
示例: const userApi = axios.create({ baseURL: "https://user.service.com" }); const productApi = axios.create({ baseURL: "https://product.service.com" });
2. 独立的拦截器管理
- 场景:某些API需要身份验证(如添加
Authorization
头),而其他API可能是公开的。 - 解决方案:为不同实例绑定独立的请求/响应拦截器,例如认证逻辑仅作用于需要登录的实例,避免在拦截器中编写冗余的条件判断。
-
// 需要认证的实例 const authApi = axios.create({ baseURL: "https://secure.api.com" }); authApi.interceptors.request.use(config => { config.headers.Authorization = `Bearer ${getToken()}`; return config; });
3. 模块化与可维护性
- 场景:按功能模块划分API(如用户模块、订单模块),提升代码组织性。
- 解决方案:通过命名实例(如
userService
、orderService
)明确职责,使代码更易维护和协作。 -
// userService.js export const userService = axios.create({ baseURL: "/user", timeout: 5000, });
4. 灵活应对不同场景需求
- 场景:某些API需要更长的超时时间,或不同的
Content-Type
(如上传文件的multipart/form-data
)。 - 解决方案:通过实例化快速生成适配不同场景的客户端,无需每次请求重复配置。
-
const uploadApi = axios.create({ baseURL: "/upload", headers: { "Content-Type": "multipart/form-data" }, timeout: 30000, // 更长超时时间 });
创建Axios实例的核心目的是通过配置隔离和逻辑解耦,提升代码的健壮性和可维护性。这种方式使得不同服务或模块的API调用互不干扰,同时支持更细粒度的控制和扩展,是复杂项目中的最佳实践。
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 1000000
})
创建步骤:
1. 创建实例
创建 Axios 实例
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API, // 根据环境变量设置后端 API 地址
timeout: 1000000 // 请求超时时间(约 16 分钟)
});
配置所有请求的基础 URL 和超时时间。
2. 自动身份验证
// 自动添加JWT Token
if ( getToken ( ) && ! isToken ) {
config . headers [ 'Authorization' ] = 'Bearer ' + getToken ( )
}
3. 统一错误处理
- 二进制响应: 直接返回数据供下载方法处理。
- 错误码处理:
401 未授权
: 弹出重新登录确认框,确认后调用退出登录逻辑。- 其他错误: 显示通知提示。
- 网络错误: 统一处理超时、连接异常等,显示错误消息。
4. 请求/响应拦截
// request拦截器
service.interceptors.request.use(config => {
// 判断是否需要携带 Token
const isToken = (config.headers || {}).isToken === false;
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken();
}
// GET 请求参数处理
if (config.method === 'get' && config.params) {
config.url += '?' + tansParams(config.params);
config.params = {}; // 清空 params,避免重复拼接
}
// 防止重复提交(仅 POST/PUT)
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false;
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = { url: config.url, data: config.data, time: new Date().getTime() };
const requestSize = JSON.stringify(requestObj).length;
if (requestSize >= 5 * 1024 * 1024) {
console.warn('请求数据过大,跳过防重复提交验证');
}
// 实际项目可能需要在此处添加缓存校验逻辑
}
return config;
}, error => {
return Promise.reject(error);
});
// 响应拦截器
service.interceptors.response.use(
res => {
const code = res.data.code || 200;
const msg = errorCode[code] || res.data.msg || '未知错误';
// 处理二进制响应(如文件下载)
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data;
}
// 错误处理
if (code !== 200) {
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true;
ElMessageBox.confirm('登录状态已过期,请重新登录', '提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
isRelogin.show = false;
useUserStore().logout(); // 调用 Pinia 退出登录
}).catch(() => {
isRelogin.show = false;
});
}
return Promise.reject('无效会话,请重新登录');
} else {
ElNotification.error({ title: msg });
return Promise.reject(res.data);
}
} else {
return res.data;
}
},
error => {
// 网络错误处理
let { message } = error;
if (message === 'Network Error') {
message = '后端接口连接异常';
} else if (message.includes('timeout')) {
message = '请求超时';
} else if (message.includes('Request failed with status code')) {
message = `接口 ${message.slice(-3)} 异常`;
}
ElMessage.error(message);
return Promise.reject(error);
}
);
5. 通用下载
// 通用下载方法
export function download(url, params, filename, config) {
downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", })
return service.post(url, params, {
transformRequest: [(params) => { return tansParams(params) }],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob',
...config
}).then(async (data) => {
const isBlob = blobValidate(data);
if (isBlob) {
const blob = new Blob([data])
saveAs(blob, filename)
} else {
const resText = await data.text();
const rspObj = JSON.parse(resText);
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
ElMessage.error(errMsg);
}
downloadLoadingInstance.close();
}).catch((r) => {
console.error(r)
ElMessage.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close();
})
}
6. 维护性提升
- 修改配置只需调整封装文件
- 新成员快速上手
- 减少重复代码
使用
在使用时,只需导入对应的request.js文件
import request from '@/utils/request'
//查询设备故障记录列表
export function getRowData(data) {
return request({
url: '/qc-api/getRowData',
method: 'post',
data: data
})
}