import { get } from '@/utils/request'; import type { MenuNode, PermissionCode } from '@/types'; import { FALLBACK_MENU_NODES } from '@/config/fallbackRoutes'; import { getAuthDataPromise, setAuthDataPromise, clearAuthDataCache } from '@/api/authCache'; import { forceReLogin } from '@/hooks/useAuth'; import { useUserStore } from '@/stores/userStore'; export { clearAuthDataCache } from '@/api/authCache'; /** * 权限数据(角色 + 权限码) */ export interface AuthData { roleList: string[]; permsList: string[]; nickname: string; phone: string; } const AUTH_FETCH_FAIL_MSG = '账户权限获取失败,请重新登录'; function normalizeAuthData(res: any): AuthData { if (res?.code != null && res.code != 200) { throw new Error(res.msg || AUTH_FETCH_FAIL_MSG); } const data = res?.data || {}; const roleList = data.roleList || []; const permsList = data.permsList || []; if (permsList.length === 0) { throw new Error(AUTH_FETCH_FAIL_MSG); } const nickname = data.nickName || ''; const phone = typeof data.phone === 'string' ? data.phone : ''; if (nickname || phone) { const { setUser } = useUserStore(); setUser({ ...(nickname ? { nickname } : {}), ...(phone ? { phone } : {}), }); } return { roleList, permsList, nickname, phone }; } function handleAuthFetchFailure() { clearAuthDataCache(); forceReLogin(AUTH_FETCH_FAIL_MSG); } /** * 获取当前用户的完整权限数据(角色列表 + 权限编码列表) * 接口: GET /op/permission * Promise 级缓存:并发调用共享同一个请求,失败时清除缓存以允许重试 */ export function fetchAuthData(): Promise { const cached = getAuthDataPromise(); if (cached) return cached; const promise = get('/op/permission') .then((res) => normalizeAuthData(res)) .catch((err) => { handleAuthFetchFailure(); throw err; }); setAuthDataPromise(promise); return promise; } /** * 获取当前用户的权限编码列表 * 复用 fetchAuthData 的缓存,避免重复请求 */ export function fetchPermissions(): Promise { return fetchAuthData().then((data) => data.permsList); } // ============================================================ // 菜单树 — 基于权限过滤兜底路由 // ============================================================ /** * 按用户拥有的权限码过滤菜单树 * * 规则: * - 节点有 permission 字段 → 权限码必须在 permsSet 中才保留 * - 节点无 permission 但有 children → 保留并递归过滤子节点 * - 节点被过滤且父节点过滤后无子节点、无 component → 父节点也移除 */ function filterMenuNodesByPermission(nodes: MenuNode[], permsSet: Set): MenuNode[] { return nodes .map((node) => { // 叶子节点:有 permission 则按权限过滤 if (node.permission) { if (!permsSet.has(node.permission)) return null; return { ...node }; } // 分组节点(无 permission 有 children):递归过滤子节点 if (node.children && node.children.length > 0) { const filteredChildren = filterMenuNodesByPermission(node.children, permsSet); // 子节点全部被过滤且父节点无 component → 移除父节点 if (filteredChildren.length === 0 && !node.component) return null; return { ...node, children: filteredChildren }; } // 无 permission、无 children、无 component → 保留(如纯展示节点) return { ...node }; }) .filter((node): node is MenuNode => node !== null); } /** * 获取当前用户的菜单树(基于权限过滤兜底路由) * * 1. 调用 /op/permission 获取用户权限码 * 2. 用权限码过滤 FALLBACK_MENU_NODES * 3. 返回过滤后的菜单树 * * 权限获取失败时由 fetchAuthData 统一强制重新登录 */ export function fetchMenuTree(): Promise { return fetchAuthData().then(({ permsList }) => { const permsSet = new Set(permsList); return filterMenuNodesByPermission(FALLBACK_MENU_NODES, permsSet); }); }