Files
thebet365/apps/admin/src/utils/session-hydrate.ts
Mars e1d33dde9a 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:39 +08:00

108 lines
3.2 KiB
TypeScript

import {
reconcileStaffSessionFromToken,
useAuthStore,
type StaffUser,
type StaffUserType,
} from '../stores/auth';
let hydratePromise: Promise<boolean> | null = null;
let lastHydrateAt = 0;
/** 跨路由复用 /me 结果,减少菜单切换时的重复鉴权请求 */
const HYDRATE_TTL_MS = 60_000;
function isStaffUserType(value: unknown): value is StaffUserType {
return value === 'ADMIN' || value === 'AGENT';
}
export function resetStaffSessionHydration() {
hydratePromise = null;
lastHydrateAt = 0;
}
/** 同步判断 session 是否已经新鲜,无需网络请求。主要给 router beforeEach 使用。 */
export function isSessionFresh(): boolean {
const auth = useAuthStore();
if (!auth.token.value) return false;
return (
lastHydrateAt > 0 &&
Date.now() - lastHydrateAt < HYDRATE_TTL_MS &&
hasCompleteStaffUser(auth.user.value)
);
}
function hasCompleteStaffUser(u: StaffUser | null | undefined): u is StaffUser {
return !!(u?.id && u.username && u.userType);
}
/** Sync manage_user from JWT + /manage/auth/me (fixes stale localStorage userType). */
export async function hydrateStaffSession(): Promise<boolean> {
const auth = useAuthStore();
if (!auth.token.value) return false;
const freshEnough =
lastHydrateAt > 0 &&
Date.now() - lastHydrateAt < HYDRATE_TTL_MS &&
hasCompleteStaffUser(auth.user.value);
if (freshEnough) return true;
if (hydratePromise) return hydratePromise;
hydratePromise = (async () => {
reconcileStaffSessionFromToken();
if (!hasCompleteStaffUser(auth.user.value)) {
auth.clearStaffSession();
return false;
}
try {
const { default: api } = await import('../api');
const { data } = await api.get('/manage/auth/me');
const raw = data.data as Partial<StaffUser>;
if (!raw?.id || !raw.username || !isStaffUserType(raw.userType)) {
return true;
}
auth.setSession(auth.token.value, {
id: raw.id,
username: raw.username,
userType: raw.userType,
locale: raw.locale,
role: raw.role,
permissions: Array.isArray(raw.permissions) ? raw.permissions : auth.user.value?.permissions,
agentLevel: typeof raw.agentLevel === 'number' ? raw.agentLevel : null,
maxAgentLevel: typeof raw.maxAgentLevel === 'number' ? raw.maxAgentLevel : null,
canManageSubAgents: raw.canManageSubAgents === true,
inviteCode: raw.inviteCode ?? null,
});
lastHydrateAt = Date.now();
return true;
} catch (e: unknown) {
const status = (e as { response?: { status?: number } })?.response?.status;
if (status === 401) {
auth.clearStaffSession();
return false;
}
return hasCompleteStaffUser(auth.user.value);
} finally {
hydratePromise = null;
}
})();
return hydratePromise;
}
/** Run before any authenticated route — JWT reconcile + optional /me refresh. */
export async function ensureStaffSession(): Promise<boolean> {
reconcileStaffSessionFromToken();
const auth = useAuthStore();
if (!auth.token.value) return false;
if (!hasCompleteStaffUser(auth.user.value)) {
reconcileStaffSessionFromToken();
}
if (!hasCompleteStaffUser(auth.user.value)) {
return false;
}
return hydrateStaffSession();
}