diff --git a/src/pages/403.vue b/src/pages/403.vue new file mode 100644 index 0000000..74be3d0 --- /dev/null +++ b/src/pages/403.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/src/router/index.ts b/src/router/index.ts index 40b2030..9d82cd2 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -9,6 +9,7 @@ import { routes } from 'vue-router/auto-routes' import { setupLayouts } from 'virtual:generated-layouts' import i18n, { loadLanguage } from '@/i18n' import { getAccessToken } from '@/utils/token' +import { useMenuStore } from '@/stores/modules/menu' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -39,15 +40,49 @@ async function loadRouteLocales(to: RouteLocationNormalized) { } } +// 无需菜单权限校验的路径(首页 + 错误页) +const FREE_PATHS = new Set(['/', '/403']) + +/** 收集菜单树中所有可访问 path。 */ +function collectMenuPaths(nodes: { path?: string | null; children?: unknown[] }[]): string[] { + const paths: string[] = [] + for (const n of nodes) { + if (n.path) paths.push(n.path) + if (n.children?.length) paths.push(...collectMenuPaths(n.children as { path?: string | null; children?: unknown[] }[])) + } + return paths +} + // 路由前置守卫 router.beforeEach(async (to, from, next) => { await loadRouteLocales(to) // 未标记 requiresAuth: false 的路由默认需要登录 const requiresAuth = to.meta.requiresAuth !== false - if (requiresAuth && !getAccessToken()) { - next('/login') - } else { + if (!requiresAuth) { next() + return + } + if (!getAccessToken()) { + next('/login') + return + } + // 路由级权限:自由路径放行,其余须在可访问菜单中 + if (FREE_PATHS.has(to.path)) { + next() + return + } + const menuStore = useMenuStore() + try { + await menuStore.fetch() + } catch { + next('/login') + return + } + const accessible = new Set(collectMenuPaths(menuStore.menus)) + if (accessible.has(to.path)) { + next() + } else { + next('/403') } }) diff --git a/typed-router.d.ts b/typed-router.d.ts index daa4a1c..470712c 100644 --- a/typed-router.d.ts +++ b/typed-router.d.ts @@ -37,6 +37,13 @@ declare module 'vue-router/auto-routes' { { all: ParamValue }, | never >, + '/403': RouteRecordInfo< + '/403', + '/403', + Record, + Record, + | never + >, '/demo': RouteRecordInfo< '/demo', '/demo', @@ -174,6 +181,12 @@ declare module 'vue-router/auto-routes' { views: | never } + 'src/pages/403.vue': { + routes: + | '/403' + views: + | never + } 'src/pages/demo.vue': { routes: | '/demo'