Pinia + Vue Router 权限控制(终极完整版)
🚀 Pinia + Vue Router 权限控制(终极完整版)
一、整体设计理念
功能 | 说明 |
---|---|
认证模式 | Token + Role + Permission |
动态路由 | 后端返回,前端动态挂载 |
超管放行 | 超级管理员跳过所有权限校验 |
按钮权限 | v-permission 指令 |
动态标题 | 根据路由 meta.title 自动动态设置 |
缓存优化 | Pinia 持久化,动态路由注入缓存 |
二、核心模块拆分
src/store/user.tspermission.tsrouter/index.tsroutes.tsutils/permission.tsdirective/permission.tsapi/user.tsviews/Layout.vueLogin.vueDashboard.vueAdmin.vueEditor.vueNotFound.vue
三、完整代码实战
1️⃣ user.ts — 用户 Store
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 — 模拟后台接口
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
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 — 按钮权限工具
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 指令
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;
全局注册:
import permission from '@/directive/permission';
app.directive('permission', permission);
使用示例:
<el-button v-permission="'admin:manage'">仅管理员按钮</el-button>
6️⃣ routes.ts — 基础路由
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
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 控制 |
五、完整权限框架架构图
- 登录成功返回:- token- roles(角色列表)- permissions(按钮权限码)- asyncRoutes(动态路由列表)- 前端:- Pinia缓存用户信息- 动态注入路由- 路由守卫控制进入- v-permission 控制按钮显示
✅ 目前框架特点
- 🟢 企业项目标准权限架构
- 🟢 完整前后端配合
- 🟢 灵活易扩展
- 🟢 稳定性高