Reorganize the frontend around app-level routing and page modules so the runtime and feature screens share a clearer navigation and composition layout for future work. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
84 lines
2.4 KiB
TypeScript
84 lines
2.4 KiB
TypeScript
import axios from 'axios'
|
|
|
|
const api = axios.create({
|
|
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:9527',
|
|
timeout: 30000,
|
|
})
|
|
|
|
function createRequestId() {
|
|
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
|
|
return crypto.randomUUID()
|
|
}
|
|
return `req-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
|
}
|
|
|
|
function isDev() {
|
|
return Boolean(import.meta.env.DEV)
|
|
}
|
|
|
|
function debugLog(stage: string, payload: Record<string, unknown>) {
|
|
if (!isDev()) return
|
|
console.debug(`[api:${stage}]`, payload)
|
|
}
|
|
|
|
// 请求拦截器:添加 Token
|
|
api.interceptors.request.use((config) => {
|
|
const token = localStorage.getItem('access_token')
|
|
const requestId = createRequestId()
|
|
config.headers = config.headers || {}
|
|
config.headers['X-Request-ID'] = requestId
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`
|
|
}
|
|
;(config as typeof config & { metadata?: { startedAt: number; requestId: string } }).metadata = {
|
|
startedAt: Date.now(),
|
|
requestId,
|
|
}
|
|
debugLog('request', {
|
|
requestId,
|
|
method: config.method,
|
|
url: config.url,
|
|
params: config.params,
|
|
data: config.data,
|
|
})
|
|
return config
|
|
})
|
|
|
|
// 响应拦截器:处理错误
|
|
api.interceptors.response.use(
|
|
(response) => {
|
|
const metadata = (response.config as typeof response.config & { metadata?: { startedAt: number; requestId: string } }).metadata
|
|
debugLog('response', {
|
|
requestId: response.headers['x-request-id'] || metadata?.requestId,
|
|
method: response.config.method,
|
|
url: response.config.url,
|
|
status: response.status,
|
|
durationMs: metadata ? Date.now() - metadata.startedAt : undefined,
|
|
data: response.data,
|
|
})
|
|
return response
|
|
},
|
|
async (error) => {
|
|
const metadata = (error.config as typeof error.config & { metadata?: { startedAt: number; requestId: string } })?.metadata
|
|
const requestId = error.response?.headers?.['x-request-id'] || metadata?.requestId
|
|
if (error.response?.status === 401) {
|
|
localStorage.removeItem('access_token')
|
|
window.location.href = '/login'
|
|
}
|
|
debugLog('error', {
|
|
requestId,
|
|
method: error.config?.method,
|
|
url: error.config?.url,
|
|
status: error.response?.status,
|
|
durationMs: metadata ? Date.now() - metadata.startedAt : undefined,
|
|
detail: error.response?.data,
|
|
})
|
|
if (requestId) {
|
|
error.requestId = requestId
|
|
}
|
|
return Promise.reject(error)
|
|
}
|
|
)
|
|
|
|
export default api
|