现有的像是vben-admin,pure-admin,这些的后台模版,都有各自的 Axios 封装,这两个框架我都使用了很久,在使用的过程中,虽然已经很便利了;VbenAdmin 对请求的状态控制划分的比较细、清晰明了;PureAdmin 又比较简洁;能不能综合下各自的优势呢?
需求
- 整合
VbenAdmin和PureAdmin中的Axios的封装
用法
export interface PureHttpResponse extends AxiosResponse<Result> {
config: PureHttpRequestConfig;
}
export interface PureHttpRequestConfig extends AxiosRequestConfig {
beforeRequestCallback?: (request: PureHttpRequestConfig) => void;
beforeResponseCallback?: (response: PureHttpResponse) => void;
}
export default class PureHttp {
request<T>(
method: RequestMethods,
url: string,
param?: AxiosRequestConfig,
axiosConfig?: PureHttpRequestConfig
): Promise<T>;
post<T, P>(
url: string,
params?: T,
config?: PureHttpRequestConfig
): Promise<P>;
get<T, P>(
url: string,
params?: T,
config?: PureHttpRequestConfig
): Promise<P>;
delete<T, P>(
url: string,
params?: T,
config?: PureHttpRequestConfig
): Promise<P>;
put<T, P>(
url: string,
params?: T,
config?: PureHttpRequestConfig
): Promise<P>;
patch<T, P>(
url: string,
params?: T,
config?: PureHttpRequestConfig
): Promise<P>;
}
实现效果
import { http } from "@/utils/http";
import { prefix } from "./prefix";
import type { ConfigVO, SmsConfig, DigitalVirtualParams, ReturnDigitalVirtualData } from './props'
// 查询平台配置
export const findConfig = () =>
http.request<ConfigVO>("get", `${prefix.sys}/config`);
// 修改短信平台配置
export const updateSmsConfig = (data: SmsConfig) =>
http.post<SmsConfig, any>(`${prefix.sys}/config/sms`, {
data
});
// 数字虚拟人总览接口
export const getDigitalVirtualData = (params: DigitalVirtualParams) =>
http.get<DigitalVirtualParams, ReturnDigitalVirtualData>(
`${prefix.robot}/digital-human/overview/total`,
{
params
}
);
代码
- 目录结构
index.ts
import Axios, {
AxiosInstance,
AxiosRequestConfig,
CustomParamsSerializer
} from "axios";
import {
PureHttpError,
RequestMethods,
PureHttpResponse,
PureHttpRequestConfig
} from "./types.d";
import { stringify } from "qs";
import NProgress from "../progress";
import { getToken, formatToken } from "@/utils/auth";
import { handleSystemStatus, handlerAbnormalCode } from "./status";
// 相关配置请参考:www.axios-js.com/zh-cn/docs/#axios-request-config-1
const defaultConfig: AxiosRequestConfig = {
baseURL: import.meta.env.MODE === "production" ? window.API : "",
// 请求超时时间
timeout: 10000,
headers: {
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest"
},
// 数组格式参数序列化(https://github.com/axios/axios/issues/5142)
paramsSerializer: {
serialize: stringify as unknown as CustomParamsSerializer
}
};
const whiteList = ["/refreshToken", "/api-sys/oauth2/web/token"];
class PureHttp {
[x: string]: any;
constructor() {
this.httpInterceptorsRequest();
this.httpInterceptorsResponse();
}
/** token过期后,暂存待执行的请求 */
private static requests = [];
/** 防止重复刷新token */
private static isRefreshing = false;
/** 初始化配置对象 */
private static initConfig: PureHttpRequestConfig = {};
/** 保存当前Axios实例对象 */
private static axiosInstance: AxiosInstance = Axios.create(defaultConfig);
/** 重连原始请求 */
private static retryOriginalRequest(config: PureHttpRequestConfig) {
return new Promise(resolve => {
PureHttp.requests.push((token: string) => {
config.headers["Authorization"] = formatToken(token);
resolve(config);
});
});
}
/** 请求拦截 */
private httpInterceptorsRequest(): void {
PureHttp.axiosInstance.interceptors.request.use(
async (config: PureHttpRequestConfig): Promise<any> => {
// 开启进度条动画
NProgress.start();
// 优先判断post/get等方法是否传入回调,否则执行初始化设置等回调
if (typeof config.beforeRequestCallback === "function") {
config.beforeRequestCallback(config);
return config;
}
if (PureHttp.initConfig.beforeRequestCallback) {
PureHttp.initConfig.beforeRequestCallback(config);
return config;
}
/** 请求白名单,放置一些不需要token的接口(通过设置请求白名单,防止token过期后再请求造成的死循环问题) */
return whiteList.find(url => url === config.url)
? config
: new Promise(resolve => {
const token = getToken();
config.headers["Authorization"] = formatToken(token);
resolve(config);
});
},
error => {
return Promise.reject(error);
}
);
}
/** 响应拦截 */
private httpInterceptorsResponse(): void {
const instance = PureHttp.axiosInstance;
instance.interceptors.response.use(
(response: PureHttpResponse) => {
const $config = response.config;
// 关闭进度条动画
NProgress.done();
// 优先判断post/get等方法是否传入回调,否则执行初始化设置等回调
if (typeof $config.beforeResponseCallback === "function") {
$config.beforeResponseCallback(response);
return response.data;
}
if (PureHttp.initConfig.beforeResponseCallback) {
PureHttp.initConfig.beforeResponseCallback(response);
return response.data;
}
const { code, msg, data: result } = response.data;
const isWhite = whiteList.find(url => url === response.config.url);
// 白名单直接处理且须有返回
if (isWhite) {
handlerAbnormalCode(code, msg);
return {
...result,
status: code === "000000" ? 1 : 0
};
}
if (code !== "000000") {
handlerAbnormalCode(code, msg);
return Promise.reject("异常!");
}
// 返回结果
return result;
},
(error: PureHttpError) => {
// 关闭进度条动画
NProgress.done();
const $error = error;
$error.isCancelRequest = Axios.isCancel($error);
const { code, message } = error;
const err: string = error?.toString?.() ?? "";
// 网络超时
if (code === "ECONNABORTED" && message.indexOf("timeout") !== -1) {
handleSystemStatus("ECONNABORTED");
} else if (err?.includes("Network Error")) {
// 网络错误
handleSystemStatus("Network Error");
} else {
handleSystemStatus(error?.response?.status);
}
// 所有的响应异常 区分来源为取消请求/非取消请求
return Promise.reject($error);
}
);
}
/** 通用请求工具函数 */
public request<T>(
method: RequestMethods,
url: string,
param?: AxiosRequestConfig,
axiosConfig?: PureHttpRequestConfig
): Promise<T> {
const config = {
method,
url,
...param,
...axiosConfig
} as PureHttpRequestConfig;
// 单独处理自定义请求/响应回调
return new Promise((resolve, reject) => {
PureHttp.axiosInstance
.request(config)
.then((response: undefined) => {
resolve(response);
})
.catch(error => {
reject(error);
});
});
}
/** 单独抽离的post工具函数 */
public post<T, P>(
url: string,
params?: AxiosRequestConfig<T>,
config?: PureHttpRequestConfig
): Promise<P> {
return this.request<P>("post", url, params, config);
}
/** 单独抽离的get工具函数 */
public get<T, P>(
url: string,
params?: AxiosRequestConfig<T>,
config?: PureHttpRequestConfig
): Promise<P> {
return this.request<P>("get", url, params, config);
}
/** 单独抽离的delete工具函数 */
public delete<T, P>(
url: string,
params?: AxiosRequestConfig<T>,
config?: PureHttpRequestConfig
): Promise<P> {
return this.request<P>("delete", url, params, config);
}
/** 单独抽离的put工具函数 */
public put<T, P>(
url: string,
params?: AxiosRequestConfig<T>,
config?: PureHttpRequestConfig
): Promise<P> {
return this.request<P>("put", url, params, config);
}
/** 单独抽离的patch工具函数 */
public patch<T, P>(
url: string,
params?: AxiosRequestConfig<T>,
config?: PureHttpRequestConfig
): Promise<P> {
return this.request<P>("patch", url, params, config);
}
}
export const http = new PureHttp();
status.ts
import sys from "./enum";
import { removeToken } from "../auth";
import { ElMessageBox } from "element-plus";
import { useUserStoreHook } from "@/store/modules/user";
import { message } from "../message";
// 创建队列
export function handleSystemStatus(
status: number | string,
msg?: string
): void {
if (status !== 401) {
let errMessage = "";
switch (status) {
case 400:
errMessage = `${msg}`;
break;
case 403:
errMessage = sys.api.errMsg403;
break;
// 404请求不存在
case 404:
errMessage = sys.api.errMsg404;
break;
case 405:
errMessage = sys.api.errMsg405;
break;
case 408:
errMessage = sys.api.errMsg408;
break;
case 500:
errMessage = sys.api.errMsg500;
break;
case 501:
errMessage = sys.api.errMsg501;
break;
case 502:
errMessage = sys.api.errMsg502;
break;
case 503:
errMessage = sys.api.errMsg503;
break;
case 504:
errMessage = sys.api.errMsg504;
break;
case 505:
errMessage = sys.api.errMsg505;
break;
case "Network Error":
errMessage = sys.api.networkExceptionMsg;
break;
case "ECONNABORTED":
errMessage = sys.api.apiTimeoutMessage;
break;
case "ABNORMAL":
errMessage = sys.api.apiRequestFailed;
break;
default:
}
// 存在错误信息就提示
errMessage &&
message(errMessage, {
type: "error"
});
return;
}
ElMessageBox.alert("用户登录已过期, 确定跳转登录页吗?", "过期提示", {
confirmButtonText: "确定",
callback: () => {
removeToken();
location.reload();
}
});
}
// 异常状态码
export const CodeEnum = ["103107", "103104", "103105", "103109", "103102"];
// 异常状态码处理
export const handlerAbnormalCode = (code: string, msg?: string): void => {
// 用户名密码错误多次提交需要多次提示 '103111', '291003'
const userStore = useUserStoreHook();
switch (code) {
case "103107":
ElMessageBox.alert(
"该用户已在其他地方登录, 确定强制登录吗?",
"登录提示",
{
confirmButtonText: "确定",
callback: () => {
userStore.handleForceLogin();
}
}
);
break;
case "103104":
ElMessageBox.alert(
"该用户已在其他地方登录, 确定重新登录吗?",
"登录提示",
{
confirmButtonText: "确定",
callback: () => {
userStore.expiredLogin();
}
}
);
break;
case "103105":
ElMessageBox.alert(
"该用户密码已过期, 确定跳转修改密码页吗?",
"过期提示",
{
confirmButtonText: "确定",
callback: async () => {
userStore.handleJumpToModifyPage();
}
}
);
break;
case "103109":
ElMessageBox.alert("该用户权限已变更, 确定跳转登录页吗?", "变更提示", {
confirmButtonText: "确定",
callback: () => {
userStore.expiredLogin();
}
});
break;
case "103102":
message("登录已失效,请重新登录!", {
type: "error"
});
userStore.expiredLogin();
break;
default:
msg &&
message(msg, {
type: "error"
});
}
};
enum.ts
export default {
api: {
operationFailed: "操作失败",
errorTip: "错误提示",
timeoutMessage: "登录超时,请重新登录!",
apiTimeoutMessage: "接口请求超时,请刷新页面重试!",
apiRequestFailed: "请求出错,请稍候重试",
networkException: "网络异常",
networkExceptionMsg: "网络异常,请检查您的网络连接是否正常!",
errMsg401: "用户没有权限(令牌、用户名、密码错误)!",
errMsg403: "用户得到授权,但是访问是被禁止的。!",
errMsg404: "网络请求错误,未找到该资源!",
errMsg405: "网络请求错误,请求方法未允许!",
errMsg408: "网络请求超时!",
errMsg500: "服务器错误,请联系管理员!",
errMsg501: "网络未实现!",
errMsg502: "网络错误!",
errMsg503: "服务不可用,服务器暂时过载或维护!",
errMsg504: "网络超时!",
errMsg505: "http版本不支持该请求!"
},
app: {
logoutTip: "温馨提醒",
logoutMessage: "是否确认退出系统?",
menuLoading: "菜单加载中..."
},
errorLog: {
tableTitle: "错误日志列表",
tableColumnType: "类型",
tableColumnDate: "时间",
tableColumnFile: "文件",
tableColumnMsg: "错误信息",
tableColumnStackMsg: "stack信息",
tableActionDesc: "详情",
modalTitle: "错误详情",
fireVueError: "点击触发vue错误",
fireResourceError: "点击触发资源加载错误",
fireAjaxError: "点击触发ajax错误",
enableMessage:
"只在`/src/settings/projectSetting.ts` 内的useErrorHandle=true时生效."
},
exception: {
backLogin: "返回登录",
backHome: "返回首页",
subTitle403: "抱歉,您无权访问此页面。",
subTitle404: "抱歉,您访问的页面不存在。",
subTitle500: "抱歉,服务器报告错误。",
noDataTitle: "当前页无数据",
networkErrorTitle: "网络错误",
networkErrorSubTitle: "抱歉,您的网络连接已断开,请检查您的网络!"
},
lock: {
unlock: "点击解锁",
alert: "锁屏密码错误",
backToLogin: "返回登录",
entry: "进入系统",
placeholder: "请输入锁屏密码或者用户密码"
},
login: {
backSignIn: "返回",
signInFormTitle: "登录",
mobileSignInFormTitle: "手机登录",
qrSignInFormTitle: "二维码登录",
signUpFormTitle: "注册",
forgetFormTitle: "重置密码",
signInTitle: "开箱即用的中后台管理系统",
signInDesc: "输入您的个人详细信息开始使用!",
policy: "我同意xxx隐私政策",
scanSign: `扫码后点击"确认",即可完成登录`,
loginButton: "登录",
registerButton: "注册",
rememberMe: "记住我",
forgetPassword: "忘记密码?",
otherSignIn: "其他登录方式",
// notify
loginSuccessTitle: "登录成功",
loginSuccessDesc: "欢迎回来",
// placeholder
accountPlaceholder: "请输入账号",
passwordPlaceholder: "请输入密码",
smsPlaceholder: "请输入验证码",
mobilePlaceholder: "请输入手机号码",
policyPlaceholder: "勾选后才能注册",
diffPwd: "两次输入密码不一致",
userName: "账号",
password: "密码",
confirmPassword: "确认密码",
email: "邮箱",
smsCode: "短信验证码",
mobile: "手机号码"
}
};
/**
* @description: request method
*/
export enum RequestEnum {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE"
}
/**
* @description: contentTyp
*/
export enum ContentTypeEnum {
// json
JSON = "application/json;charset=UTF-8",
// form-data qs
FORM_URLENCODED = "application/x-www-form-urlencoded;charset=UTF-8",
// form-data upload
FORM_DATA = "multipart/form-data;charset=UTF-8"
}
types.d.ts
import Axios, {
Method,
AxiosError,
AxiosResponse,
AxiosRequestConfig
} from "axios";
export type resultType = {
accessToken?: string;
};
export type RequestMethods = Extract<
Method,
"get" | "post" | "put" | "delete" | "patch" | "option" | "head"
>;
export interface PureHttpError extends AxiosError {
isCancelRequest?: boolean;
}
export interface Result<T = any> {
code: string;
data: T;
msg: string;
}
export interface PureHttpResponse extends AxiosResponse<Result> {
config: PureHttpRequestConfig;
}
export interface PureHttpRequestConfig extends AxiosRequestConfig {
beforeRequestCallback?: (request: PureHttpRequestConfig) => void;
beforeResponseCallback?: (response: PureHttpResponse) => void;
}
export default class PureHttp {
request<T>(
method: RequestMethods,
url: string,
param?: AxiosRequestConfig,
axiosConfig?: PureHttpRequestConfig
): Promise<T>;
post<T, P>(
url: string,
params?: T,
config?: PureHttpRequestConfig
): Promise<P>;
get<T, P>(
url: string,
params?: T,
config?: PureHttpRequestConfig
): Promise<P>;
delete<T, P>(
url: string,
params?: T,
config?: PureHttpRequestConfig
): Promise<P>;
put<T, P>(
url: string,
params?: T,
config?: PureHttpRequestConfig
): Promise<P>;
patch<T, P>(
url: string,
params?: T,
config?: PureHttpRequestConfig
): Promise<P>;
}