【vue高频面试题—场景篇】:如何实现完善的动态路由与按钮权限控制?

59 阅读3分钟

1. 场景背景

面试官提问: “在一个中大型后台管理系统中,不同角色(如管理员、运营、财务)登录后看到的侧边栏菜单是不一样的,甚至同一个页面上,管理员能看到‘删除’按钮,而普通员工看不到。请问你如何设计并实现这套权限系统?请从前端架构的角度谈谈你的实现流程。”


2. 核心考点

  • 路由守卫(Navigation Guards) 的运用。
  • 动态添加路由(addRoute) 的机制。
  • 自定义指令(Custom Directives) 的实现。
  • 后端菜单权限数据结构设计

3. 综合解决方案

第一步:路由设计(静态与动态分离)

不要将所有路由都写在 routes 数组里。

  • 公有路由(Constant Routes): 登录页、404 页面等,直接初始化。
  • 私有路由(Async Routes): 包含 meta: { roles: ['admin'] } 等信息的页面,等待权限过滤。

第二步:登录与获取权限(核心流程)

  1. 用户登录拿到 Token 并存储。
  2. 在路由前置守卫 router.beforeEach 中判断:如果用户已登录但没有用户信息(或权限列表),则调用后端接口。
  3. 关键点: 后端返回一个权限标识符列表(如 ['order:list', 'order:delete'])或菜单树。

第三步:动态生成路由(addRoute)

  1. 前端根据后端返回的权限数据,在 Vuex/Pinia 中过滤出当前用户有权访问的 Async Routes
  2. 使用 router.addRoute() 动态将这些路由注入到路由实例中。
  3. 避坑: 注入后需要执行一次 next({ ...to, replace: true }) 来确保路由生效。

第四步:按钮级权限控制

对于页面内的操作按钮,使用 自定义指令

JavaScript

// v-permission 指令实现
const permission = {
  mounted(el, binding) {
    const { value } = binding; // 获取指令绑定的权限值
    const userPermissions = store.getters.permissions; // 获取用户拥有的权限列表
    if (value && !userPermissions.includes(value)) {
      el.parentNode && el.parentNode.removeChild(el); // 无权限则删除 DOM
    }
  }
}

4. 满分回答示例

回答要点: “我会采取 ‘前端定义路由表,后端返回权限标识’ 的方案来实现方案。

首先,在代码层面,我会将路由分为常量路由异步动态路由

  1. 动态挂载: 在全局路由守卫 beforeEach 中,当用户登录后,我会请求后端接口获取其角色的‘权限码’数组。然后利用一个递归函数,将本地定义的动态路由表与权限码进行匹配,筛选出可访问的路由,最后通过 router.addRoute 动态添加到路由系统中。
  2. 菜单渲染: 侧边栏菜单直接根据 Vuex/Pinia 中存储的这部分过滤后的路由表进行 v-for 渲染,从而保证 UI 与权限的一致性。
  3. 细粒度控制: 针对按钮权限,我会封装一个全局自定义指令 v-permission。这样在业务代码中,只需要写 <button v-permission="'user:delete'">删除</button> 即可。
  4. 安全加固: 除了前端控制,我还会强调必须配合后端接口拦截,因为前端的所有隐藏都只是交互层面的,真正的安全屏障在后端。”

5. 查漏补缺(进阶加分项)

  • 路由硬链接访问: 如果用户手动在地址栏输入无权访问的 URL,由于 addRoute 没注册该路由,会自动触发 404,这保证了安全性。
  • 退出登录重置: 退出登录时,必须重置路由实例(或者直接 location.reload()),否则切换账号后,上一个账号的路由可能依然存在。