feat(rbac): 路由级权限过滤 + 403 页
- 路由守卫按可访问菜单 path 校验,无权跳转 /403 - 新增 403 页 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2f7f83b8b1
commit
84edbc67c0
25
src/pages/403.vue
Normal file
25
src/pages/403.vue
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<template>
|
||||||
|
<div class="error-page">
|
||||||
|
<el-result icon="warning" title="403" sub-title="抱歉,你没有权限访问该页面">
|
||||||
|
<template #extra>
|
||||||
|
<el-button type="primary" @click="goHome">回到首页</el-button>
|
||||||
|
</template>
|
||||||
|
</el-result>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
function goHome(): void {
|
||||||
|
router.push('/')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.error-page {
|
||||||
|
@apply flex h-full items-center justify-center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -9,6 +9,7 @@ import { routes } from 'vue-router/auto-routes'
|
|||||||
import { setupLayouts } from 'virtual:generated-layouts'
|
import { setupLayouts } from 'virtual:generated-layouts'
|
||||||
import i18n, { loadLanguage } from '@/i18n'
|
import i18n, { loadLanguage } from '@/i18n'
|
||||||
import { getAccessToken } from '@/utils/token'
|
import { getAccessToken } from '@/utils/token'
|
||||||
|
import { useMenuStore } from '@/stores/modules/menu'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
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) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
await loadRouteLocales(to)
|
await loadRouteLocales(to)
|
||||||
// 未标记 requiresAuth: false 的路由默认需要登录
|
// 未标记 requiresAuth: false 的路由默认需要登录
|
||||||
const requiresAuth = to.meta.requiresAuth !== false
|
const requiresAuth = to.meta.requiresAuth !== false
|
||||||
if (requiresAuth && !getAccessToken()) {
|
if (!requiresAuth) {
|
||||||
next('/login')
|
|
||||||
} else {
|
|
||||||
next()
|
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')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
13
typed-router.d.ts
vendored
13
typed-router.d.ts
vendored
@ -37,6 +37,13 @@ declare module 'vue-router/auto-routes' {
|
|||||||
{ all: ParamValue<false> },
|
{ all: ParamValue<false> },
|
||||||
| never
|
| never
|
||||||
>,
|
>,
|
||||||
|
'/403': RouteRecordInfo<
|
||||||
|
'/403',
|
||||||
|
'/403',
|
||||||
|
Record<never, never>,
|
||||||
|
Record<never, never>,
|
||||||
|
| never
|
||||||
|
>,
|
||||||
'/demo': RouteRecordInfo<
|
'/demo': RouteRecordInfo<
|
||||||
'/demo',
|
'/demo',
|
||||||
'/demo',
|
'/demo',
|
||||||
@ -174,6 +181,12 @@ declare module 'vue-router/auto-routes' {
|
|||||||
views:
|
views:
|
||||||
| never
|
| never
|
||||||
}
|
}
|
||||||
|
'src/pages/403.vue': {
|
||||||
|
routes:
|
||||||
|
| '/403'
|
||||||
|
views:
|
||||||
|
| never
|
||||||
|
}
|
||||||
'src/pages/demo.vue': {
|
'src/pages/demo.vue': {
|
||||||
routes:
|
routes:
|
||||||
| '/demo'
|
| '/demo'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user