Files
Migu2.0/frontends/apps/simple-platform-vue/src/api/http.ts
T
zhaowei.huangandCursor 42978930ca feat: 迁入 MiGu.Server、平台前端与车辆列表 reflection 回退
从 Simple-FR 拆出 Platform.Server 并重命名为 MiGu.Server;frontends 源码与构建脚本迁入本仓库。地图监控在 projection/cars 失败或为空时回退 reflection 车辆列表;Simple 仓库已移除旧 Platform.Server。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-29 18:16:34 +08:00

76 lines
3.3 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.
import axios, { AxiosError, type AxiosInstance, type InternalAxiosRequestConfig } from 'axios'
const baseURL = (import.meta.env.VITE_API_BASE as string | undefined) ?? '/api'
// AR-5 (会话 45)withCredentials=true 让后端下发的 httpOnly Cookie (simple.auth.token)
// 自动随请求带回。Platform.Server `/api/auth/login` 同时下发 Cookie 和返回 token 字段,
// 过渡期内 Authorization: Bearer header 仍然兼容(旧浏览器 / 跨进程脚本)。
const http: AxiosInstance = axios.create({
baseURL,
timeout: 15000,
headers: { 'Content-Type': 'application/json' },
withCredentials: true
})
http.interceptors.request.use((config: InternalAxiosRequestConfig) => {
// 双轨:优先用 localStorage 里的 token(过渡期 fallback),Cookie 会自动带;
// 后端首先看 Authorization: Bearer,没有再看 Cookie,两路任一通过即可。
const token = localStorage.getItem('simple.auth.token')
if (token) {
config.headers.set('Authorization', `Bearer ${token}`)
}
const scope = localStorage.getItem('simple.auth.scope')
if (scope) {
config.headers.set('X-Scope', scope)
}
return config
})
/**
* 把 axios 异常翻译成更友好的中文文案,特别是 SimpleLite 链路:
*
* /api/sl/projection/** → Platform.Server YARP → SimpleLite EmbedIO :8222
*
* 如果 SimpleLite 没启动 / 端口未监听,YARP 一定回 502;超时一般是 504。把这两种
* 情况单独识别,写成「SimpleLite 未连接,请先启动 SimpleLite (端口 8222)」之类,
* 比直接抛 `Request failed with status code 502` 友好得多,也避免用户怀疑前端 bug。
*/
function describeError(err: AxiosError): string {
const status = err.response?.status
const url = err.config?.url ?? ''
const isSimpleLite = url.startsWith('/sl/') || url.startsWith('sl/')
if (status === 502 || status === 504 || err.code === 'ECONNABORTED' || err.code === 'ERR_NETWORK') {
if (isSimpleLite) {
return 'SimpleLite 未连接:请先启动 SimpleLite 后端(监听端口 8222)后重试。'
}
return `网关无法连接到下游服务(${status ?? err.code ?? 'network'})。`
}
// 后端 envelope 模式:{ success: false, message: 'xxx' }
const data = err.response?.data as { message?: string } | undefined
if (data?.message) return data.message
return err.message
}
http.interceptors.response.use(
(resp) => resp,
(err: AxiosError) => {
if (err.response?.status === 401) {
// 会话 45 HC-1401 全量清 state,避免老 user/scope/perm 残留导致 UI 误判。
try {
localStorage.removeItem('simple.auth.token')
localStorage.removeItem('simple.auth.user')
localStorage.removeItem('simple.auth.scope')
localStorage.removeItem('simple.auth.runMode')
localStorage.removeItem('simple.auth.perm')
} catch { /* 某些隐私模式 / iframe 沙箱可能禁用 localStorage */ }
if (location.pathname !== '/login') location.assign('/login')
}
// 用 Object.defineProperty 覆盖 message,避免后续 `(err as Error).message` 还是看到原文。
const friendly = describeError(err)
try { Object.defineProperty(err, 'message', { value: friendly, configurable: true }) } catch { /* ignore */ }
return Promise.reject(err)
}
)
export default http