提交
This commit is contained in:
1 parent
c3be9d1dce
commit
299609706c
328 files changed
+144899
-13873
No files matched your search
@@ -5,7 +5,6 @@
|
||||
import axios, { type AxiosRequestConfig, type InternalAxiosRequestConfig } from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { applyPortalAuthHeader } from '@/utils/http/portal-auth'
|
||||
|
||||
export interface BxApiResponse<T = unknown> {
|
||||
code: number
|
||||
@@ -13,14 +12,80 @@ export interface BxApiResponse<T = unknown> {
|
||||
data?: T
|
||||
}
|
||||
|
||||
/** 解析 bx / 运输模块响应体:兼容直出 JSON 与 { code, data } 信封 */
|
||||
export function pickBxData<T>(res: unknown): T {
|
||||
if (res == null) return res as T
|
||||
if (Array.isArray(res)) return res as T
|
||||
if (typeof res !== 'object') return res as T
|
||||
|
||||
const obj = res as BxApiResponse<T> & Record<string, unknown>
|
||||
if ('data' in obj && obj.data !== undefined) {
|
||||
return obj.data as T
|
||||
}
|
||||
return res as T
|
||||
}
|
||||
|
||||
/** 解析列表类响应,保证始终返回数组 */
|
||||
export function pickBxList<T>(res: unknown): T[] {
|
||||
const payload = pickBxData<T[] | { list?: T[]; rows?: T[] }>(res)
|
||||
if (Array.isArray(payload)) return payload
|
||||
if (payload && typeof payload === 'object') {
|
||||
const record = payload as { list?: T[]; rows?: T[] }
|
||||
if (Array.isArray(record.list)) return record.list
|
||||
if (Array.isArray(record.rows)) return record.rows
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
const bxAxios = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 60000,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
|
||||
bxAxios.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
||||
applyPortalAuthHeader(config)
|
||||
/** FastAPI @router.get("/") 集合根须带尾斜杠,否则 307 重定向到绝对 URL 会丢 Authorization */
|
||||
const BX_COLLECTION_ROOT = /^\/(transport\/(order|plan|freight)|reimburses|advances|payments|workflows|expense-items)$/
|
||||
|
||||
function normalizeBxUrl(url?: string): string | undefined {
|
||||
if (!url || url.endsWith('/')) return url
|
||||
return BX_COLLECTION_ROOT.test(url) ? `${url}/` : url
|
||||
}
|
||||
|
||||
/** 从 localStorage 兜底读取 accessToken(兼容 Pinia persist 的版本化 key) */
|
||||
function readTokenFallback(): string {
|
||||
if (typeof localStorage === 'undefined') return ''
|
||||
try {
|
||||
// 尝试 Pinia persist 的版本化 key(如 sys-v3.0.2-user)
|
||||
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 ''
|
||||
}
|
||||
|
||||
bxAxios.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
|
||||
if (config.url) {
|
||||
config.url = normalizeBxUrl(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
|
||||
})
|
||||
|
||||
@@ -76,6 +141,9 @@ const bxRequest = {
|
||||
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' })
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import axios, { type AxiosRequestConfig, type InternalAxiosRequestConfig } from
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { fetchRefreshToken } from '@/api/auth'
|
||||
import { applyPortalAuthHeader } from '@/utils/http/portal-auth'
|
||||
|
||||
type RetryableConfig = InternalAxiosRequestConfig & {
|
||||
_retry?: boolean
|
||||
@@ -51,11 +50,40 @@ function onRefreshFailed(error: unknown) {
|
||||
pendingRequests = []
|
||||
}
|
||||
|
||||
dbAxios.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
||||
/** 从 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)
|
||||
}
|
||||
applyPortalAuthHeader(config)
|
||||
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
|
||||
})
|
||||
|
||||
@@ -74,7 +102,11 @@ dbAxios.interceptors.response.use(
|
||||
return new Promise((resolve, reject) => {
|
||||
subscribeTokenRefresh(
|
||||
(newToken: string) => {
|
||||
applyPortalAuthHeader(config)
|
||||
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))
|
||||
},
|
||||
@@ -89,7 +121,11 @@ dbAxios.interceptors.response.use(
|
||||
const result = await fetchRefreshToken(storedRefreshToken)
|
||||
userStore.setToken(result.token, result.refreshToken)
|
||||
onRefreshed(result.token)
|
||||
applyPortalAuthHeader(config)
|
||||
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)
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import axios from 'axios'
|
||||
import type { BaseResponse } from '@/types'
|
||||
|
||||
const publicPlRequest = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL || '/api',
|
||||
timeout: 60000
|
||||
})
|
||||
|
||||
publicPlRequest.interceptors.response.use(response => {
|
||||
const payload = response.data as BaseResponse<unknown>
|
||||
if (payload && typeof payload.code === 'number' && payload.code !== 200) {
|
||||
return Promise.reject(new Error(payload.message || '请求失败'))
|
||||
}
|
||||
return response
|
||||
})
|
||||
|
||||
export async function publicPlGet<T>(url: string): Promise<T> {
|
||||
const res = await publicPlRequest.get<BaseResponse<T>>(url)
|
||||
return (res.data?.data ?? res.data) as T
|
||||
}
|
||||
|
||||
export async function publicPlPost<T>(url: string, data?: unknown): Promise<T> {
|
||||
const res = await publicPlRequest.post<BaseResponse<T>>(url, data)
|
||||
return (res.data?.data ?? res.data) as T
|
||||
}
|
||||
|
||||
export async function publicPlUpload<T>(url: string, file: File): Promise<T> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const res = await publicPlRequest.post<BaseResponse<T>>(url, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
})
|
||||
return (res.data?.data ?? res.data) as T
|
||||
}
|
||||
|
||||
export default publicPlRequest
|
||||
Reference in new issue
Block a user