feat: 迁入 MiGu.Server、平台前端与车辆列表 reflection 回退

从 Simple-FR 拆出 Platform.Server 并重命名为 MiGu.Server;frontends 源码与构建脚本迁入本仓库。地图监控在 projection/cars 失败或为空时回退 reflection 车辆列表;Simple 仓库已移除旧 Platform.Server。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhaowei.huang
2026-05-29 18:16:34 +08:00
co-authored by Cursor
parent 804aa68ade
commit 42978930ca
280 changed files with 30046 additions and 8 deletions
@@ -0,0 +1,75 @@
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