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

✅ 目前框架特点

  • 🟢 企业项目标准权限架构
  • 🟢 完整前后端配合
  • 🟢 灵活易扩展
  • 🟢 稳定性高
相关推荐
前端 贾公子3 小时前
pnpm 的 resolution-mode 配置 ( pnpm 的版本解析)
前端
伍哥的传说4 小时前
React 自定义Hook——页面或元素滚动到底部监听 Hook
前端·react.js·前端框架
gadiaola5 小时前
【SSM面试篇】Spring、SpringMVC、SpringBoot、Mybatis高频八股汇总
java·spring boot·spring·面试·mybatis
麦兜*5 小时前
Spring Boot 集成Reactive Web 性能优化全栈技术方案,包含底层原理、压测方法论、参数调优
java·前端·spring boot·spring·spring cloud·性能优化·maven
Jinkxs5 小时前
JavaScript性能优化实战技术
开发语言·javascript·性能优化
知了一笑6 小时前
独立开发第二周:构建、执行、规划
java·前端·后端
UI前端开发工作室6 小时前
数字孪生技术为UI前端提供新视角:产品性能的实时模拟与预测
大数据·前端
Sapphire~6 小时前
重学前端004 --- html 表单
前端·html
TE-茶叶蛋7 小时前
Flutter、Vue 3 和 React 在 UI 布局比较
vue.js·flutter·react.js
Maybyy7 小时前
力扣242.有效的字母异位词
java·javascript·leetcode