路由与权限控制
约 490 字大约 2 分钟
布欧-Lewyon
2026-05-15
路由配置
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/login',
name: 'login',
component: () => import('@/views/Login.vue'),
meta: { guest: true, title: '登录' }
},
{
path: '/',
component: () => import('@/layouts/MainLayout.vue'),
children: [
{
path: '',
redirect: '/dashboard'
},
{
path: 'dashboard',
name: 'dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { title: '仪表盘', requiresAuth: true, permission: 'dashboard:view' }
},
{
path: 'board/:id',
name: 'board-detail',
component: () => import('@/views/BoardDetail.vue'),
meta: { title: '看板详情', requiresAuth: true, permission: 'board:view' }
}
]
},
{
path: '/403',
name: 'forbidden',
component: () => import('@/views/errors/Forbidden.vue')
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/errors/NotFound.vue')
}
]
})路由守卫
// router/guard.ts
import router from './index'
import { useAuthStore } from '@/stores/auth'
const WHITE_LIST = ['/login', '/403', '/404']
router.beforeEach(async (to, from) => {
const authStore = useAuthStore()
// 设置页面标题
document.title = `${to.meta.title} | 看板系统`
// 白名单放行
if (WHITE_LIST.includes(to.path)) return true
// 未登录 → 登录页
if (!authStore.isLoggedIn) {
try {
await authStore.fetchUser()
} catch {
return { path: '/login', query: { redirect: to.fullPath } }
}
}
// 权限检查
const requiredPerm = to.meta.permission as string | undefined
if (requiredPerm && !authStore.hasPermission(requiredPerm)) {
return { name: 'forbidden' }
}
})
router.afterEach(() => {
// 页面埋点等
})权限组件
<!-- components/common/Permission.vue -->
<script setup lang="ts">
import { useAuthStore } from '@/stores/auth'
import { computed } from 'vue'
const props = defineProps<{
code: string
mode?: 'one' | 'all'
}>()
const authStore = useAuthStore()
const hasPermission = computed(() => {
const codes = Array.isArray(props.code) ? props.code : [props.code]
return props.mode === 'all'
? codes.every(c => authStore.hasPermission(c))
: codes.some(c => authStore.hasPermission(c))
})
</script>
<template>
<slot v-if="hasPermission" />
<slot v-else name="fallback" />
</template>使用:
<Permission code="board:create">
<button @click="createBoard">新建看板</button>
<template #fallback>
<button disabled title="无权限">新建看板</button>
</template>
</Permission>动态路由
// router/dynamic.ts
import router from './index'
const adminRoutes = [
{
path: 'admin',
name: 'admin',
component: () => import('@/views/admin/AdminLayout.vue'),
meta: { title: '管理后台', requiresAuth: true },
children: [
{
path: 'users',
name: 'admin-users',
component: () => import('@/views/admin/UserManage.vue'),
meta: { title: '用户管理', permission: 'admin:users' }
}
]
}
]
export function setupDynamicRoutes(role: string) {
if (role === 'admin') {
adminRoutes.forEach(route => {
if (!router.hasRoute('admin')) {
router.addRoute(route)
}
})
}
}在 Auth Store 的 login 和 fetchUser 成功后调用:
async function login(credentials) {
const data = await request.post('/auth/login', credentials)
// ...保存状态
setupDynamicRoutes(data.user.role)
}
async function fetchUser() {
const data = await request.get('/auth/me')
// ...保存状态
setupDynamicRoutes(data.user.role)
}小结
| 功能 | 实现 |
|---|---|
| 路由守卫 | beforeEach 鉴权 + 权限检查 |
| 导航白名单 | /login / /403 / /404 |
| 权限组件 | Permission 组件 + fallback 插槽 |
| 动态路由 | addRoute 根据角色动态添加 |
下一节:测试与部署
