This commit is contained in:
ray_wuhao 2023-06-20 13:37:40 +08:00
parent 54d370c7a0
commit eaf6f16132
8 changed files with 289 additions and 62 deletions

View File

@ -1,5 +1,19 @@
# CHANGE LOG
## 3.3.6
### Feats
- 重写 axios interceptor 方法。现在逻辑更加清晰,并且支持请求错误、响应错误处理。补充了两个工具函数
- MenuTag 支持动态更新所在位置
- 修复了鉴权方法的 bug
- 更新了 router permission 方法(路由守卫)
- 补充了一些模块文档
### 补充
> 文档拖欠太多了,我补不回来了,就。。。算了吧,我在每个关键模块补充了对应的 md 说明文档,凑合一下吧。真希望有一个好心人帮补充文档。
## 3.3.5
### Feats

71
src/axios/README.md Normal file
View File

@ -0,0 +1,71 @@
## 说明
> axios 包,全局的 axios 使用入口
> `src/axios/instance.ts` 文件为 axios 实例文件,应该不可更改,如果需要有拦截器相关的操作,应该在 `src/axios/inject` 文件下按照需求在对应注册器中进行注册相关方法。该项目将 axios instance 与 axios interceptor 进行解耦,避免实例文件的臃肿。
## 工具函数
- BeforeFetchFunction
- FetchErrorFunction
> 两个工具函数方便类型推导。
## 约束
### 拦截器添加
> 这里以请求拦截器添加为示例
```ts
/**
*
* 拦截器说明
*
* 注册一个根据系统当前环境是否携带测试环境请求拦截器
*/
import { appendRequestHeaders } from '@/axios/helper/axiosCopilot'
import type {
RequestInterceptorConfig,
BeforeFetchFunction,
FetchErrorFunction,
} from '@/axios/type'
const injectRequestHeaderOfEnv: BeforeFetchFunction<
RequestInterceptorConfig
> = (ins, mode) => {
if (mode === 'development') {
appendRequestHeaders(ins, [
{
key: 'Development-Mode',
value: 'development',
},
])
}
}
/**
*
* 在 setupRequestInterceptor 中注册
*/
export const setupRequestInterceptor = () => {
setImplement(
'implementRequestInterceptorArray',
[injectRequestHeaderOfEnv],
'ok',
)
}
/** 至此完成了请求拦截器的注册 */
```
### 注册器
> 每个类型注册器都有两个方法,用于注册拦截器方法。都以 setupXXX 开头命名。注册器以队列形式管理拦截器方法,所以可能需要注意执行顺序。如果有新的拦截器方法,需要根据其使用场景在对应的注册器中注册(注册器第二个参数中注册)。
- 请求注册器: setupRequestInterceptor
- 请求错误注册器: setupRequestErrorInterceptor
- 响应注册器: setupResponseInterceptor
- 响应错误注册器: setupResponseErrorInterceptor

View File

