1、在src目录下新建service文件夹(名字看自己喜好,一般是service),里面存放接口请求的封装文件和公共配置文件。
(1)新建config.ts文件,里面统一管理请求的基础路径、请求超时时间和请求头等,如下:
/**
* 公共配置
*/
const requestPublicConfig = {
baseUrl: import.meta.env.MODE === "development" ? "/devApi" : "/mockApi",
timeout: 10000,
headers: {
Accept: 'application/json, text/plain, */*',
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
};
export default requestPublicConfig;
(2)新建request.ts文件,里面包含对发送请求前的处理、响应结果后的处理等,如下:
- 导入相关依赖、添加默认配置
/**
* 请求封装
* 1.下载依赖
* 2.创建文件
* 3.导入/引入 axios
* 4.添加默认配置
* 5.定义返回的数据类型
* 6.添加拦截器
* 7.封装请求方法
* 8.导出/抛出 实例
*/
import axios, { AxiosInstance, AxiosResponse, AxiosError, InternalAxiosRequestConfig, AxiosRequestConfig, Method } from "axios";
import { useRouter } from "vue-router";
import requestPublicConfig from "./config";
import { ElMessage } from "element-plus";
//自定义请求配置
export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
isLoading?: boolean
encrypt?: boolean
}
//默认配置
const defaultConfig = {
baseURL: requestPublicConfig.baseUrl,//接口地址 TODO: 根据不同的环境需要更换
timeout: requestPublicConfig.timeout,//超时时间
headers: requestPublicConfig.headers,//请求头设置
withCredentials: true,// 跨域时候允许携带凭证
isLoading: true,//是否显示loading
encrypt: true,//加解密开关
}
- 定义取消重复请求
// 定义接口
interface PendingType {
url?: string;
method?: Method;
params: any;
data: any;
cancel: any;
}
// 取消重复请求
const pending: Array<PendingType> = [];
const CancelToken = axios.CancelToken;
// 移除重复请求
const cancelDuplicateRequests = (config: AxiosRequestConfig) => {
for (const key in pending) {
const item: number = +key;
const list: PendingType = pending[key];
// 当前请求在数组中存在时执行取消操作
if (list.url === config.url && list.method === config.method && JSON.stringify(list.params) === JSON.stringify(config.params) && JSON.stringify(list.data) === JSON.stringify(config.data)) {
// 执行取消操作
list.cancel('操作太频繁,请稍后再试');
// 从数组中移除记录
pending.splice(item, 1);
}
}
};
- 定义对请求失败状态码的统一处理
//请求失败状态码统一处理
const checkStatus = (status: number | string) => {
switch (Number(status)) {
case 302:
ElMessage.error('接口重定向了!');
break;
case 400:
ElMessage.error("请求错误" + status)
break;
case 401:
ElMessage.error("当前用户未授权,请进行身份验证" + status)
localStorage.removeItem("userInfo");
localStorage.removeItem("token");
router.replace({
path: '/login',
});
break;
case 403:
ElMessage.error("登录过期,token失效" + status)
localStorage.removeItem("userInfo");
setTimeout(() => {
router.replace({
path: '/login',
});
}, 1000);
break;
case 404:
ElMessage.error("请求的资源不存在" + status)
break;
case 500:
ElMessage.error("服务器发生错误" + status)
break;
case 502:
ElMessage.error("网关错误" + status)
break;
case 503:
ElMessage.error("服务不可用,服务器暂时处于维护中" + status)
break;
default:
ElMessage.error("发生未知错误" + status)
}
}
- 创建axios实例
//实例化请求配置
const service: AxiosInstance = axios.create(defaultConfig);
- 添加请求拦截器,对发送请求之前进行一些处理,例如取消重复的请求、添加token到请求头中,添加loading加载等
//请求拦截器
service.interceptors.request.use((config) => {
//发送请求前,做些事情 例如取消重复请求、添加token到请求头中、添加loading的加载等
cancelDuplicateRequests(config);// 取消重复请求
config.cancelToken = new CancelToken((msg) => {
pending.push({
url: config.url,
method: config.method as Method,
params: config.params,
data: config.data,
cancel: msg,
});
//去localStorage中获取token
let token = localStorage.getItem("token");
if (token) {
//添加token到请求头中
config.headers['Authorization'] = "Bearer" + token;
}
});
return config;
}, (error) => {
return Promise.reject(error);
});
- 添加响应拦截器,对响应数据进行处理 例如 取消loading的加载、对返回状态进行判断:如请求错误、请求超时、获取数据失败、暂无数据等
//响应拦截器
service.interceptors.response.use((response: AxiosResponse) => {
//对响应数据进行处理 例如 1、取消loading的加载;2、对返回状态进行判断:如请求错误、请求超时、获取数据失败、暂无数据等
const { status, data, config } = response;
cancelDuplicateRequests(config);
// 请求成功
if (status === 200 || status === 204) {
return Promise.resolve(data);
} else {
return Promise.reject(config);
}
}, (error: AxiosError) => {
// 请求失败
const { response } = error;
if (response) {
checkStatus(response?.status);
}
});
- 封装请求方法并导出
//封装请求方法
const httpRequest = {
//get请求 获取数据 例如:获取列表、获取数据详情、搜索内容
async get<T>(url: string, params?: any): Promise<T> {
return await service.get(url, { params });
},
//post请求 添加数据 例如:表单提交、文件上传、添加新内容
async post<T>(url: string, data?: any): Promise<T> {
return await service.post(url, data);
},
//put请求 修改数据 例如:更新内容
async put<T>(url: string, data?: any): Promise<T> {
return await service.put(url, data);
},
//delete请求 删除数据 例如:删除内容
async delete<T>(url: string, params?: any): Promise<T> {
return await service.delete(url, { params });
}
}
export default httpRequest;
2、在vite.config.ts文件中配置代理,如下:
// 配置本地服务
server: {
host: true, //0.0.0.0或者true为监听所有ip
port: 5000, //端口号
open: false, //是否自动打开浏览器
// hmr: true,//热更新
//反向代理
proxy: {
"/devApi": {
target: "http://127.0.0.1:4523/m1/5561793-5238912-default", //代理地址 目标后端服务地址
changeOrigin: true, //开启跨域
rewrite: (path) => path.replace(/^\/devApi/, ""), //重写路径,将/devApi替换为""
},
"/mockApi": {
target: "http://127.0.0.1:5000", //代理地址 mock环境后端服务地址
changeOrigin: true, //开启跨域
rewrite: (path) => path.replace(/^\/mockApi/, ""), //重写路径,将/mockApi替换为""
},
},
},
3、在src目录下新建api文件夹,里面存放各种业务接口,例如获取用户信息接口,如下:
import httpRequest from "@/service/request";
// 获取用户信息
export const getUserInfo = (params: Object) => {
return httpRequest.get("/userApi/userInfo",params)
}
4、在业务组件和用户页面中调用存放在api文件夹中接口,如下:
import {getUserInfo} from "@/api/user";
onMounted(async () => {
try {
let res = await getUserInfo({token: "12345aBc67890dEf"});
console.log("接口返回的用户信息", res);
} catch (e) {
console.log(e);
}
});
启动项目,打开浏览器预览,效果如下:
至此,通过封装的方法调用接口可以正常获取返回来的数据。
多说一句:当我们改变响应状态码时,例如改成404,会发现出现了下述问题:
这是因为在对404响应状态码做了用element-plus的message弹出异常信息处理,如下:
而项目中采用的是自动按需导入 element-plus 组件,没有引入 element-plus 的全局样式导致样式缺失。
解决方法:在main.ts文件中添加如下代码:
import 'element-plus/dist/index.css'; // 引入Element Plus的样式
效果如下:
【PS:若有其它更完善的地方,请您大声说出来,欢迎大家一起探讨】