(五)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:token、userInfo:{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
相关推荐
时间的情敌12 小时前
Cesium 流动管线、管道动画、管线流向仿真,管网、油气、水利孪生高频需求
前端·交互·cesium
wangxiaoxuan21713 小时前
SLAM 的基本结构:前端、后端、回环与地图优化
前端·机器人·科沃斯八界
Agent世界16 小时前
用 CDP 做发布自动化:四道机器闸门与产物验收
前端·javascript
FungLeo16 小时前
成为全栈·Vue 管理后台篇·编辑器图片上传:从文件选择到文章附件关联
前端·vue.js·编辑器·markdown·文件上传·formdata·附件管理
某哈压力大17 小时前
SSH 突然 Connection reset?不是密钥坏了,是 OpenSSL 把 sshd 干趴了
前端·node.js·命令行
程序员大阳17 小时前
副队长ES6+教程(3)--解构赋值
前端·javascript·es6·赋值·解构
神膘护体小月半17 小时前
css - 元素背景图由 hover 触发,初次加载时闪动的解决办法
前端·javascript·css
程序员大阳17 小时前
副队长ES6+教程(2)--let 与 const
前端·javascript·es6·const·let·var
deli00718 小时前
写满 200GB 后 SSD 为什么掉到 95MB/s?SLC 缓存模型实测
前端
曦樂~18 小时前
【Web Development】03JavaScript 基础和事件
开发语言·前端·javascript