@ -16,7 +16,8 @@
*
*
*
* create , emmmmmm
* axios ,
* 使,
*/
import RequestCanceler from '@/axios/helper/canceler'
@ -25,73 +26,116 @@ import { getAppEnvironment } from '@use-utils/hook'
import type {
RequestInterceptorConfig,
ResponseInterceptorConfig,
ImplementKey,
ImplementQueue,
ErrorImplementQueue,
FetchType,
} from '@/axios/type'
/** 当前请求的实例 */
const axiosFetchInstance = {
requestInstance: null as RequestInterceptorConfig | null,
responseInstance: null as ResponseInterceptorConfig | null,
}
const axiosFetchError = {
requestError: null as null | unknown,
responseError: null as null | unknown,
}
/** 请求队列(区分 reslove 与 reject 状态) */
const implement: ImplementQueue = {
implementRequestInterceptorArray: [],
implementResponseInterceptorArray: [],
}
const errorImplement: ErrorImplementQueue = {
implementRequestInterceptorErrorArray: [],
implementResponseInterceptorErrorArray: [],
}
/** 取消器实例 */
export const axiosCanceler = new RequestCanceler()
export const useAxiosInterceptor = () => {
const getImplementKey = (key: ImplementKey) => {
return key === 'requestInstance'
? 'implementRequestInterceptorArray'
: 'implementResponseInterceptorArray'
/** 创建拦截器实例 */
const createAxiosInstance = (
instance: RequestInterceptorConfig | ResponseInterceptorConfig,
instanceKey: keyof typeof axiosFetchInstance,
) => {
instanceKey === 'requestInstance'
? (axiosFetchInstance['requestInstance'] =
instance as RequestInterceptorConfig)
: (axiosFetchInstance['responseInstance'] =
instance as ResponseInterceptorConfig)
}
const createRequestAxiosInstance = (instance: RequestInterceptorConfig) => {
axiosFetchInstance['requestInstance'] = instance
/** 获取当前实例 */
const getAxiosInstance = (instanceKey: keyof typeof axiosFetchInstance) => {
return axiosFetchInstance[instanceKey]
}
const createResponseAxiosInstance = (instance: ResponseInterceptorConfig) => {
axiosFetchInstance['responseInstance'] = instance
/** 设置注入方法队列 */
const setImplement = (
key: keyof ImplementQueue | keyof ErrorImplementQueue,
func: AnyFunc[],
fetchType: FetchType,
) => {
fetchType === 'ok' ? (implement[key] = func) : (errorImplement[key] = func)
}
/** 获取请求实例或者响应实例 */
const getAxiosFetchInstance = (key: ImplementKey) => {
return axiosFetchInstance[key]
/** 获取队列中所有的所有拦截器方法 */
const getImplement = (
key: keyof ImplementQueue | keyof ErrorImplementQueue,
fetchType: FetchType,
): AnyFunc[] => {
return fetchType === 'ok' ? implement[key] : errorImplement[key]
}
/** 请求前, 执行队列所有方法 */
const beforeAxiosFetch = (key: ImplementKey) => {
const funcArr = implement[getImplementKey(key)]
const instance = getAxiosFetchInstance(key)
const { MODE } = getAppEnvironment()
if (instance) {
funcArr?.forEach((curr) => {
/** 队列执行器 */
const implementer = (funcs: AnyFunc[], ...args: any[]) => {
if (Array.isArray(funcs)) {
funcs?.forEach((curr) => {
if (typeof curr === 'function') {
curr(instance, MODE)
curr(...args)
}
})
}
}
/** 设置拦截器队列 */
const setImplementQueue = (func: AnyFunc[], key: ImplementKey) => {
if (func && key) {
implement[getImplementKey(key)] = func
/** 请求、响应前执行拦截器队列中的所有方法 */
const beforeFetch = (
key: keyof typeof axiosFetchInstance,
implementKey: keyof ImplementQueue | keyof ErrorImplementQueue,
fetchType: FetchType,
) => {
const funcArr =
fetchType === 'ok'
? implement[implementKey]
: errorImplement[implementKey]
const instance = getAxiosInstance(key)
const { MODE } = getAppEnvironment()
if (instance) {
implementer(funcArr, instance, MODE)
}
}
/** 获取拦截器队列 */
const getImplementQueue = (key: ImplementKey) => {
return implement[getImplementKey(key)]
/** 请求、响应错误时执行队列中所有方法 */
const fetchError = (
key: keyof typeof axiosFetchError,
error: unknown,
errorImplementKey: keyof ErrorImplementQueue,
) => {
axiosFetchError[key] = error
const funcArr = errorImplement[errorImplementKey]
const { MODE } = getAppEnvironment()
implementer(funcArr, error, MODE)
}
return {
createRequestAxiosInstance,
createResponseAxiosInstance,
beforeAxiosFetch,
setImplementQueue,
getImplementQueue,
getAxiosFetchInstance,
createAxiosInstance,
setImplement,
getImplement,
getAxiosInstance,
beforeFetch,
fetchError,
}
}

View File

@ -14,6 +14,8 @@
*
* ,
* ,
*
* , 便
*/
import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor'
@ -21,8 +23,12 @@ import { appendRequestHeaders } from '@/axios/helper/axiosCopilot'
import { APP_CATCH_KEY } from '@/appConfig/appConfig'
import { getCache } from '@/utils/cache'
import type { RequestInterceptorConfig, ImplementFunction } from '@/axios/type'
const { setImplementQueue } = useAxiosInterceptor()
import type {
RequestInterceptorConfig,
BeforeFetchFunction,
FetchErrorFunction,
} from '@/axios/type'
const { setImplement } = useAxiosInterceptor()
/**
*
@ -46,7 +52,7 @@ const requestHeaderToken = (ins: RequestInterceptorConfig, mode: string) => {
}
/** 注入请求头信息 */
const injectRequestHeaders: ImplementFunction<RequestInterceptorConfig> = (
const injectRequestHeaders: BeforeFetchFunction<RequestInterceptorConfig> = (
ins,
mode,
) => {
@ -60,7 +66,7 @@ const injectRequestHeaders: ImplementFunction<RequestInterceptorConfig> = (
}
/** 注入重复请求拦截器 */
const injectCanceler: ImplementFunction<RequestInterceptorConfig> = (
const injectCanceler: BeforeFetchFunction<RequestInterceptorConfig> = (
ins,
mode,
) => {
@ -68,11 +74,28 @@ const injectCanceler: ImplementFunction<RequestInterceptorConfig> = (
axiosCanceler.addPendingRequest(ins) // 把当前的请求信息添加到 pendingRequest 表中
}
const requestError: FetchErrorFunction<unknown> = (error, mode) => {
console.log(error, mode)
}
/**
*
*
* , setImplementQueue
*
*/
export const setupRequestInterceptor = () => {
setImplementQueue([injectRequestHeaders, injectCanceler], 'requestInstance')
setImplement(
'implementRequestInterceptorArray',
[injectRequestHeaders, injectCanceler],
'ok',
)
}
/**
*
*
*
*/
export const setupRequestErrorInterceptor = () => {
setImplement('implementRequestInterceptorErrorArray', [requestError], 'error')
}

View File

@ -14,27 +14,64 @@
*
* ,
* ,
*
* , 便
*/
import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor'
import type { ResponseInterceptorConfig, ImplementFunction } from '@/axios/type'
import type {
ResponseInterceptorConfig,
BeforeFetchFunction,
FetchErrorFunction,
} from '@/axios/type'
const { setImplementQueue } = useAxiosInterceptor()
const { setImplement } = useAxiosInterceptor()
/** 响应成功后移除缓存请求 url */
const injectResponseCanceler: ImplementFunction<ResponseInterceptorConfig> = (
const injectResponseCanceler: BeforeFetchFunction<ResponseInterceptorConfig> = (
ins,
mode,
) => {
axiosCanceler.removePendingRequest(ins.config)
}
/**
*
* @param error
* @param mode
*
*
*
*
* ,
*/
const responseError: FetchErrorFunction<unknown> = (error, mode) => {
console.log(error, mode)
}
/**
*
*
* , setImplementQueue
*
*/
export const setupResponseInterceptor = () => {
setImplementQueue([injectResponseCanceler], 'responseInstance')
setImplement(
'implementResponseInterceptorArray',
[injectResponseCanceler],
'ok',
)
}
/**
*
*
*
*/
export const setupResponseErrorInterceptor = () => {
setImplement(
'implementResponseInterceptorErrorArray',
[responseError],
'error',
)
}

View File

@ -11,7 +11,7 @@
/**
*
* ,
*
* , inject
*
*/
@ -19,42 +19,50 @@
import axios from 'axios'
import { AXIOS_CONFIG } from '@/appConfig/requestConfig'
import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor'
import { setupRequestInterceptor } from '@/axios/inject/requestInject'
import { setupResponseInterceptor } from '@/axios/inject/responseInject'
import {
setupResponseInterceptor,
setupResponseErrorInterceptor,
} from '@/axios/inject/responseInject'
import {
setupRequestInterceptor,
setupRequestErrorInterceptor,
} from '@/axios/inject/requestInject'
import type { AxiosInstanceExpand } from './type'
const server: AxiosInstanceExpand = axios.create(AXIOS_CONFIG)
const {
createRequestAxiosInstance,
createResponseAxiosInstance,
beforeAxiosFetch,
} = useAxiosInterceptor()
const { createAxiosInstance, beforeFetch, fetchError } = useAxiosInterceptor()
server.interceptors.request.use(
(request) => {
createRequestAxiosInstance(request)
createAxiosInstance(request, 'requestInstance')
setupRequestInterceptor()
beforeAxiosFetch('requestInstance')
beforeFetch('requestInstance', 'implementRequestInterceptorArray', 'ok')
return request
},
(error) => {
setupRequestErrorInterceptor()
fetchError('requestError', error, 'implementRequestInterceptorErrorArray')
return Promise.reject(error)
},
)
server.interceptors.response.use(
(response) => {
createResponseAxiosInstance(response)
createAxiosInstance(response, 'responseInstance')
setupResponseInterceptor()
beforeAxiosFetch('responseInstance')
beforeFetch('responseInstance', 'implementResponseInterceptorArray', 'ok')
const { data } = response
return Promise.resolve(data)
},
(error) => {
setupResponseErrorInterceptor()
fetchError('responseError', error, 'implementResponseInterceptorErrorArray')
axiosCanceler.removePendingRequest(error.config || {})
return Promise.reject(error)

View File

@ -83,13 +83,23 @@ export type RequestInterceptorConfig<T = any> = InternalAxiosRequestConfig<T>
export type ResponseInterceptorConfig<T = any, K = any> = AxiosResponse<T, K>
export type ImplementKey = 'requestInstance' | 'responseInstance'
export interface ImplementQueue {
implementRequestInterceptorArray: AnyFunc[]
implementResponseInterceptorArray: AnyFunc[]
}
export type ImplementFunction<
export interface ErrorImplementQueue {
implementRequestInterceptorErrorArray: AnyFunc[]
implementResponseInterceptorErrorArray: AnyFunc[]
}
export type BeforeFetchFunction<
T = RequestInterceptorConfig | ResponseInterceptorConfig,
> = <K extends T>(ins: K, mode: string) => void
export type FetchType = 'ok' | 'error'
export type FetchErrorFunction<T = any> = <K extends T>(
error: K,
mode: string,
) => void

View File

@ -19,6 +19,8 @@
*
* root path ,
* , key tag
*
* MENU_TAG_DATA , MenuTag
*/
import './index.scss'
@ -31,6 +33,7 @@ import { uuid } from '@/utils/hook'
import { hasClass } from '@/utils/element'
import { redirectRouterToDashboard } from '@/router/helper/routerCopilot'
import { ROOT_ROUTE } from '@/appConfig/appConfig'
import { getElement } from '@use-utils/element'
import type { MenuOption, ScrollbarInst } from 'naive-ui'
@ -205,6 +208,7 @@ const MenuTag = defineComponent({
y: 0,
actionDropdownShow: false,
})
const MENU_TAG_DATA = 'menu_tag_data'
/**
*
@ -373,6 +377,19 @@ const MenuTag = defineComponent({
}
}
/** 动态更新 menu tag 所在位置 */
const positionMenuTag = () => {
nextTick().then(() => {
const tags = getElement(`attr:${MENU_TAG_DATA}="${menuKey.value}"`)
if (tags?.length) {
const [menuTag] = tags
menuTag.scrollIntoView?.()
}
})
}
/** 如果有且只有一个标签页时, 禁止全部关闭操作 */
watch(
() => modelMenuTagOptions.value,
@ -388,6 +405,8 @@ const MenuTag = defineComponent({
if (oldData?.length) {
if (newData.length > oldData?.length) {
updateScrollBarPosition()
} else if (newData.length === oldData?.length) {
positionMenuTag()
}
}
},
@ -423,6 +442,7 @@ const MenuTag = defineComponent({
setCurrentContentmenuIndex,
menuTagMouseenter,
menuTagMouseleave,
MENU_TAG_DATA,
}
},
render() {
@ -482,7 +502,7 @@ const MenuTag = defineComponent({
onContextmenu: this.handleContextMenu.bind(this, idx),
onMouseenter: this.menuTagMouseenter.bind(this, curr),
onMouseleave: this.menuTagMouseleave.bind(this, curr),
tag_data: curr.path,
[this.MENU_TAG_DATA]: curr.path,
}}
>
{typeof curr.label === 'function'