feat: 新增按钮级权限指令 v-permission 与 Axios 全局防重复提交机制

This commit is contained in:
DXC
2026-03-10 11:59:41 +08:00
parent 3ab47ab2fb
commit 525acae423
4 changed files with 105 additions and 6 deletions

View File

@ -13,7 +13,21 @@ const service = axios.create({
})
// ============================================================
// 2. 无感刷新 Token 核心逻辑
// 2. 防重复提交 - Pending 请求池
// ============================================================
const pendingRequests = new Map<string, AbortController>()
// 生成唯一请求 Key方法 + URL + 序列化参数
const generateRequestKey = (config: InternalAxiosRequestConfig): string => {
const method = (config.method || 'get').toLowerCase()
const url = config.url || ''
const params = config.params ? JSON.stringify(config.params) : ''
const data = config.data ? JSON.stringify(config.data) : ''
return `${method}:${url}:${params}:${data}`
}
// ============================================================
// 3. 无感刷新 Token 核心逻辑
// ============================================================
// 标记是否正在刷新 Token
@ -54,17 +68,37 @@ const refreshToken = async (refreshTokenValue: string): Promise<string> => {
}
// ============================================================
// 3. 请求拦截器
// 4. 请求拦截器(添加 Token + 防重复提交)
// ============================================================
service.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么
// 1. 添加 Token
const token = localStorage.getItem('access_token') || localStorage.getItem('token')
if (token && config.headers) {
// Flask-JWT-Extended 默认需要 'Bearer <token>' 格式
config.headers['Authorization'] = 'Bearer ' + token
}
// 2. 防重复提交检查
const requestKey = generateRequestKey(config)
// 排除一些不需要防重复的请求(如查询类 GET 请求可以根据需求调整)
const ignoreMethods = ['get', 'head']
if (!ignoreMethods.includes((config.method || 'get').toLowerCase())) {
if (pendingRequests.has(requestKey)) {
// 取消之前的请求
const controller = pendingRequests.get(requestKey)
controller?.abort('正在处理中,请勿重复操作')
pendingRequests.delete(requestKey)
console.warn(`[防重复] 取消重复请求: ${requestKey}`)
}
// 创建新的 AbortController 并存储
const controller = new AbortController()
config.signal = controller.signal
pendingRequests.set(requestKey, controller)
}
return config
},
(error) => {
@ -73,10 +107,16 @@ service.interceptors.request.use(
)
// ============================================================
// 4. 响应拦截器(核心:无感刷新)
// 5. 响应拦截器(核心:无感刷新 + 清理 pending
// ============================================================
service.interceptors.response.use(
(response) => {
// 清理 pending 请求池
const requestKey = generateRequestKey(response.config)
if (pendingRequests.has(requestKey)) {
pendingRequests.delete(requestKey)
}
// Axios 默认包了一层 data所以这里取 response.data
const res = response.data
@ -94,6 +134,14 @@ service.interceptors.response.use(
async (error: AxiosError) => {
console.log('err: ' + error) // for debug
// 清理 pending 请求池(无论成功还是失败都要清理)
if (error.config) {
const requestKey = generateRequestKey(error.config)
if (pendingRequests.has(requestKey)) {
pendingRequests.delete(requestKey)
}
}
// 如果不是 axios 错误,直接抛出
if (!error.response) {
return Promise.reject(error)