This commit is contained in:
zlwu committed 2026-06-10 22:59:34 +08:00
1 parent 725774ef0c
commit c3be9d1dce
176 files changed
+41925 -6342

No files matched your search

+2 -4
View File
@@ -5,6 +5,7 @@
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
@@ -19,10 +20,7 @@ const bxAxios = axios.create({
})
bxAxios.interceptors.request.use((config: InternalAxiosRequestConfig) => {
const token = useUserStore().accessToken
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
applyPortalAuthHeader(config)
return config
})
+144
View File
@@ -0,0 +1,144 @@
/**
* 地磅(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'
import { applyPortalAuthHeader } from '@/utils/http/portal-auth'
type RetryableConfig = InternalAxiosRequestConfig & {
_retry?: boolean
/** 为 false 时不弹出全局错误提示(由调用方自行处理) */
showErrorMessage?: boolean
}
/** 地磅集合根路径(后端 @router.get("/") 注册,须带尾斜杠,否则 307 重定向会丢 Authorization) */
const DB_COLLECTION_ROOT = /^\/(plans|inventory|weighing|master-data\/vehicles)$/
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 = []
}
dbAxios.interceptors.request.use((config: InternalAxiosRequestConfig) => {
if (config.url) {
config.url = normalizeDbUrl(config.url)
}
applyPortalAuthHeader(config)
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) => {
applyPortalAuthHeader(config)
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)
applyPortalAuthHeader(config)
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
+68
View File
@@ -0,0 +1,68 @@
/**
* 门户 JWT 读取与请求头注入(供 db / bx 等非信封协议客户端复用)
*/
import { unref } from 'vue'
import type { InternalAxiosRequestConfig } from 'axios'
import { useUserStore } from '@/store/modules/user'
import { StorageConfig } from '@/utils/storage/storage-config'
function readTokenFromStorageKey(key: string): string {
if (typeof localStorage === 'undefined') return ''
try {
const raw = localStorage.getItem(key)
if (!raw) return ''
const parsed = JSON.parse(raw) as { accessToken?: unknown }
const token = parsed?.accessToken
return typeof token === 'string' ? token.trim() : ''
} catch {
return ''
}
}
/** 从 Pinia 或持久化缓存解析 accessToken(setup store / 持久化时序兜底) */
export function resolvePortalAccessToken(): string {
try {
const store = useUserStore()
const fromStore = unref(store.accessToken as unknown as string | { value?: string })
if (typeof fromStore === 'string' && fromStore.trim()) {
return fromStore.trim()
}
const fromState = store.$state?.accessToken
if (typeof fromState === 'string' && fromState.trim()) {
return fromState.trim()
}
} catch {
// Pinia 未就绪时走 localStorage 兜底
}
const direct = readTokenFromStorageKey('user')
if (direct) return direct
const versioned = readTokenFromStorageKey(StorageConfig.generateStorageKey('userStore'))
if (versioned) return versioned
if (typeof localStorage === 'undefined') return ''
try {
const pattern = StorageConfig.createKeyPattern('userStore')
for (const key of Object.keys(localStorage)) {
if (!pattern.test(key)) continue
const token = readTokenFromStorageKey(key)
if (token) return token
}
} catch {
// ignore
}
return ''
}
/** 为 Axios 请求附加 Bearer Token */
export function applyPortalAuthHeader(config: InternalAxiosRequestConfig): void {
const token = resolvePortalAccessToken()
if (!token) return
if (typeof config.headers.set === 'function') {
config.headers.set('Authorization', `Bearer ${token}`)
} else {
config.headers.Authorization = `Bearer ${token}`
}
}