feat(bx-mobile): add bxClient and retarget business APIs to shanxia2.0
Remove legacy bx user/role/menu/auth APIs. Wire auth store to portal temporarily so the app still typechecks; IAM admin pages use local stubs until Task 6 drops their routes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
c7a8930ad6
commit
3f1faeae34
9 files changed
+340
-262
No files matched your search
@@ -0,0 +1,96 @@
|
||||
import axios, { type InternalAxiosRequestConfig } from 'axios'
|
||||
import { showToast } from 'vant'
|
||||
import { portalAuthApi } from './portalAuth'
|
||||
|
||||
export class BizError extends Error {
|
||||
code?: number
|
||||
constructor(message: string, code?: number) {
|
||||
super(message)
|
||||
this.code = code
|
||||
this.name = 'BizError'
|
||||
}
|
||||
}
|
||||
|
||||
const BX_COLLECTION_ROOT =
|
||||
/^\/(reimburses|advances|payments|workflows|expense-items|invoices|invoice-wallets|invoice-ledgers)$/
|
||||
|
||||
function normalizeBxUrl(url?: string): string | undefined {
|
||||
if (!url || url.endsWith('/')) return url
|
||||
return BX_COLLECTION_ROOT.test(url) ? `${url}/` : url
|
||||
}
|
||||
|
||||
const bxClient = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL || '/api',
|
||||
timeout: 0,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
let refreshing: Promise<boolean> | null = null
|
||||
|
||||
async function tryRefresh(): Promise<boolean> {
|
||||
const rt = localStorage.getItem('refreshToken')
|
||||
if (!rt) return false
|
||||
try {
|
||||
const res: any = await portalAuthApi.refreshToken(rt)
|
||||
const token = res?.data?.token
|
||||
const refreshToken = res?.data?.refreshToken
|
||||
if (!token) return false
|
||||
localStorage.setItem('accessToken', token)
|
||||
if (refreshToken) localStorage.setItem('refreshToken', refreshToken)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
bxClient.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
||||
if (config.url) config.url = normalizeBxUrl(config.url)
|
||||
if (config.data instanceof FormData) {
|
||||
delete (config.headers as any)['Content-Type']
|
||||
}
|
||||
const token = localStorage.getItem('accessToken')
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`
|
||||
return config
|
||||
})
|
||||
|
||||
bxClient.interceptors.response.use(
|
||||
(res) => {
|
||||
if (res.config.responseType === 'blob') return res.data
|
||||
const body = res.data
|
||||
if (body?.code !== undefined && body.code !== 0) {
|
||||
showToast(body.msg || '请求失败')
|
||||
return Promise.reject(new BizError(body.msg || '请求失败', body.code))
|
||||
}
|
||||
return body
|
||||
},
|
||||
async (err) => {
|
||||
const original = err.config
|
||||
if (err.response?.status === 401 && original && !(original as any)._retry) {
|
||||
;(original as any)._retry = true
|
||||
if (!refreshing) {
|
||||
refreshing = tryRefresh().finally(() => {
|
||||
refreshing = null
|
||||
})
|
||||
}
|
||||
const ok = await refreshing
|
||||
if (ok) {
|
||||
original.headers = original.headers || {}
|
||||
original.headers.Authorization = `Bearer ${localStorage.getItem('accessToken')}`
|
||||
return bxClient.request(original)
|
||||
}
|
||||
localStorage.removeItem('accessToken')
|
||||
localStorage.removeItem('refreshToken')
|
||||
showToast('登录已过期,请重新登录')
|
||||
if (window.location.pathname !== '/login') {
|
||||
window.location.href = '/login'
|
||||
}
|
||||
} else if (err.response?.status !== 401) {
|
||||
const msg =
|
||||
err.response?.data?.msg || err.response?.data?.detail || err.message || '网络异常'
|
||||
showToast(msg)
|
||||
}
|
||||
return Promise.reject(err)
|
||||
},
|
||||
)
|
||||
|
||||
export default bxClient
|
||||
Reference in new issue
Block a user