Files
shanxia2.0/web-admin/src/utils/http/bx.ts
T
2026-06-21 09:19:07 +08:00

153 lines
4.9 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.
/**
* bx 报销模块 HTTP 客户端
* 协议:code === 0、msg、data.rows(与门户 request code===200 分离)
*/
import axios, { type AxiosRequestConfig, type InternalAxiosRequestConfig } from 'axios'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/store/modules/user'
export interface BxApiResponse<T = unknown> {
code: number
msg: string
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' }
})
/** 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
})
bxAxios.interceptors.response.use(
async (res) => {
if (res.config.responseType === 'blob') {
const contentType = String(res.headers['content-type'] || '')
if (contentType.includes('application/json')) {
const text = await (res.data as Blob).text()
try {
const json = JSON.parse(text) as BxApiResponse
if (json.code !== 0) {
ElMessage.error(json.msg || '请求失败')
return Promise.reject(new Error(json.msg))
}
} catch {
/* 非 JSON blob 直出 */
}
}
return res.data
}
const body = res.data as BxApiResponse
if (body?.code !== undefined && body.code !== 0) {
ElMessage.error(body.msg || '请求失败')
return Promise.reject(new Error(body.msg || '请求失败'))
}
return body
},
async (err) => {
if (err.response?.status === 401) {
const userStore = useUserStore()
await userStore.logOut()
} else {
const detail = err.response?.data?.detail || err.response?.data?.msg
ElMessage.error(detail || err.message || '网络异常')
}
return Promise.reject(err)
}
)
function request<T = unknown>(config: AxiosRequestConfig) {
return bxAxios.request<any, BxApiResponse<T>>(config)
}
const bxRequest = {
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 bxRequest