/**
* HTTP请求工具
* 封装axios,处理请求拦截、响应拦截、错误处理和401授权问题
*/
import axios, {
type AxiosError,
type AxiosInterceptorManager,
type AxiosRequestConfig,
type AxiosRequestHeaders,
type AxiosResponse,
type InternalAxiosRequestConfig,
} from 'axios';
import queryString from 'query-string';
import { getSession, removeAllCookie, setSession } from './storage';
import { getAccessToken, removeAccessToken } from './token';
import { getConfig } from '../configure';
import { gotoPage } from './router';
import { getResponse } from './common';
import notification from '../components/Notification';
import { ElMessageBox } from 'element-plus';
import { intl } from '../i18n';
/**
* Modal对话框工具
* 封装Element Plus的ElMessageBox组件
*/
const Modal = {
/**
* 打开模态对话框
* @param {Object} options - 对话框配置选项
* @returns {Promise} - 返回对话框实例
*/
open: (options: {
title?: string;
children?: string;
cancelText?: string;
okText?: string;
onOk?: () => void;
onCancel?: () => void;
afterClose?: () => void;
key?: string;
}) => {
return ElMessageBox({
title: options.title || '提示',
message: options.children || '',
showCancelButton: !!options.cancelText,
confirmButtonText: options.okText || '确定',
cancelButtonText: options.cancelText || '取消',
type: 'warning',
callback: (action: 'confirm' | 'cancel' | 'close') => {
if (action === 'confirm' && typeof options.onOk === 'function') {
options.onOk();
} else if (action === 'cancel' && typeof options.onCancel === 'function') {
options.onCancel();
}
if (typeof options.afterClose === 'function') {
options.afterClose();
}
},
});
},
close: () => {
ElMessageBox.close();
},
/**
* 更新当前打开的对话框
* @param {Object} props - 要更新的属性
*/
update: (props: Record<string, unknown>) => {
// Element Plus的对话框不直接支持更新,这里提供一个兼容接口
console.log('Modal.update called with props:', props);
},
};
// 常量定义
const BASE_PATH = '';
const INDEX_ROUTE_PATH = '/';
/**
* 重定向到登录页
* @param {Object} options - 配置选项
* @param {string} options.loginPageUrl - 可选的登录页URL
*/
export function redirectToLogin({ loginPageUrl }: { loginPageUrl?: string } = {}) {
removeAccessToken();
removeAllCookie();
const {
request: { baseURL: API_HOST },
auth: { oauthUrl },
} = getConfig();
const LOGIN_URL = loginPageUrl || `${API_HOST}${oauthUrl}`;
const sessionData = getSession('redirectUrl');
let cacheLocation = sessionData;
if (!cacheLocation) {
cacheLocation = encodeURIComponent(`${window.location.origin}${BASE_PATH || '/'}`);
}
// const modifyReloginLocation = getConfig().auth?.modifyReloginLocation;
// if (modifyReloginLocation) {
// cacheLocation = modifyReloginLocation(cacheLocation, {
// BASE_PATH: BASE_PATH || '/',
// });
// }
// 构建重定向URL
const redirectParams = getSession('templateParams') || '';
if (LOGIN_URL.includes('?')) {
window.location.href = `${LOGIN_URL}&redirect_uri=${cacheLocation}${redirectParams}`;
} else {
window.location.href = `${LOGIN_URL}?redirect_uri=${cacheLocation}${redirectParams}`;
}
}
// 创建带token的axios实例
const cubeAxios = axios.create();
// 创建不带token的axios实例
const notWithTokenAxios = axios.create();
notWithTokenAxios.interceptors.request.use((config) => {
const {
request: { baseURL: API_HOST },
} = getConfig();
let { url = '' } = config;
if (url.indexOf('://') === -1 && !url.startsWith('/_api')) {
url = `${API_HOST}${url}`;
}
return {
...config,
url,
};
});
// 401错误标志,防止重复处理401
let isErrorFlag = false;
/**
* 鉴权拦截处理
* @param {number} status - HTTP状态码
* @param {InternalAxiosRequestConfig} config - 请求配置
* @returns {boolean} - 是否继续处理响应
*/
function authIntercept(status: number, config: InternalAxiosRequestConfig) {
if (status === 401) {
// 避免重复处理401
if (isErrorFlag) {
return false;
}
/**
* 设置重定向URL到会话存储
*/
const setRedirectUrl = () => {
let _cacheLocation = window.location.toString().replace('/unauthorized', '');
// @ts-expect-error 全局窗口对象可能包含routerBase属性
const basePath = (window.routerBase || BASE_PATH)?.replace(/\/$/, '');
const url1 = new URL(_cacheLocation);
let p = url1.pathname;
if (basePath && p.startsWith(basePath)) {
p = p.replace(basePath, '');
}
if (p === '/') {
url1.pathname = `${basePath}${INDEX_ROUTE_PATH}`;
_cacheLocation = url1.toString();
}
const cacheLocation = encodeURIComponent(_cacheLocation);
const searchParams = queryString.parse(window.location.search)?.template;
const templateParams = searchParams ? `&template=${searchParams}` : '';
setSession('templateParams', templateParams);
setSession('redirectUrl', cacheLocation);
};
isErrorFlag = true;
const { url = '' } = config;
const {
user: { getUserInfoAxiosConfig },
} = getConfig();
let AUTH_SELF_URL = '/Admin/User/Info';
if (typeof getUserInfoAxiosConfig === 'function') {
const config = getUserInfoAxiosConfig();
if (!(config instanceof Promise)) {
AUTH_SELF_URL = config.url || AUTH_SELF_URL;
}
} else {
AUTH_SELF_URL = getUserInfoAxiosConfig.url || AUTH_SELF_URL;
}
const isSelf401 = url.includes(AUTH_SELF_URL);
if (isSelf401) {
setRedirectUrl();
redirectToLogin();
return false;
}
/**
* 跳转到未授权页面
* @param {string} pageUrl - 未授权页面路径
*/
const redirectToUnauthorized = (pageUrl = '/unauthorized') => {
const language = intl.getLocale()?.replace('-', '_');
// 登录后需要跳回的界面,放到session中
if (!window.location.pathname.startsWith(`${BASE_PATH}${pageUrl.replace(/^\//, '')}`)) {
setRedirectUrl();
}
// token失效,跳转到token失效页面
gotoPage(`${pageUrl}?language=${language}${getSession('templateParams') || ''}`);
};
// 当位于/unauthorized页面时,不处理401
const isInUnauthorizedPage = window.location.toString().indexOf('/unauthorized') !== -1;
if (isInUnauthorizedPage) {
return false;
}
setTimeout(() => {
redirectToUnauthorized();
}, 100);
}
return true;
}
/**
* 响应错误处理
* @param {Object} error - 错误对象
* @returns {Promise} - 处理后的Promise
*/
function handleResponseError(error: AxiosError) {
// 移除debugger语句,优化错误日志记录
console.error('Request error:', error.message, error.config?.url);
const { response, config, code } = error;
// 如果response为空,直接返回
if (!response) {
if (code === 'ERR_NETWORK') {
notification.error({ message: intl.get('notification.network.typeError').d('网络请求异常') });
return;
}
return Promise.reject(error);
}
const envConfig = getConfig();
// 判断是否鉴权问题
if (response && response.status && !authIntercept(response.status, response.config)) {
return Promise.reject(false);
}
// 状态204当做成功处理
if (response?.status === 204) {
return undefined;
}
// 响应拦截,请求时设置
const responseIntercept = envConfig.request.responseIntercept;
if (responseIntercept && typeof responseIntercept === 'function') {
responseIntercept(error);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data = response?.data as any;
const errorObj = {
type: undefined,
message: error.message,
// @ts-expect-error 包含描述的可能的字段
description: `${data?.description || data?.content || response?.requestMessage || ''}`,
};
if (data && data.code > 200) {
errorObj.message =
data.message || data.code || data.type || intl.get('cube.notification.failed').d('操作失败');
// errorObj.type === data.type;
}
if (data && data.detailsMessage) {
errorObj.description += `\n ${data.detailsMessage}`;
}
if (errorObj.message === 'Network Error') {
errorObj.message = intl.get('notification.network.typeError').d('网络请求异常');
errorObj.description = intl.get('notification.typeError.description').d('请稍后重试');
}
if (response.status === 200) {
getResponse(data);
} else if (response.status) {
notification.autoNotification(
errorObj?.type || 'error',
errorObj?.message,
errorObj?.description,
);
}
throw error;
}
/**
* 处理成功响应
* @param {Object} response - 响应对象
* @returns {any} - 处理后的响应数据,默认返回data
*/
function handleResponseSuccess(response: AxiosResponse) {
const { status, data, config } = response;
if (config.responseType === 'text' && typeof data !== 'string') {
return JSON.stringify(data);
}
// 响应拦截
const responseIntercept = getConfig().request.responseIntercept;
if (responseIntercept && typeof responseIntercept === 'function') {
responseIntercept(response);
}
// 异常响应拦截
if (data && typeof data === 'object' && 'code' in data && data.code !== 0 && data.code !== 200) {
// 处理业务逻辑错误
const errorCode = data.code;
const errorMessage = data.message || intl.get('error.unknown').d('未知错误');
// 输出业务错误日志,但仍然返回响应以供业务代码处理
console.warn(`业务错误 [${errorCode}]: ${errorMessage}`, data);
}
return data;
}
cubeAxios.interceptors.response.use(handleResponseSuccess, handleResponseError);
/**
* 请求拦截器
* @param {Object} config - 请求配置
* @returns {Object} - 处理后的请求配置
*/
function handleRequestConfig(config: InternalAxiosRequestConfig) {
const {
request: { baseURL: API_HOST },
} = getConfig();
let { url = '' } = config || {};
if (url.indexOf('://') === -1 && !url.startsWith('/_api')) {
url = `${API_HOST}${url}`;
}
// 添加额外的请求头
const additionalRequestHeaderConfig = getConfig().request.additionalRequestHeader;
let additionalRequestHeader: Record<string, string> = {};
if (additionalRequestHeaderConfig) {
additionalRequestHeader =
typeof additionalRequestHeaderConfig === 'function'
? additionalRequestHeaderConfig()
: additionalRequestHeaderConfig;
}
const newOptions: InternalAxiosRequestConfig = {
...config,
url,
withCredentials: true,
headers: {
Authorization: `bearer ${getAccessToken()}`,
...additionalRequestHeader,
...(config?.headers || {}),
} as AxiosRequestHeaders,
};
return newOptions;
}
/**
* 请求错误处理
* @param {Object} error - 错误对象
* @returns {Promise} - 处理后的Promise
*/
async function handleRequestError(error: AxiosError) {
const { config } = error;
console.error('Request processing error:', error.message);
return Promise.reject(error);
}
const requestHandlers = [handleRequestConfig, handleRequestError];
cubeAxios.interceptors.request.use(handleRequestConfig, handleRequestError);
// 导出配置好的axios实例
export const request = cubeAxios;
export default request;
export { cubeAxios };
// 替换原来导出的toReLogin
export { redirectToLogin as toReLogin };
|