1.6.0 README.md update

This commit is contained in:
xiaoma
2021-12-24 10:48:49 +08:00
parent fc5e138ed8
commit 1ab8b5b221
25 changed files with 2848 additions and 2956 deletions

View File

@@ -48,7 +48,7 @@ export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) =
if (expire === null || expire >= Date.now()) {
return value;
}
this.remove(this.getKey(key));
this.remove(key);
} catch (e) {
return def;
}

View File

@@ -5,8 +5,8 @@ import { AxiosCanceler } from './axiosCancel';
import { isFunction } from '@/utils/is';
import { cloneDeep } from 'lodash-es';
import type { RequestOptions, CreateAxiosOptions, Result } from './types';
// import { ContentTypeEnum } from '/@/enums/httpEnum';
import type { RequestOptions, CreateAxiosOptions, Result, UploadFileParams } from './types';
import { ContentTypeEnum } from '@/enums/httpEnum';
export * from './axiosTransform';
@@ -23,18 +23,6 @@ export class VAxios {
this.setupInterceptors();
}
/**
* @description: 创建axios实例
*/
private createAxios(config: CreateAxiosOptions): void {
this.axiosInstance = axios.create(config);
}
private getTransform() {
const { transform } = this.options;
return transform;
}
getAxios(): AxiosInstance {
return this.axiosInstance;
}
@@ -59,6 +47,103 @@ export class VAxios {
Object.assign(this.axiosInstance.defaults.headers, headers);
}
/**
* @description: 请求方法
*/
request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
let conf: AxiosRequestConfig = cloneDeep(config);
const transform = this.getTransform();
const { requestOptions } = this.options;
const opt: RequestOptions = Object.assign({}, requestOptions, options);
const { beforeRequestHook, requestCatch, transformRequestData } = transform || {};
if (beforeRequestHook && isFunction(beforeRequestHook)) {
conf = beforeRequestHook(conf, opt);
}
//这里重新 赋值成最新的配置
// @ts-ignore
conf.requestOptions = opt;
return new Promise((resolve, reject) => {
this.axiosInstance
.request<any, AxiosResponse<Result>>(conf)
.then((res: AxiosResponse<Result>) => {
// 请求是否被取消
const isCancel = axios.isCancel(res);
if (transformRequestData && isFunction(transformRequestData) && !isCancel) {
try {
const ret = transformRequestData(res, opt);
resolve(ret);
} catch (err) {
reject(err || new Error('request error!'));
}
return;
}
resolve(res as unknown as Promise<T>);
})
.catch((e: Error) => {
if (requestCatch && isFunction(requestCatch)) {
reject(requestCatch(e));
return;
}
reject(e);
});
});
}
/**
* @description: 创建axios实例
*/
private createAxios(config: CreateAxiosOptions): void {
this.axiosInstance = axios.create(config);
}
private getTransform() {
const { transform } = this.options;
return transform;
}
/**
* @description: 文件上传
*/
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) {
const formData = new window.FormData();
const customFilename = params.name || 'file';
if (params.filename) {
formData.append(customFilename, params.file, params.filename);
} else {
formData.append(customFilename, params.file);
}
if (params.data) {
Object.keys(params.data).forEach((key) => {
const value = params.data![key];
if (Array.isArray(value)) {
value.forEach((item) => {
formData.append(`${key}[]`, item);
});
return;
}
formData.append(key, params.data![key]);
});
}
return this.axiosInstance.request<T>({
method: 'POST',
data: formData,
headers: {
'Content-type': ContentTypeEnum.FORM_DATA,
ignoreCancelToken: true,
},
...config,
});
}
/**
* @description: 拦截器配置
*/
@@ -78,10 +163,17 @@ export class VAxios {
// 请求拦截器配置处理
this.axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
const { headers: { ignoreCancelToken } = { ignoreCancelToken: false } } = config;
!ignoreCancelToken && axiosCanceler.addPending(config);
const {
headers: { ignoreCancelToken },
} = config;
const ignoreCancel =
ignoreCancelToken !== undefined
? ignoreCancelToken
: this.options.requestOptions?.ignoreCancelToken;
!ignoreCancel && axiosCanceler.addPending(config);
if (requestInterceptors && isFunction(requestInterceptors)) {
config = requestInterceptors(config);
config = requestInterceptors(config, this.options);
}
return config;
}, undefined);
@@ -105,62 +197,4 @@ export class VAxios {
isFunction(responseInterceptorsCatch) &&
this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch);
}
// /**
// * @description: 文件上传
// */
// uploadFiles(config: AxiosRequestConfig, params: File[]) {
// const formData = new FormData();
// Object.keys(params).forEach((key) => {
// formData.append(key, params[key as any]);
// });
// return this.request({
// ...config,
// method: 'POST',
// data: formData,
// headers: {
// 'Content-type': ContentTypeEnum.FORM_DATA,
// },
// });
// }
/**
* @description: 请求方法
*/
request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
let conf: AxiosRequestConfig = cloneDeep(config);
const transform = this.getTransform();
const { requestOptions } = this.options;
const opt: RequestOptions = Object.assign({}, requestOptions, options);
const { beforeRequestHook, requestCatch, transformRequestData } = transform || {};
if (beforeRequestHook && isFunction(beforeRequestHook)) {
conf = beforeRequestHook(conf, opt);
}
return new Promise((resolve, reject) => {
this.axiosInstance
.request<any, AxiosResponse<Result>>(conf)
.then((res: AxiosResponse<Result>) => {
// 请求是否被取消
const isCancel = axios.isCancel(res);
if (transformRequestData && isFunction(transformRequestData) && !isCancel) {
const ret = transformRequestData(res, opt);
// ret !== undefined ? resolve(ret) : reject(new Error('request error!'));
return resolve(ret);
}
reject(res as unknown as Promise<T>);
})
.catch((e: Error) => {
if (requestCatch && isFunction(requestCatch)) {
reject(requestCatch(e));
return;
}
reject(e);
});
});
}
}

