import type { AxiosRequestConfig, Method } from 'axios' import axios from 'axios' import { ElLoading, ElNotification, type LoadingOptions } from 'element-plus' import { refreshToken } from '@/api/user-boot/user' import router from '@/router/index' import { useAdminInfo } from '@/stores/adminInfo' window.requests = [] window.tokenRefreshing = false const pendingMap = new Map() const loadingInstance: LoadingInstance = { target: null, count: 0 } const VITE_FLAG = import.meta.env.VITE_NAME == 'removeMode' // console.log('🚀 ~ import.meta.env.VITE_NAME:', import.meta.env.VITE_NAME) /** * 根据运行环境获取基础请求URL */ export const getUrl = (): string => { return '/api' } function removeBeforeSecondSlash(str: string) { // 找到第一个斜杠的位置 const firstSlashIndex = str.indexOf('/') if (firstSlashIndex === -1) { // 如果没有斜杠,返回原字符串 return str } // 从第一个斜杠之后开始找第二个斜杠 const secondSlashIndex = str.indexOf('/', firstSlashIndex + 1) if (secondSlashIndex === -1) { // 如果只有一个斜杠,返回原字符串 return str } // 返回第二个斜杠及之后的内容 return str.substring(secondSlashIndex) } /** * 创建`Axios` * 默认开启`reductDataFormat(简洁响应)`,返回类型为`ApiPromise` * 关闭`reductDataFormat`,返回类型则为`AxiosPromise` */ function createAxios>( axiosConfig: AxiosRequestConfig, options: Options = {}, loading: LoadingOptions = {} ): T { const adminInfo = useAdminInfo() const Axios = axios.create({ baseURL: getUrl(), timeout: 1000 * 60 * 5, headers: {}, responseType: 'json' }) options = Object.assign( { CancelDuplicateRequest: true, // 是否开启取消重复请求, 默认为 true loading: false, // 是否开启loading层效果, 默认为false reductDataFormat: true, // 是否开启简洁的数据结构响应, 默认为true showErrorMessage: true, // 是否开启接口错误信息展示,默认为true showCodeMessage: true, // 是否开启code不为1时的信息提示, 默认为true showSuccessMessage: false, // 是否开启code为1时的信息提示, 默认为false anotherToken: '' // 当前请求使用另外的用户token }, options ) // 请求拦截 Axios.interceptors.request.use( async (config: any) => { //嵌入去除所有请求头 if (VITE_FLAG) { config.url = await removeBeforeSecondSlash(config.url) } // 取消重复请求 if ( !( config.url == '/system-boot/file/upload' || config.url == '/harmonic-boot/grid/getAssessOverview' || config.url == '/system-boot/file/getFileVO' || config.url == '/harmonic-boot/gridDiagram/getGridDiagramAreaData' ) ) removePending(config) options.CancelDuplicateRequest && addPending(config) // 创建loading实例 if (options.loading) { loadingInstance.count++ if (loadingInstance.count === 1) { loadingInstance.target = ElLoading.service(loading) } } // 自动携带token if (config.headers) { const token = adminInfo.getToken() if (token) { ;(config.headers as anyObj).Authorization = token } else { config.headers.Authorization = 'Basic bmpjbnRlc3Q6bmpjbnBxcw==' } } if (config.url == '/user-boot/user/generateSm2Key' || config.url == '/pqs-auth/oauth/token') { config.headers.Authorization = 'Basic bmpjbnRlc3Q6bmpjbnBxcw==' } return config }, error => { return Promise.reject(error) } ) // 响应拦截 Axios.interceptors.response.use( response => { removePending(response.config) options.loading && closeLoading(options) // 关闭loading if ( response.data.code === 'A0000' || response.data.type === 'application/json' || Array.isArray(response.data) || response.data.size || response.config.url == '/harmonic-boot/exportmodel/exportModelJB' || response.config.url == '/system-boot/file/download' || response.config.url == '/harmonic-boot/powerStatistics/exportExcelListTemplate' || response.config.url == '/harmonic-boot/powerStatistics/exportExcelRangTemplate' // || // response.data.type === 'application/octet-stream' || // response.data.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) { return options.reductDataFormat ? response.data : response } else if (response.data.code == 'A0202') { if (!window.tokenRefreshing) { window.tokenRefreshing = true return refreshToken() .then(res => { adminInfo.setToken(res.data.access_token, 'auth') window.requests.forEach(cb => cb(res.data.access_token)) window.requests = [] return Axios(response.config) }) .catch(err => { adminInfo.removeToken() router.push({ name: 'login' }) return Promise.reject(err) }) .finally(() => { window.tokenRefreshing = false }) } else { return new Promise(resolve => { // 用函数形式将 resolve 存入,等待刷新后再执行 window.requests.push((token: string) => { response.headers.Authorization = `${token}` resolve(Axios(response.config)) }) }) } } else if (response.data.code == 'A0024' || response.data.code == 'null') { // // 登录失效 ElNotification({ type: 'error', message: response.data.message }) adminInfo.removeToken() router.push({ name: 'login' }) return Promise.reject(response.data) } else { if (options.showCodeMessage) { ElNotification({ type: 'error', message: response.data.message || '未知错误' }) } return Promise.reject(response.data) } }, error => { error.config && removePending(error.config) options.loading && closeLoading(options) // 关闭loading return Promise.reject(error) // 错误继续返回给到具体页面 } ) return Axios(axiosConfig) as T } export default createAxios /** * 关闭Loading层实例 */ function closeLoading(options: Options) { if (options.loading && loadingInstance.count > 0) loadingInstance.count-- if (loadingInstance.count === 0) { loadingInstance.target.close() loadingInstance.target = null } } /** * 储存每个请求的唯一cancel回调, 以此为标识 */ function addPending(config: AxiosRequestConfig) { const pendingKey = getPendingKey(config) config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => { if (!pendingMap.has(pendingKey)) { pendingMap.set(pendingKey, cancel) } }) } /** * 删除重复的请求 */ function removePending(config: AxiosRequestConfig) { const pendingKey = getPendingKey(config) if (pendingMap.has(pendingKey)) { const cancelToken = pendingMap.get(pendingKey) cancelToken(pendingKey) pendingMap.delete(pendingKey) } } /** * 生成每个请求的唯一key */ function getPendingKey(config: AxiosRequestConfig) { let { data } = config const { url, method, params, headers } = config if (typeof data === 'string') data = JSON.parse(data) // response里面返回的config.data是个字符串对象 return [ url, method, headers && (headers as anyObj).Authorization ? (headers as anyObj).Authorization : '', headers && (headers as anyObj)['ba-user-token'] ? (headers as anyObj)['ba-user-token'] : '', JSON.stringify(params), JSON.stringify(data) ].join('&') } /** * 根据请求方法组装请求数据/参数 */ export function requestPayload(method: Method, data: anyObj, paramsPOST: boolean) { if (method == 'GET') { return { params: data } } else if (method == 'POST') { if (paramsPOST) { return { params: data } } else { return { data: data } } } } // 适配器, 用于适配不同的请求方式 export function baseRequest(url, value = {}, method = 'post', options = {}) { url = sysConfig.API_URL + url if (method === 'post') { return service.post(url, value, options) } else if (method === 'get') { return service.get(url, { params: value, ...options }) } else if (method === 'formdata') { // form-data表单提交的方式 return service.post(url, qs.stringify(value), { headers: { 'Content-Type': 'multipart/form-data' }, ...options }) } else { // 其他请求方式,例如:put、delete return service({ method: method, url: url, data: value, ...options }) } } // 模块内的请求, 会自动加上模块的前缀 export const moduleRequest = moduleUrl => (url, ...arg) => { return baseRequest(moduleUrl + url, ...arg) } interface LoadingInstance { target: any count: number } interface Options { // 是否开启取消重复请求, 默认为 true CancelDuplicateRequest?: boolean // 是否开启loading层效果, 默认为false loading?: boolean // 是否开启简洁的数据结构响应, 默认为true reductDataFormat?: boolean // 是否开启code不为A0000时的信息提示, 默认为true showCodeMessage?: boolean // 是否开启code为0时的信息提示, 默认为false showSuccessMessage?: boolean // 当前请求使用另外的用户token anotherToken?: string }