Files
shanxia2.0/web-admin/src/utils/http/db.ts
T
2026-06-21 12:41:39 +08:00

181 lines
5.7 KiB
TypeScript
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.
/**
* 地磅(db)模块 HTTP 客户端
* 协议:直接 JSON 响应(与原 simple-dibang-xiaoshou 一致,非门户 code/data 信封)
*/
import axios, { type AxiosRequestConfig, type InternalAxiosRequestConfig } from 'axios'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/store/modules/user'
import { fetchRefreshToken } from '@/api/auth'
type RetryableConfig = InternalAxiosRequestConfig & {
_retry?: boolean
/** 为 false 时不弹出全局错误提示(由调用方自行处理) */
showErrorMessage?: boolean
}
/** 地磅集合根路径(后端 @router.get("/") 注册,须带尾斜杠,否则 307 重定向会丢 Authorization) */
const DB_COLLECTION_ROOT = /^\/(plans|inventory|weighing)$/
function normalizeDbUrl(url?: string): string | undefined {
if (!url || url.endsWith('/')) return url
return DB_COLLECTION_ROOT.test(url) ? `${url}/` : url
}
const dbAxios = axios.create({
baseURL: '/api',
timeout: 60000,
headers: { 'Content-Type': 'application/json' }
})
let isRefreshing = false
let pendingRequests: Array<{
resolve: (token: string) => void
reject: (error: unknown) => void
}> = []
function subscribeTokenRefresh(
resolve: (token: string) => void,
reject: (error: unknown) => void
) {
pendingRequests.push({ resolve, reject })
}
function onRefreshed(newToken: string) {
pendingRequests.forEach(({ resolve }) => resolve(newToken))
pendingRequests = []
}
function onRefreshFailed(error: unknown) {
pendingRequests.forEach(({ reject }) => reject(error))
pendingRequests = []
}
/** 从 localStorage 兜底读取 accessToken(兼容 Pinia persist 的版本化 key) */
function readTokenFallback(): string {
if (typeof localStorage === 'undefined') return ''
try {
for (const k of Object.keys(localStorage)) {
if (!k.startsWith('sys-') || !k.includes('-user')) continue
const raw = localStorage.getItem(k)
if (!raw) continue
const parsed = JSON.parse(raw) as { accessToken?: unknown }
const token = parsed?.accessToken
if (typeof token === 'string' && token.trim()) return token.trim()
}
} catch {
// ignore
}
return ''
}
dbAxios.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
if (config.url) {
config.url = normalizeDbUrl(config.url)
}
const store = useUserStore()
let token = store.accessToken as unknown as string
if (!token) {
token = readTokenFallback()
}
if (token) {
if (typeof config.headers.set === 'function') {
config.headers.set('Authorization', `Bearer ${token}`)
} else {
config.headers.Authorization = `Bearer ${token}`
}
}
return config
})
dbAxios.interceptors.response.use(
(res) => res.data,
async (err) => {
const status = err.response?.status
const config = err.config as RetryableConfig | undefined
if (status === 401 && config && !config._retry) {
const userStore = useUserStore()
const storedRefreshToken = userStore.refreshToken
if (storedRefreshToken) {
if (isRefreshing) {
return new Promise((resolve, reject) => {
subscribeTokenRefresh(
(newToken: string) => {
if (typeof config.headers.set === 'function') {
config.headers.set('Authorization', `Bearer ${newToken}`)
} else {
config.headers.Authorization = `Bearer ${newToken}`
}
config._retry = true
resolve(dbAxios.request(config))
},
(refreshError: unknown) => reject(refreshError)
)
})
}
isRefreshing = true
config._retry = true
try {
const result = await fetchRefreshToken(storedRefreshToken)
userStore.setToken(result.token, result.refreshToken)
onRefreshed(result.token)
if (typeof config.headers.set === 'function') {
config.headers.set('Authorization', `Bearer ${result.token}`)
} else {
config.headers.Authorization = `Bearer ${result.token}`
}
return dbAxios.request(config)
} catch (refreshError) {
onRefreshFailed(refreshError)
await userStore.logOut()
return Promise.reject(refreshError)
} finally {
isRefreshing = false
}
}
await userStore.logOut()
return Promise.reject(err)
}
const showErrorMessage = config?.showErrorMessage !== false
if (showErrorMessage) {
const detail = err.response?.data?.detail
const message =
typeof detail === 'string'
? detail
: Array.isArray(detail)
? detail.map((x: { msg?: string }) => x.msg).filter(Boolean).join(';')
: err.response?.data?.msg
ElMessage.error(message || err.message || '网络异常')
}
return Promise.reject(err)
}
)
function request<T = unknown>(config: AxiosRequestConfig) {
return dbAxios.request<any, T>(config)
}
const dbRequest = {
get<T = unknown>(url: string, config?: AxiosRequestConfig) {
return request<T>({ ...config, url, method: 'GET' })
},
post<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig) {
return request<T>({ ...config, url, method: 'POST', data })
},
put<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig) {
return request<T>({ ...config, url, method: 'PUT', data })
},
patch<T = unknown>(url: string, data?: unknown, config?: AxiosRequestConfig) {
return request<T>({ ...config, url, method: 'PATCH', data })
},
delete<T = unknown>(url: string, config?: AxiosRequestConfig) {
return request<T>({ ...config, url, method: 'DELETE' })
}
}
export default dbRequest