Files
thebet365/apps/admin/src/api.ts
Mars 2487724692 perf(admin): 优化管理端页面切换性能与包体积 (同步自 main)
- 阶段 A: 增加 KeepAlive 缓存高频列表页,改用 onActivated 进行后台静默刷新,优化 tab 切换与 HomeEntry 闪屏

- 阶段 B: beforeEach 守卫在 TTL 内走同步快速路径,api 请求拦截器缓存 token 避免重复解析 JWT

- 阶段 C: 引入 unplugin 插件启用 Element Plus 组件按需加载,清理 App.vue 中 960+ 行冗余暗色主题 CSS

- i18n 拆包: 将三语文案包提取为独立懒加载 chunks,主包体积从 209KB 减少至 99KB (降低 53%)
2026-06-18 15:31:57 +08:00

149 lines
4.1 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 from 'axios';
import router from './router';
import { clearStaffSession, reconcileStaffSessionFromToken, useAuthStore } from './stores/auth';
import { ensureStaffSession, resetStaffSessionHydration } from './utils/session-hydrate';
import { ADMIN_LOCALE_STORAGE_KEY } from './i18n';
const api = axios.create({ baseURL: '/api' });
let handlingAuthInvalid = false;
let handling403Portal = false;
const PORTAL_MISMATCH_CODES = new Set(['ADMIN_ACCESS_ONLY', 'AGENT_ACCESS_ONLY']);
function requestPath(config: { url?: string; baseURL?: string } | undefined): string {
if (!config?.url) return '';
const base = config.baseURL ?? '';
return `${base}${config.url}`;
}
function isLoginRequest(config: { url?: string; baseURL?: string } | undefined): boolean {
return requestPath(config).includes('/auth/login');
}
function isInvalidCredentialsResponse(data: unknown): boolean {
return (
typeof data === 'object' &&
data !== null &&
'code' in data &&
(data as { code?: unknown }).code === 'INVALID_CREDENTIALS'
);
}
async function redirectToLoginForInvalidSession() {
if (handlingAuthInvalid) return;
handlingAuthInvalid = true;
try {
clearStaffSession();
resetStaffSessionHydration();
if (router.currentRoute.value.path !== '/login') {
await router.replace('/login');
}
} finally {
handlingAuthInvalid = false;
}
}
let _lastReconciledToken: string | null = null;
api.interceptors.request.use((config) => {
const t = localStorage.getItem('manage_token');
if (t) config.headers.Authorization = `Bearer ${t}`;
const locale = localStorage.getItem(ADMIN_LOCALE_STORAGE_KEY) || 'zh-CN';
config.headers['X-Locale'] = locale;
// 只在 token 变更时才重新 decode JWT + reconcile避免并发请求时多次执行同步开销
if (t !== _lastReconciledToken) {
reconcileStaffSessionFromToken();
_lastReconciledToken = t;
}
const auth = useAuthStore();
const path = requestPath(config);
if (path.includes('/admin/') && auth.isAgent.value) {
return Promise.reject(
Object.assign(new Error('Agent portal blocked admin API'), {
isPortalMismatch: true,
blockedPath: path,
}),
);
}
if (path.includes('/agent/') && auth.isAdmin.value) {
return Promise.reject(
Object.assign(new Error('Admin portal blocked agent API'), {
isPortalMismatch: true,
blockedPath: path,
}),
);
}
return config;
});
api.interceptors.response.use(
async (res) => {
if (!isLoginRequest(res.config) && isInvalidCredentialsResponse(res.data)) {
await redirectToLoginForInvalidSession();
return Promise.reject(
Object.assign(new Error((res.data as { error?: string }).error || 'Invalid credentials'), {
response: res,
config: res.config,
}),
);
}
return res;
},
async (err) => {
if (err.isPortalMismatch) {
await ensureStaffSession();
if (router.currentRoute.value.path !== '/login') {
await router.replace('/');
}
return Promise.reject(err);
}
if (
!isLoginRequest(err.config) &&
(err.response?.status === 401 || isInvalidCredentialsResponse(err.response?.data))
) {
await redirectToLoginForInvalidSession();
}
if (
err.response?.status === 403 &&
!handling403Portal &&
PORTAL_MISMATCH_CODES.has(err.response?.data?.code)
) {
handling403Portal = true;
await ensureStaffSession();
handling403Portal = false;
const auth = useAuthStore();
const path = requestPath(err.config);
const isAdminApi = path.includes('/admin/');
const isAgentApi = path.includes('/agent/');
if ((isAdminApi && auth.isAgent.value) || (isAgentApi && auth.isAdmin.value)) {
if (router.currentRoute.value.path !== '/login') {
await router.replace('/');
}
return Promise.reject(err);
}
if (err.config) {
return api.request(err.config);
}
clearStaffSession();
if (router.currentRoute.value.path !== '/login') {
await router.replace('/login');
}
}
return Promise.reject(err);
},
);
export default api;