(五)Vue3 管理后台:权限控制原理 + 完整流程梳理

(五)Vue3 管理后台:权限控制原理 + 完整流程梳理

对应我们项目:登录 → 获取用户信息 + 角色权限 → 路由守卫 → 按钮级权限控制 (v‑perm 指令)

一、整体业务流程总览

复制代码
用户输入账号密码登录
        ↓
1.登录接口获取token,存入localStorage,pinia保存token
        ↓
2.跳转首页 /home,触发路由守卫 router.beforeEach
        ↓
3.路由守卫判断:本地是否有token
    ├─无token → 强制跳转到登录页
    └─有token → 调用 /api/getUserInfo 获取用户信息{username, roles:['user:add','user:edit']},存入pinia userStore
        ↓
4.页面渲染,全局自定义指令 v‑perm 执行
        ↓
5.v‑perm读取当前用户roles,对比按钮绑定的权限标识
    ├─拥有权限:保留DOM按钮
    └─没有权限:直接移除DOM元素,按钮不显示
        ↓
用户退出登录:清空token、清空pinia用户信息,跳转登录页

二、分模块拆解原理

1. Token 作用(身份凭证)

  1. 用户登录成功后端返回token,保存localStorage,持久化,刷新页面不会丢失。
  2. 后续每次接口请求,axios 请求拦截器自动把 token 放到请求头Authorization,后端靠 token 识别是谁在访问。
  3. token 过期,响应拦截捕获 401,清空本地信息,退回登录页。

token 只代表 "你是谁";不代表你有什么权限 ;权限来自getUserInfo返回的roles数组。

2. Pinia userStore:存放用户数据

src/stores/user.js

  • state:tokenuserInfo:{id, username, roles:[]}
  • loginAction:保存 token
  • getUserInfoAction:调用接口,把后端返回的roles存入userInfo
  • logoutAction:清空 token、清空 userInfo,清除 localStorage

⚠️重点坑点:

pinia 必须在 Vue 组件实例环境下执行;如果在 main.js 全局指令钩子错误时机调用 useUserStore,拿不到真实用户数据,只能拿到初始默认空对象 → 权限失效,也就是刚才遇到的 bug

✅正确:mounted / updated钩子内部再执行useUserStore()

3. 路由守卫 router/index.js beforeEach

路由跳转前执行,做页面访问权限拦截。

复制代码
router.beforeEach((to, from, next)=>{
  const token = localStorage.getItem('token')
  // 没token,非登录页面,跳登录
  if(!token && to.path !== '/login') return next('/login')
  // 有token访问登录页,直接跳首页
  if(token && to.path === '/login') return next('/home')

  // 有token,但是还没有拉取用户信息,则调用store.getUserInfo()
  if(token && !userStore.userInfo.username){
    await userStore.getUserInfo()
  }
  next()
})

路由守卫控制页面能不能进 ;v‑perm 控制页面内部按钮显隐

4. 按钮权限自定义指令 v‑perm 原理

文件 main.js 注册全局指令

复制代码
app.directive('perm',{
  mounted(el, binding){
    // el:绑定指令的DOM元素(就是按钮)
    // binding.value:模板传过来的值 v‑perm="'user:add'" → binding.value = 'user:add'
    const permission = binding.value
    const userStore = useUserStore()
    const roles = userStore.userInfo.roles

    // hasPermission工具函数判断:用户roles数组是否包含当前按钮需要的权限标识
    if(!hasPermission(permission, roles)){
      // 没有权限,直接把按钮DOM从父节点删除,页面看不到
      el.parentNode.removeChild(el)
    }
  },
  updated() {
    // 数据更新(切换角色)时重新执行权限校验
  }
})

utils/permission.js

复制代码
export function hasPermission(permission, roles) {
  if (!permission) return true //没写权限标识默认放行
  return roles.includes(permission)
}

模板使用:

复制代码
<el-button v-perm="'user:add'">新增用户</el-button>

对比v‑if

  • v‑if:Vue 虚拟 DOM 层面控制,dom 可能还留在内存。
  • 自定义指令 v‑perm:直接物理删除真实 DOM 节点,调试工具看不到这个按钮,相对安全一点。

5.Mock 模拟接口角色数据

src/mock/user.js /api/getUserInfo返回 roles 数组

复制代码
data:{
  username:'admin',
  roles:['user:add','user:edit','user:delete']
}

roles 数组是权限的数据源,所有权限判断全部基于这个数组。

修改 mock 后必须重启 pnpm dev,mock 才生效;修改完要退出登录重新登录,重新拉取用户信息。

6. 退出登录流程

  1. 调用userStore.logout()
  2. 清空 pinia 中 token、userInfo
  3. localStorage 清除 token
  4. router 跳转到登录页

三、区分:页面权限 vs 按钮权限

表格

类型 实现方式 作用场景
页面访问权限 路由守卫 beforeEach 控制用户能不能打开某个路由页面
按钮级权限 自定义指令 v‑perm /v‑if 同一个页面,不同角色看不同按钮,新增 / 编辑 / 删除隐藏

拓展:后续做动态路由 ,就是后端返回菜单列表,前端根据 roles 过滤菜单,调用router.addRoute()动态生成侧边栏菜单,控制哪些菜单显示。

四、刚才踩坑复盘(权限失效)

❌错误写法:在指令外面提前实例化const userStore = useUserStore()

复制代码
// 错误!全局顶层执行,此时pinia还没挂载,拿不到登录后的用户数据
const userStore = useUserStore()
app.directive('perm',{
  mounted(el){
    const roles = userStore.userInfo.roles
  }
})

✅正确写法:进入 mounted 钩子函数内部,再调用 useUserStore (),此时 vue、pinia 实例已经完成挂载,可以拿到登录后的真实状态。


五、完整数据流简图

复制代码
Mock后端返回roles数组
        ↓
axios请求getUserInfo
        ↓
pinia userStore.userInfo.roles 保存
        ↓
├─路由守卫:根据roles做页面控制(动态路由用)
└─v‑perm指令读取roles数组,和按钮标识对比,移除无权限DOM
相关推荐
meilindehuzi_a1 小时前
React JWT 登录鉴权实战:从无状态 HTTP 到路由守卫、Zustand 与 Axios 拦截器
前端·react.js·http
爱酱丶1 小时前
VS Code 快速生成Vue3 + TypeScript + Setup 基础空模板
前端·javascript·typescript
Maxkim1 小时前
在 GitHub 仓库里配一个 AI Code Reviewer,自动审查 PR
前端·javascript
忆江南1 小时前
Flutter 图片库与 Dio 库优化实战指南
前端
用户921080262861 小时前
AI 对话为什么需要 Markdown 解析器:从纯文本气泡到专业内容渲染
前端
NeverSettle_1 小时前
Agent 如何快速调用公司接口?——CLI + Skill 实践与踩坑
前端·javascript·后端
环境栈笔记1 小时前
指纹浏览器怎么用:从 Profile、代理到环境检测的完整上手流程
前端·人工智能·后端·自动化
sugar__salt1 小时前
三列布局与 TypeScript 工具类型 Pick / Omit / Partial 详解
前端·javascript·typescript
IMPYLH2 小时前
HTML 的 <html> 元素
前端·javascript·html