View File

@@ -4,6 +4,12 @@
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
import type { RequestOptions, Result } from './types';
export interface CreateAxiosOptions extends AxiosRequestConfig {
authenticationScheme?: string;
transform?: AxiosTransform;
requestOptions?: RequestOptions;
}
export abstract class AxiosTransform {
/**
* @description: 请求之前处理配置
@@ -24,7 +30,10 @@ export abstract class AxiosTransform {
/**
* @description: 请求之前的拦截器
*/
requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig;
requestInterceptors?: (
config: AxiosRequestConfig,
options: CreateAxiosOptions
) => AxiosRequestConfig;
/**
* @description: 请求之后的拦截器

View File

@@ -1,46 +1,47 @@
export function checkStatus(status: number, msg: string, message: any): void {
export function checkStatus(status: number, msg: string): void {
const $message = window['$message'];
switch (status) {
case 400:
message.error(`${msg}`);
$message.error(msg);
break;
// 401: 未登录
// 未登录则跳转登录页面,并携带当前页面的路径
// 在登录成功后返回当前页面,这一步需要在登录页操作。
case 401:
message.error('用户没有权限(令牌、用户名、密码错误)!');
$message.error('用户没有权限(令牌、用户名、密码错误)!');
break;
case 403:
message.error('用户得到授权,但是访问是被禁止的。!');
$message.error('用户得到授权,但是访问是被禁止的。!');
break;
// 404请求不存在
case 404:
message.error('网络请求错误,未找到该资源!');
$message.error('网络请求错误未找到该资源!');
break;
case 405:
message.error('网络请求错误,请求方法未允许!');
$message.error('网络请求错误请求方法未允许!');
break;
case 408:
message.error('网络请求超时!');
$message.error('网络请求超时');
break;
case 500:
message.error('服务器错误,请联系管理员!');
$message.error('服务器错误,请联系管理员!');
break;
case 501:
message.error('网络未实现!');
$message.error('网络未实现');
break;
case 502:
message.error('网络错误!');
$message.error('网络错误');
break;
case 503:
message.error('服务不可用,服务器暂时过载或维护!');
$message.error('服务不可用,服务器暂时过载或维护!');
break;
case 504:
message.error('网络超时!');
$message.error('网络超时');
break;
case 505:
message.error('http版本不支持该请求!');
$message.error('http版本不支持该请求!');
break;
default:
message.error(msg);
$message.error(msg);
}
}

View File

@@ -36,7 +36,7 @@ export function formatRequestDate(params: Recordable) {
try {
params[key] = isString(value) ? value.trim() : value;
} catch (error) {
throw new Error(error);
throw new Error(error as any);
}
}
}

View File

@@ -10,9 +10,10 @@ import { PageEnum } from '@/enums/pageEnum';
import { useGlobSetting } from '@/hooks/setting';
import { isString } from '@/utils/is/';
import { deepMerge, isUrl } from '@/utils';
import { setObjToUrlParams } from '@/utils/urlUtils';
import { RequestOptions, Result } from './types';
import { RequestOptions, Result, CreateAxiosOptions } from './types';
import { useUserStoreWidthOut } from '@/store/modules/user';
@@ -30,8 +31,6 @@ const transform: AxiosTransform = {
* @description: 处理请求数据
*/
transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => {
// @ts-ignore
const { $message: Message, $dialog: Modal } = window;
const {
isShowMessage = true,
isShowErrorMessage,
@@ -52,15 +51,16 @@ const transform: AxiosTransform = {
return res.data;
}
const reject = Promise.reject;
const { data } = res;
const $dialog = window['$dialog'];
const $message = window['$message'];
if (!data) {
// return '[HTTP] Request has no return value';
return reject(data);
throw new Error('请求出错,请稍候重试');
}
// 这里 coderesultmessage为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
// 这里 coderesultmessage为 后台统一的字段,需要修改为项目自己的接口返回格式
const { code, result, message } = data;
// 请求成功
const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS;
@@ -68,13 +68,16 @@ const transform: AxiosTransform = {
if (isShowMessage) {
if (hasSuccess && (successMessageText || isShowSuccessMessage)) {
// 是否显示自定义信息提示
Message.success(successMessageText || message || '操作成功!');
$dialog.success({
type: 'success',
content: successMessageText || message || '操作成功!',
});
} else if (!hasSuccess && (errorMessageText || isShowErrorMessage)) {
// 是否显示自定义信息提示
Message.error(message || errorMessageText || '操作失败!');
$message.error(message || errorMessageText || '操作失败!');
} else if (!hasSuccess && options.errorMessageMode === 'modal') {
// errorMessageMode=custom-modal的时候会显示modal错误弹窗而不是消息提示用于一些比较重要的错误
Modal.info({
$dialog.info({
title: '提示',
content: message,
positiveText: '确定',
@@ -87,61 +90,49 @@ const transform: AxiosTransform = {
if (code === ResultEnum.SUCCESS) {
return result;
}
// 接口请求错误,统一提示错误信息
if (code === ResultEnum.ERROR) {
if (message) {
Message.error(data.message);
Promise.reject(new Error(message));
} else {
const msg = '操作失败,系统异常!';
Message.error(msg);
Promise.reject(new Error(msg));
}
return reject();
// 接口请求错误,统一提示错误信息 这里逻辑可以根据项目进行修改
let errorMsg = message;
switch (code) {
// 请求失败
case ResultEnum.ERROR:
$message.error(errorMsg);
break;
// 登录超时
case ResultEnum.TIMEOUT:
const LoginName = PageEnum.BASE_LOGIN_NAME;
const LoginPath = PageEnum.BASE_LOGIN;
if (router.currentRoute.value?.name === LoginName) return;
// 到登录页
errorMsg = '登录超时,请重新登录!';
$dialog.warning({
title: '提示',
content: '登录身份已失效,请重新登录!',
positiveText: '确定',
//negativeText: '取消',
closable: false,
maskClosable: false,
onPositiveClick: () => {
storage.clear();
window.location.href = LoginPath;
},
onNegativeClick: () => {},
});
break;
}
// 登录超时
if (code === ResultEnum.TIMEOUT) {
const LoginName = PageEnum.BASE_LOGIN_NAME;
if (router.currentRoute.value.name == LoginName) return;
// 到登录页
const timeoutMsg = '登录超时,请重新登录!';
Modal.warning({
title: '提示',
content: '登录身份已失效,请重新登录!',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: () => {
storage.clear();
router.replace({
name: LoginName,
query: {
redirect: router.currentRoute.value.fullPath,
},
});
},
onNegativeClick: () => {},
});
return reject(new Error(timeoutMsg));
}
// 这里逻辑可以根据项目进行修改
if (!hasSuccess) {
return reject(new Error(message));
}
return data;
throw new Error(errorMsg);
},
// 请求之前处理config
beforeRequestHook: (config, options) => {
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true } = options;
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
if (joinPrefix) {
const isUrlStr = isUrl(config.url as string);
if (!isUrlStr && joinPrefix) {
config.url = `${urlPrefix}${config.url}`;
}
if (apiUrl && isString(apiUrl)) {
if (!isUrlStr && apiUrl && isString(apiUrl)) {
config.url = `${apiUrl}${config.url}`;
}
const params = config.params || {};
@@ -158,7 +149,7 @@ const transform: AxiosTransform = {
} else {
if (!isString(params)) {
formatDate && formatRequestDate(params);
if (Reflect.has(config, 'data') && config.data && Object.keys(config.data).length) {
if (Reflect.has(config, 'data') && config.data && Object.keys(config.data).length > 0) {
config.data = data;
config.params = params;
} else {
@@ -183,13 +174,15 @@ const transform: AxiosTransform = {
/**
* @description: 请求拦截器处理
*/
requestInterceptors: (config) => {
requestInterceptors: (config, options) => {
// 请求之前处理config
const userStore = useUserStoreWidthOut();
const token = userStore.getToken;
if (token) {
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
// jwt token
config.headers.token = token;
(config as Recordable).headers.Authorization = options.authenticationScheme
? `${options.authenticationScheme} ${token}`
: token;
}
return config;
},
@@ -198,8 +191,8 @@ const transform: AxiosTransform = {
* @description: 响应错误处理
*/
responseInterceptorsCatch: (error: any) => {
// @ts-ignore
const { $message: Message, $dialog: Modal } = window;
const $dialog = window['$dialog'];
const $message = window['$message'];
const { response, code, message } = error || {};
// TODO 此处要根据后端接口返回格式修改
const msg: string =
@@ -207,57 +200,88 @@ const transform: AxiosTransform = {
const err: string = error.toString();
try {
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
Message.error('接口请求超时,请刷新页面重试!');
$message.error('接口请求超时请刷新页面重试!');
return;
}
if (err && err.includes('Network Error')) {
Modal.info({
$dialog.info({
title: '网络异常',
content: '请检查您的网络连接是否正常!',
content: '请检查您的网络连接是否正常',
positiveText: '确定',
//negativeText: '取消',
closable: false,
maskClosable: false,
onPositiveClick: () => {},
onNegativeClick: () => {},
});
return;
return Promise.reject(error);
}
} catch (error) {
throw new Error(error);
throw new Error(error as any);
}
// 请求是否被取消
const isCancel = axios.isCancel(error);
if (!isCancel) {
checkStatus(error.response && error.response.status, msg, Message);
checkStatus(error.response && error.response.status, msg);
} else {
console.warn(error, '请求被取消!');
}
return error;
//return Promise.reject(error);
return Promise.reject(response?.data);
},
};
const Axios = new VAxios({
timeout: 10 * 1000,
// 接口前缀
prefixUrl: urlPrefix,
headers: { 'Content-Type': ContentTypeEnum.JSON },
// 数据处理方式
transform,
// 配置项,下面的选项都可以在独立的接口请求中覆盖
requestOptions: {
// 默认将prefix 添加到url
joinPrefix: true,
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
isReturnNativeResponse: false,
// 需要对返回数据进行处理
isTransformResponse: true,
// post请求的时候添加参数到url
joinParamsToUrl: false,
// 格式化提交参数时间
formatDate: true,
// 消息提示类型
errorMessageMode: 'none',
// 接口地址
apiUrl: globSetting.apiUrl as string,
},
withCredentials: false,
});
function createAxios(opt?: Partial<CreateAxiosOptions>) {
return new VAxios(
deepMerge(
{
timeout: 10 * 1000,
authenticationScheme: '',
// 接口前缀
prefixUrl: urlPrefix,
headers: { 'Content-Type': ContentTypeEnum.JSON },
// 数据处理方式
transform,
// 配置项,下面的选项都可以在独立的接口请求中覆盖
requestOptions: {
// 默认将prefix 添加到url
joinPrefix: true,
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
isReturnNativeResponse: false,
// 需要对返回数据进行处理
isTransformResponse: true,
// post请求的时候添加参数到url
joinParamsToUrl: false,
// 格式化提交参数时间
formatDate: true,
// 消息提示类型
errorMessageMode: 'none',
// 接口地址
apiUrl: globSetting.apiUrl,
// 接口拼接地址
urlPrefix: urlPrefix,
// 是否加入时间戳
joinTime: true,
// 忽略重复请求
ignoreCancelToken: true,
// 是否携带token
withToken: true,
},
withCredentials: false,
},
opt || {}
)
);
}
export default Axios;
export const http = createAxios();
// 项目,多个不同 api 地址,直接在这里导出多个
// src/api ts 里面接口,就可以单独使用这个请求,
// import { httpTwo } from '@/utils/http/axios'
// export const httpTwo = createAxios({
// requestOptions: {
// apiUrl: 'http://localhost:9001',
// urlPrefix: 'api',
// },
// });

View File

@@ -2,9 +2,22 @@ import { AxiosRequestConfig } from 'axios';
import { AxiosTransform } from './axiosTransform';
export interface CreateAxiosOptions extends AxiosRequestConfig {
prefixUrl?: string;
transform?: AxiosTransform;
requestOptions?: RequestOptions;
authenticationScheme?: string;
}
// 上传文件
export interface UploadFileParams {
// 其他参数
data?: Recordable;
// 文件参数接口字段名
name?: string;
// 文件
file: File | Blob;
// 文件名称
filename?: string;
[key: string]: any;
}
export interface RequestOptions {
@@ -28,6 +41,8 @@ export interface RequestOptions {
joinPrefix?: boolean;
// 接口地址, 不填则使用默认apiUrl
apiUrl?: string;
// 请求拼接路径
urlPrefix?: string;
// 错误消息提示类型
errorMessageMode?: 'none' | 'modal';
// 是否添加时间戳
@@ -36,6 +51,10 @@ export interface RequestOptions {
isTransformResponse?: boolean;
// 是否返回原生响应头
isReturnNativeResponse?: boolean;
//忽略重复请求
ignoreCancelToken?: boolean;
// 是否携带token
withToken?: boolean;
}
export interface Result<T = any> {

View File

@@ -206,3 +206,10 @@ export function lighten(color: string, amount: number) {
amount
)}${addLight(color.substring(4, 6), amount)}`;
}
/**
* 判断是否 url
* */
export function isUrl(url: string) {
return /(^http|https:\/\/)/g.test(url);
}