完善平台角色菜单树与授权登录导航

This commit is contained in:
czl231
2026-08-17 21:56:22 +08:00
parent c7449cb38d
commit d6bb3bd43e
22 changed files with 739 additions and 17 deletions

View File

@@ -0,0 +1,85 @@
/**
* 功能:为平台管理员选择安全的登录落点。
* 版本v1.0.0
*/
import type {
RouteLocationRaw,
RouteMeta,
RouteRecordName,
Router,
} from 'vue-router';
export const NO_PERMISSION_ROUTE_NAME = 'noPermission';
export interface AuthorizedMenuRoute {
name?: RouteRecordName | null;
meta?: RouteMeta;
children?: AuthorizedMenuRoute[];
}
/**
* 按服务端菜单顺序寻找首个可落地的叶子页面。
* 父级分组和隐藏详情页不能成为默认首页。
*/
export function findFirstAuthorizedMenuRoute(
routes: readonly AuthorizedMenuRoute[],
): RouteLocationRaw | null {
for (const route of routes) {
if (route.children?.length) {
const child = findFirstAuthorizedMenuRoute(route.children);
if (child) return child;
continue;
}
if (route.name && route.meta?.hideInMenu !== true) {
return { name: route.name };
}
}
return null;
}
/** 仅允许恢复当前角色确实有权访问的叶子路由。 */
export function isAuthorizedLoginTarget(
router: Router,
target: RouteLocationRaw,
role: string,
menuCodes: readonly string[],
) {
const resolved = router.resolve(target);
const terminal = resolved.matched[resolved.matched.length - 1];
if (
!terminal ||
resolved.name === 'login' ||
resolved.name === 'notFound' ||
resolved.name === NO_PERMISSION_ROUTE_NAME ||
terminal.redirect ||
terminal.children.length
) {
return false;
}
const menuCode = resolved.meta.menuCode;
return (
role === 'root' ||
(typeof menuCode === 'string' && menuCodes.includes(menuCode))
);
}
/** 优先恢复有权历史地址,否则进入首个授权菜单或权限引导页。 */
export function resolveAuthorizedLanding(
router: Router,
menus: readonly AuthorizedMenuRoute[],
role: string,
menuCodes: readonly string[],
requested?: RouteLocationRaw,
): RouteLocationRaw {
if (
requested &&
isAuthorizedLoginTarget(router, requested, role, menuCodes)
) {
return requested;
}
return (
findFirstAuthorizedMenuRoute(menus) ?? {
name: NO_PERMISSION_ROUTE_NAME,
}
);
}

View File

@@ -1,6 +1,7 @@
export const WHITE_LIST = [
{ name: 'notFound', children: [] },
{ name: 'login', children: [] },
{ name: 'noPermission', children: [] },
];
export const NOT_FOUND = {

View File

@@ -4,7 +4,11 @@ import 'nprogress/nprogress.css';
import createRouteGuard from './guard';
import { appRoutes } from './routes';
import { NOT_FOUND_ROUTE, REDIRECT_MAIN } from './routes/base';
import {
NOT_FOUND_ROUTE,
NO_PERMISSION_ROUTE,
REDIRECT_MAIN,
} from './routes/base';
NProgress.configure({ showSpinner: false }); // NProgress Configuration
@@ -25,6 +29,7 @@ const router = createRouter({
},
...appRoutes,
REDIRECT_MAIN,
NO_PERMISSION_ROUTE,
NOT_FOUND_ROUTE,
],
scrollBehavior() {

View File

@@ -24,6 +24,17 @@ export const REDIRECT_MAIN: RouteRecordRaw = {
],
};
/** 已认证但暂无业务菜单时的独立引导页。 */
export const NO_PERMISSION_ROUTE: RouteRecordRaw = {
path: '/no-permission',
name: 'noPermission',
component: () => import('@/views/no-permission/index.vue'),
meta: {
requiresAuth: true,
hideInMenu: true,
},
};
export const NOT_FOUND_ROUTE: RouteRecordRaw = {
path: '/:pathMatch(.*)*',
name: 'notFound',