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 # CHANGE LOG
## 3.3.6
### Feats
- 重写 axios interceptor 方法。现在逻辑更加清晰,并且支持请求错误、响应错误处理。补充了两个工具函数
- MenuTag 支持动态更新所在位置
- 修复了鉴权方法的 bug
- 更新了 router permission 方法(路由守卫)
- 补充了一些模块文档
### 补充
> 文档拖欠太多了,我补不回来了,就。。。算了吧,我在每个关键模块补充了对应的 md 说明文档,凑合一下吧。真希望有一个好心人帮补充文档。
## 3.3.5 ## 3.3.5
### Feats ### 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' import RequestCanceler from '@/axios/helper/canceler'
@ -25,73 +26,116 @@ import { getAppEnvironment } from '@use-utils/hook'
import type { import type {
RequestInterceptorConfig, RequestInterceptorConfig,
ResponseInterceptorConfig, ResponseInterceptorConfig,
ImplementKey,
ImplementQueue, ImplementQueue,
ErrorImplementQueue,
FetchType,
} from '@/axios/type' } from '@/axios/type'
/** 当前请求的实例 */
const axiosFetchInstance = { const axiosFetchInstance = {
requestInstance: null as RequestInterceptorConfig | null, requestInstance: null as RequestInterceptorConfig | null,
responseInstance: null as ResponseInterceptorConfig | null, responseInstance: null as ResponseInterceptorConfig | null,
} }
const axiosFetchError = {
requestError: null as null | unknown,
responseError: null as null | unknown,
}
/** 请求队列(区分 reslove 与 reject 状态) */
const implement: ImplementQueue = { const implement: ImplementQueue = {
implementRequestInterceptorArray: [], implementRequestInterceptorArray: [],
implementResponseInterceptorArray: [], implementResponseInterceptorArray: [],
} }
const errorImplement: ErrorImplementQueue = {
implementRequestInterceptorErrorArray: [],
implementResponseInterceptorErrorArray: [],
}
/** 取消器实例 */
export const axiosCanceler = new RequestCanceler() export const axiosCanceler = new RequestCanceler()
export const useAxiosInterceptor = () => { export const useAxiosInterceptor = () => {
const getImplementKey = (key: ImplementKey) => { /** 创建拦截器实例 */
return key === 'requestInstance' const createAxiosInstance = (
? 'implementRequestInterceptorArray' instance: RequestInterceptorConfig | ResponseInterceptorConfig,
: 'implementResponseInterceptorArray' 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) => { const getImplement = (
return axiosFetchInstance[key] key: keyof ImplementQueue | keyof ErrorImplementQueue,
fetchType: FetchType,
): AnyFunc[] => {
return fetchType === 'ok' ? implement[key] : errorImplement[key]
} }
/** 请求前, 执行队列所有方法 */ /** 队列执行器 */
const beforeAxiosFetch = (key: ImplementKey) => { const implementer = (funcs: AnyFunc[], ...args: any[]) => {
const funcArr = implement[getImplementKey(key)] if (Array.isArray(funcs)) {
const instance = getAxiosFetchInstance(key) funcs?.forEach((curr) => {
const { MODE } = getAppEnvironment()
if (instance) {
funcArr?.forEach((curr) => {
if (typeof curr === 'function') { if (typeof curr === 'function') {
curr(instance, MODE) curr(...args)
} }
}) })
} }
} }
/** 设置拦截器队列 */ /** 请求、响应前执行拦截器队列中的所有方法 */
const setImplementQueue = (func: AnyFunc[], key: ImplementKey) => { const beforeFetch = (
if (func && key) { key: keyof typeof axiosFetchInstance,
implement[getImplementKey(key)] = func 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) => { const fetchError = (
return implement[getImplementKey(key)] key: keyof typeof axiosFetchError,
error: unknown,
errorImplementKey: keyof ErrorImplementQueue,
) => {
axiosFetchError[key] = error
const funcArr = errorImplement[errorImplementKey]
const { MODE } = getAppEnvironment()
implementer(funcArr, error, MODE)
} }
return { return {
createRequestAxiosInstance, createAxiosInstance,
createResponseAxiosInstance, setImplement,
beforeAxiosFetch, getImplement,
setImplementQueue, getAxiosInstance,
getImplementQueue, beforeFetch,
getAxiosFetchInstance, fetchError,
} }
} }

View File

@ -14,6 +14,8 @@
* *
* , * ,
* , * ,
*
* , 便
*/ */
import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor' 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 { APP_CATCH_KEY } from '@/appConfig/appConfig'
import { getCache } from '@/utils/cache' import { getCache } from '@/utils/cache'
import type { RequestInterceptorConfig, ImplementFunction } from '@/axios/type' import type {
const { setImplementQueue } = useAxiosInterceptor() 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, ins,
mode, mode,
) => { ) => {
@ -60,7 +66,7 @@ const injectRequestHeaders: ImplementFunction<RequestInterceptorConfig> = (
} }
/** 注入重复请求拦截器 */ /** 注入重复请求拦截器 */
const injectCanceler: ImplementFunction<RequestInterceptorConfig> = ( const injectCanceler: BeforeFetchFunction<RequestInterceptorConfig> = (
ins, ins,
mode, mode,
) => { ) => {
@ -68,11 +74,28 @@ const injectCanceler: ImplementFunction<RequestInterceptorConfig> = (
axiosCanceler.addPendingRequest(ins) // 把当前的请求信息添加到 pendingRequest 表中 axiosCanceler.addPendingRequest(ins) // 把当前的请求信息添加到 pendingRequest 表中
} }
const requestError: FetchErrorFunction<unknown> = (error, mode) => {
console.log(error, mode)
}
/** /**
* *
* *
* , setImplementQueue *
*/ */
export const setupRequestInterceptor = () => { 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 { 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 */ /** 响应成功后移除缓存请求 url */
const injectResponseCanceler: ImplementFunction<ResponseInterceptorConfig> = ( const injectResponseCanceler: BeforeFetchFunction<ResponseInterceptorConfig> = (
ins, ins,
mode, mode,
) => { ) => {
axiosCanceler.removePendingRequest(ins.config) axiosCanceler.removePendingRequest(ins.config)
} }
/**
*
* @param error
* @param mode
*
*
*
*
* ,
*/
const responseError: FetchErrorFunction<unknown> = (error, mode) => {
console.log(error, mode)
}
/** /**
* *
* *
* , setImplementQueue *
*/ */
export const setupResponseInterceptor = () => { export const setupResponseInterceptor = () => {
setImplementQueue([injectResponseCanceler], 'responseInstance') setImplement(
'implementResponseInterceptorArray',
[injectResponseCanceler],
'ok',
)
}
/**
*
*
*
*/
export const setupResponseErrorInterceptor = () => {
setImplement(
'implementResponseInterceptorErrorArray',
[responseError],
'error',
)
} }

View File

@ -11,7 +11,7 @@
/** /**
* *
* , *
* , inject * , inject
* *
*/ */
@ -19,42 +19,50 @@
import axios from 'axios' import axios from 'axios'
import { AXIOS_CONFIG } from '@/appConfig/requestConfig' import { AXIOS_CONFIG } from '@/appConfig/requestConfig'
import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor' import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor'
import { setupRequestInterceptor } from '@/axios/inject/requestInject' import {
import { setupResponseInterceptor } from '@/axios/inject/responseInject' setupResponseInterceptor,
setupResponseErrorInterceptor,
} from '@/axios/inject/responseInject'
import {
setupRequestInterceptor,
setupRequestErrorInterceptor,
} from '@/axios/inject/requestInject'
import type { AxiosInstanceExpand } from './type' import type { AxiosInstanceExpand } from './type'
const server: AxiosInstanceExpand = axios.create(AXIOS_CONFIG) const server: AxiosInstanceExpand = axios.create(AXIOS_CONFIG)
const { const { createAxiosInstance, beforeFetch, fetchError } = useAxiosInterceptor()
createRequestAxiosInstance,
createResponseAxiosInstance,
beforeAxiosFetch,
} = useAxiosInterceptor()
server.interceptors.request.use( server.interceptors.request.use(
(request) => { (request) => {
createRequestAxiosInstance(request) createAxiosInstance(request, 'requestInstance')
setupRequestInterceptor() setupRequestInterceptor()
beforeAxiosFetch('requestInstance') beforeFetch('requestInstance', 'implementRequestInterceptorArray', 'ok')
return request return request
}, },
(error) => { (error) => {
setupRequestErrorInterceptor()
fetchError('requestError', error, 'implementRequestInterceptorErrorArray')
return Promise.reject(error) return Promise.reject(error)
}, },
) )
server.interceptors.response.use( server.interceptors.response.use(
(response) => { (response) => {
createResponseAxiosInstance(response) createAxiosInstance(response, 'responseInstance')
setupResponseInterceptor() setupResponseInterceptor()
beforeAxiosFetch('responseInstance') beforeFetch('responseInstance', 'implementResponseInterceptorArray', 'ok')
const { data } = response const { data } = response
return Promise.resolve(data) return Promise.resolve(data)
}, },
(error) => { (error) => {
setupResponseErrorInterceptor()
fetchError('responseError', error, 'implementResponseInterceptorErrorArray')
axiosCanceler.removePendingRequest(error.config || {}) axiosCanceler.removePendingRequest(error.config || {})
return Promise.reject(error) 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 ResponseInterceptorConfig<T = any, K = any> = AxiosResponse<T, K>
export type ImplementKey = 'requestInstance' | 'responseInstance'
export interface ImplementQueue { export interface ImplementQueue {
implementRequestInterceptorArray: AnyFunc[] implementRequestInterceptorArray: AnyFunc[]
implementResponseInterceptorArray: AnyFunc[] implementResponseInterceptorArray: AnyFunc[]
} }
export type ImplementFunction< export interface ErrorImplementQueue {
implementRequestInterceptorErrorArray: AnyFunc[]
implementResponseInterceptorErrorArray: AnyFunc[]
}
export type BeforeFetchFunction<
T = RequestInterceptorConfig | ResponseInterceptorConfig, T = RequestInterceptorConfig | ResponseInterceptorConfig,
> = <K extends T>(ins: K, mode: string) => void > = <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 , * root path ,
* , key tag * , key tag
*
* MENU_TAG_DATA , MenuTag
*/ */
import './index.scss' import './index.scss'
@ -31,6 +33,7 @@ import { uuid } from '@/utils/hook'
import { hasClass } from '@/utils/element' import { hasClass } from '@/utils/element'
import { redirectRouterToDashboard } from '@/router/helper/routerCopilot' import { redirectRouterToDashboard } from '@/router/helper/routerCopilot'
import { ROOT_ROUTE } from '@/appConfig/appConfig' import { ROOT_ROUTE } from '@/appConfig/appConfig'
import { getElement } from '@use-utils/element'
import type { MenuOption, ScrollbarInst } from 'naive-ui' import type { MenuOption, ScrollbarInst } from 'naive-ui'
@ -205,6 +208,7 @@ const MenuTag = defineComponent({
y: 0, y: 0,
actionDropdownShow: false, 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( watch(
() => modelMenuTagOptions.value, () => modelMenuTagOptions.value,
@ -388,6 +405,8 @@ const MenuTag = defineComponent({
if (oldData?.length) { if (oldData?.length) {
if (newData.length > oldData?.length) { if (newData.length > oldData?.length) {
updateScrollBarPosition() updateScrollBarPosition()
} else if (newData.length === oldData?.length) {
positionMenuTag()
} }
} }
}, },
@ -423,6 +442,7 @@ const MenuTag = defineComponent({
setCurrentContentmenuIndex, setCurrentContentmenuIndex,
menuTagMouseenter, menuTagMouseenter,
menuTagMouseleave, menuTagMouseleave,
MENU_TAG_DATA,
} }
}, },
render() { render() {
@ -482,7 +502,7 @@ const MenuTag = defineComponent({
onContextmenu: this.handleContextMenu.bind(this, idx), onContextmenu: this.handleContextMenu.bind(this, idx),
onMouseenter: this.menuTagMouseenter.bind(this, curr), onMouseenter: this.menuTagMouseenter.bind(this, curr),
onMouseleave: this.menuTagMouseleave.bind(this, curr), onMouseleave: this.menuTagMouseleave.bind(this, curr),
tag_data: curr.path, [this.MENU_TAG_DATA]: curr.path,
}} }}
> >
{typeof curr.label === 'function' {typeof curr.label === 'function'