🚀 Pinia + Vue Router 权限控制(终极完整版)
一、整体设计理念
功能 | 说明 |
---|---|
认证模式 | Token + Role + Permission |
动态路由 | 后端返回,前端动态挂载 |
超管放行 | 超级管理员跳过所有权限校验 |
按钮权限 | v-permission 指令 |
动态标题 | 根据路由 meta.title 自动动态设置 |
缓存优化 | Pinia 持久化,动态路由注入缓存 |
二、核心模块拆分
bash
src/
store/
user.ts
permission.ts
router/
index.ts
routes.ts
utils/
permission.ts
directive/
permission.ts
api/
user.ts
views/
Layout.vue
Login.vue
Dashboard.vue
Admin.vue
Editor.vue
NotFound.vue
三、完整代码实战
1️⃣ user.ts --- 用户 Store
typescript
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
token: '',
roles: [] as string[],
permissions: [] as string[],
isSuperAdmin: false // 超级管理员标识
}),
actions: {
login(token: string, roles: string[], permissions: string[]) {
this.token = token;
this.roles = roles;
this.permissions = permissions;
this.isSuperAdmin = roles.includes('super-admin');
},
logout() {
this.token = '';
this.roles = [];
this.permissions = [];
this.isSuperAdmin = false;
}
},
persist: true
});
2️⃣ api/user.ts --- 模拟后台接口
typescript
import type { RouteRecordRaw } from 'vue-router';
export function getAsyncRoutes(): Promise<RouteRecordRaw[]> {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{
path: '/admin',
component: () => import('@/views/Admin.vue'),
meta: { title: '后台管理', roles: ['admin'], permission: 'admin:manage' }
},
{
path: '/editor',
component: () => import('@/views/Editor.vue'),
meta: { title: '编辑页面', roles: ['editor'], permission: 'editor:edit' }
}
]);
}, 300);
});
}
3️⃣ permission.ts --- 权限 Store
typescript
import { defineStore } from 'pinia';
import { constantRoutes } from '@/router/routes';
import type { RouteRecordRaw } from 'vue-router';
import { getAsyncRoutes } from '@/api/user';
import { useUserStore } from './user';
export const usePermissionStore = defineStore('permission', {
state: () => ({
routes: [] as RouteRecordRaw[]
}),
actions: {
async generateRoutes() {
const userStore = useUserStore();
const asyncRoutes = await getAsyncRoutes();
const accessedRoutes = asyncRoutes.filter(route => {
if (userStore.isSuperAdmin) return true;
if (route.meta?.roles && !userStore.roles.some(role => route.meta?.roles?.includes(role))) {
return false;
}
return true;
});
this.routes = constantRoutes.concat(accessedRoutes);
return accessedRoutes;
}
}
});
4️⃣ utils/permission.ts --- 按钮权限工具
typescript
import { useUserStore } from '@/store/user';
export function hasPermission(permission: string): boolean {
const userStore = useUserStore();
return userStore.isSuperAdmin || userStore.permissions.includes(permission);
}
5️⃣ directive/permission.ts --- v-permission 指令
typescript
import { Directive } from 'vue';
import { hasPermission } from '@/utils/permission';
const permission: Directive = {
mounted(el, binding) {
const value = binding.value;
if (value && !hasPermission(value)) {
el.parentNode?.removeChild(el);
}
}
};
export default permission;
全局注册:
typescript
import permission from '@/directive/permission';
app.directive('permission', permission);
使用示例:
vue
<el-button v-permission="'admin:manage'">仅管理员按钮</el-button>
6️⃣ routes.ts --- 基础路由
typescript
import type { RouteRecordRaw } from 'vue-router';
export const constantRoutes: RouteRecordRaw[] = [
{ path: '/login', component: () => import('@/views/Login.vue'), meta: { title: '登录' } },
{
path: '/',
redirect: '/dashboard',
component: () => import('@/views/Layout.vue'),
children: [
{ path: 'dashboard', component: () => import('@/views/Dashboard.vue'), meta: { title: '首页' } }
]
},
{ path: '/:pathMatch(.*)*', component: () => import('@/views/NotFound.vue'), meta: { title: '404' } }
];
7️⃣ router/index.ts --- 动态路由守卫 + 动态 Title
typescript
import { createRouter, createWebHistory } from 'vue-router';
import { constantRoutes } from './routes';
import { useUserStore } from '@/store/user';
import { usePermissionStore } from '@/store/permission';
const router = createRouter({
history: createWebHistory(),
routes: constantRoutes
});
const whiteList = ['/login'];
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore();
const permissionStore = usePermissionStore();
if (to.meta?.title) {
document.title = to.meta.title as string;
}
if (userStore.token) {
if (permissionStore.routes.length === 0) {
const routes = await permissionStore.generateRoutes();
routes.forEach(route => router.addRoute(route));
next({ ...to, replace: true });
} else {
next();
}
} else {
if (whiteList.includes(to.path)) {
next();
} else {
next('/login');
}
}
});
export default router;
四、核心升级逻辑总结
模块 | 升级点 |
---|---|
超级管理员 | 登录时 isSuperAdmin 直接放行所有路由和按钮 |
动态 Title | 每次切换路由动态修改 document.title |
双重模型 | 路由走 roles 控制,按钮走 permissions 控制 |
五、完整权限框架架构图
bash
- 登录成功返回:
- token
- roles(角色列表)
- permissions(按钮权限码)
- asyncRoutes(动态路由列表)
- 前端:
- Pinia缓存用户信息
- 动态注入路由
- 路由守卫控制进入
- v-permission 控制按钮显示
✅ 目前框架特点
- 🟢 企业项目标准权限架构
- 🟢 完整前后端配合
- 🟢 灵活易扩展
- 🟢 稳定性高