Files
ipam/frontend/src/utils/api.js
T
Your Name 1e1e5957e8 fix(ipam): 修复IP管理页面网段筛选不生效及下拉框过窄
- Ips.vue: el-select 添加 width: 280px,修复网段下拉框过窄
- Ips.vue: query 参数 networkId → network_id(与 FastAPI snake_case 一致)
- Scan.vue: el-select 用 network.id 代替整个 network 对象作为 value,修复切换网段不生效
- enhanced_scan_service.py: 替换失效的 scapy ARP 扫描为 arp-scan 命令;扫描结果自动创建缺失 IP 记录
2026-07-18 09:45:30 +08:00

118 lines
3.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { authStore } from '@/utils/auth'
const api = axios.create({
baseURL: '/api/v1',
timeout: 30000,
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器:自动带 Authorization header
api.interceptors.request.use(
config => {
const token = authStore.getAccessToken()
if (token) {
config.headers = config.headers || {}
config.headers.Authorization = `Bearer ${token}`
}
return config
},
error => Promise.reject(error)
)
// 标记正在刷新 token,防止并发 401 触发多次刷新
let isRefreshing = false
let pendingQueue = []
function flushQueue(newToken) {
pendingQueue.forEach(cb => cb(newToken))
pendingQueue = []
}
function queueRefresh(cb) {
pendingQueue.push(cb)
}
// 响应拦截器:401 时尝试 refresh tokenrefresh 失败就跳登录
api.interceptors.response.use(
response => {
return response.data
},
async error => {
const originalRequest = error.config || {}
const status = error.response?.status
const detail = error.response?.data?.detail
// 401 且不是 login/refresh 本身:尝试刷新 token
if (status === 401 && !originalRequest._retry && !originalRequest.url?.includes('/auth/')) {
const refreshToken = authStore.getRefreshToken()
if (!refreshToken) {
authStore.clear()
redirectToLogin()
return Promise.reject(error)
}
if (isRefreshing) {
// 已有刷新请求在进行,等它完成后再重试
return new Promise(resolve => {
queueRefresh(newToken => {
if (newToken) {
originalRequest._retry = true
originalRequest.headers.Authorization = `Bearer ${newToken}`
resolve(api(originalRequest))
} else {
redirectToLogin()
resolve(Promise.reject(error))
}
})
})
}
originalRequest._retry = true
isRefreshing = true
try {
const formBody = new URLSearchParams()
formBody.append('refresh_token', refreshToken)
const resp = await axios.post('/api/v1/auth/refresh', formBody, {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
})
const newToken = resp.data?.access_token
if (newToken) {
authStore.setAccessToken(newToken)
if (resp.data?.refresh_token) {
authStore.setRefreshToken(resp.data.refresh_token)
}
isRefreshing = false
flushQueue(newToken)
originalRequest.headers.Authorization = `Bearer ${newToken}`
return api(originalRequest)
} else {
throw new Error('refresh 响应缺少 access_token')
}
} catch (refreshErr) {
isRefreshing = false
flushQueue(null)
authStore.clear()
redirectToLogin()
return Promise.reject(refreshErr)
}
}
// 非 401 或 refresh 后仍失败的:弹 toast(保持原行为,避免 view 重复弹)
console.error('API Error:', error)
ElMessage.error(typeof detail === 'string' ? detail : '请求失败')
return Promise.reject(error)
}
)
function redirectToLogin() {
if (typeof window !== 'undefined' && window.location.pathname !== '/login') {
const next = encodeURIComponent(window.location.pathname + window.location.search)
window.location.href = `/login?next=${next}`
}
}
export default api