2026-07-23 11:27:33 +08:00
|
|
|
import { get } from '@/utils/request';
|
|
|
|
|
import type { MenuNode, PermissionCode } from '@/types';
|
2026-08-04 10:16:32 +08:00
|
|
|
import { FALLBACK_MENU_NODES } from '@/config/fallbackRoutes';
|
2026-08-05 15:30:09 +08:00
|
|
|
import { getAuthDataPromise, setAuthDataPromise, clearAuthDataCache } from '@/api/authCache';
|
|
|
|
|
import { forceReLogin } from '@/hooks/useAuth';
|
2026-08-05 16:02:01 +08:00
|
|
|
import { useUserStore } from '@/stores/userStore';
|
2026-08-04 10:16:32 +08:00
|
|
|
|
2026-08-05 15:30:09 +08:00
|
|
|
export { clearAuthDataCache } from '@/api/authCache';
|
2026-08-04 10:16:32 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 权限数据(角色 + 权限码)
|
|
|
|
|
*/
|
|
|
|
|
export interface AuthData {
|
|
|
|
|
roleList: string[];
|
|
|
|
|
permsList: string[];
|
2026-08-05 16:02:01 +08:00
|
|
|
nickname: string;
|
|
|
|
|
phone: string;
|
2026-08-04 10:16:32 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-05 15:30:09 +08:00
|
|
|
const AUTH_FETCH_FAIL_MSG = '账户权限获取失败,请重新登录';
|
2026-08-07 14:48:25 +08:00
|
|
|
const AUTH_EMPTY_MSG = '当前账户权限无任何权限,请联系管理员';
|
2026-08-05 15:30:09 +08:00
|
|
|
|
|
|
|
|
function normalizeAuthData(res: any): AuthData {
|
|
|
|
|
if (res?.code != null && res.code != 200) {
|
|
|
|
|
throw new Error(res.msg || AUTH_FETCH_FAIL_MSG);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-05 16:02:01 +08:00
|
|
|
const data = res?.data || {};
|
|
|
|
|
const roleList = data.roleList || [];
|
|
|
|
|
const permsList = data.permsList || [];
|
2026-08-05 15:30:09 +08:00
|
|
|
|
|
|
|
|
if (permsList.length === 0) {
|
2026-08-07 14:48:25 +08:00
|
|
|
throw new Error(AUTH_EMPTY_MSG);
|
2026-08-05 15:30:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-05 16:02:01 +08:00
|
|
|
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 };
|
2026-08-05 15:30:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-07 14:48:25 +08:00
|
|
|
function handleAuthFetchFailure(msg?: string) {
|
2026-08-05 15:30:09 +08:00
|
|
|
clearAuthDataCache();
|
2026-08-07 14:48:25 +08:00
|
|
|
forceReLogin(msg);
|
2026-08-05 15:30:09 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-04 10:16:32 +08:00
|
|
|
/**
|
|
|
|
|
* 获取当前用户的完整权限数据(角色列表 + 权限编码列表)
|
|
|
|
|
* 接口: GET /op/permission
|
|
|
|
|
* Promise 级缓存:并发调用共享同一个请求,失败时清除缓存以允许重试
|
|
|
|
|
*/
|
|
|
|
|
export function fetchAuthData(): Promise<AuthData> {
|
2026-08-05 15:30:09 +08:00
|
|
|
const cached = getAuthDataPromise();
|
|
|
|
|
if (cached) return cached;
|
|
|
|
|
|
|
|
|
|
const promise = get('/op/permission')
|
|
|
|
|
.then((res) => normalizeAuthData(res))
|
2026-08-07 14:48:25 +08:00
|
|
|
.catch((err: Error) => {
|
|
|
|
|
handleAuthFetchFailure(err.message);
|
2026-08-04 10:16:32 +08:00
|
|
|
throw err;
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-05 15:30:09 +08:00
|
|
|
setAuthDataPromise(promise);
|
|
|
|
|
return promise;
|
2026-07-23 11:27:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取当前用户的权限编码列表
|
2026-08-04 10:16:32 +08:00
|
|
|
* 复用 fetchAuthData 的缓存,避免重复请求
|
2026-07-23 11:27:33 +08:00
|
|
|
*/
|
|
|
|
|
export function fetchPermissions(): Promise<PermissionCode[]> {
|
2026-08-04 10:16:32 +08:00
|
|
|
return fetchAuthData().then((data) => data.permsList);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// 菜单树 — 基于权限过滤兜底路由
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 按用户拥有的权限码过滤菜单树
|
|
|
|
|
*
|
|
|
|
|
* 规则:
|
|
|
|
|
* - 节点有 permission 字段 → 权限码必须在 permsSet 中才保留
|
|
|
|
|
* - 节点无 permission 但有 children → 保留并递归过滤子节点
|
|
|
|
|
* - 节点被过滤且父节点过滤后无子节点、无 component → 父节点也移除
|
|
|
|
|
*/
|
|
|
|
|
function filterMenuNodesByPermission(nodes: MenuNode[], permsSet: Set<string>): 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. 返回过滤后的菜单树
|
|
|
|
|
*
|
2026-08-05 15:30:09 +08:00
|
|
|
* 权限获取失败时由 fetchAuthData 统一强制重新登录
|
2026-08-04 10:16:32 +08:00
|
|
|
*/
|
|
|
|
|
export function fetchMenuTree(): Promise<MenuNode[]> {
|
|
|
|
|
return fetchAuthData().then(({ permsList }) => {
|
|
|
|
|
const permsSet = new Set(permsList);
|
|
|
|
|
return filterMenuNodesByPermission(FALLBACK_MENU_NODES, permsSet);
|
|
|
|
|
});
|
2026-07-23 11:27:33 +08:00
|
|
|
}
|