Pinia + Vue Router 权限控制(终极完整版)

🚀 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 控制按钮显示

✅ 目前框架特点

  • 🟢 企业项目标准权限架构
  • 🟢 完整前后端配合
  • 🟢 灵活易扩展
  • 🟢 稳定性高
相关推荐
旺仔牛仔QQ糖2 分钟前
找不到模块“vite”。你的意思是要将 “moduleResolution“ 选项设置为 “node“,还是要将别名添加到 “paths“ 选项中?
前端
Uyker2 分钟前
前端与后端主流框架分类及关键特性
前端·算法·django
GalaxyPokemon6 分钟前
RPC - Response模块
java·前端·javascript
网小鱼的学习笔记22 分钟前
CSS语法中的选择器与属性详解
前端·css
gnip29 分钟前
大屏适配-vm和vh
前端
一入JAVA毁终身38 分钟前
面试第三期
面试·职场和发展
MiyueFE1 小时前
为什么 JavaScript 中 Map 比 Object 更好
javascript
晴殇i1 小时前
3 分钟掌握图片懒加载核心技术:面试攻略
前端·面试·trae
Running_C1 小时前
一文读懂vite和webpack,秒拿offer
前端
咸鱼青菜好好味1 小时前
node的项目实战相关
前端