首页 / 资讯中心 / 文章详情

vue-element-admin实战解析:权限路由、动态菜单与后台管理系统脚手架搭建

vue-element-admin实战解析:权限路由、动态菜单与后台管理系统脚手架搭建 ★ FEATURED ARTICLE
简介这是一套基于 Vue 2 与 Element UI 的企业级后台管理界面模板适合前端开发者、全栈工程师以及需要快速搭建后台系统的团队。框架集成了动态路由、Vuex 状态管理、基于角色的权限控制、Axios 请求统一封装、国际化多语言、Mock 模拟数据、操作日志记录、Webpack 构建配置等核心功能并配套项目总结文档和常用开发工具安装包能够显著降低从零搭建管理后台的成本。资源包共含 324 个文件主要类型包括 PNG/SVG 图标、Markdown 与 Word 文档、JavaScript 脚本、Vue 组件、HTML 预览页面及少量 PDF、Excel 文件压缩后大小约 526.69MB目录结构清晰便于按模块查阅。目前已有 259 人学习下载内容涉及权限控制、前后端分离单点登录、人资项目总结等实战场景对深入理解 Vue 组件化开发及 Element UI 组件的应用很有帮助。无论是学习后台开发模式还是直接用于项目二次开发都能从中获得完整参考。1. 还在用 Vue2 的团队为什么绕不开 vue-element-admin 这类框架vue-element-admin 不是一套可以直接上线的前端产品而是一套基于 Vue2 与 Element UI 搭建的后台管理系统脚手架。团队手里的存量后台系统大多还在 Vue2 技术栈上排期单上反复出现“动态路由”“权限菜单”“多环境打包”这类需求最后几乎都会绕回这个框架——因为它把后台管理系统里最费时间的权限路由、请求封装、菜单联动和用户会话管理提前做好了一套团队不需要从零开始造轮子。它解决的问题很具体让一个 3 到 5 人的前端小组在两周内拉起一套能进入联调的后台项目而不是花两个月搭基建。适合两类人一类是要维护存量 Vue2 后台的开发者另一类是刚接手中小型管理系统的前端新手。需要注意一个预期它给你的是工程骨架不是业务成品登录接哪个接口、菜单按什么权限显示、按钮如何控制颗粒度这些落地动作仍然要自己完成。2. 先把框架的地基摸清技术栈组成与本地启动路径2.1 技术栈里每一个点都是为什么这样选整套框架建立在 Vue2 的生态之上选型逻辑基本可以看作 2018 年到 2020 年间后台管理系统的标准答案Vue2.x 作为核心框架搭配 Vuex 做全局状态管理Vue Router 负责路由跳转Element UI 提供表格、表单、弹窗等后台常用的 UI 组件axios 承担 HTTP 请求webpack 处理构建打包。这套组合在今天看来不算最新但在存量项目维护场景里仍然是最稳妥的搭配。为什么不是 Vue3不是这套框架的作者不跟进新生态而是它服务的存量市场里大量系统还在用 Vue2 维护。很多团队接手的老项目就是基于这个框架二开的新功能要在老骨架上加不可能整体迁移到 Vue3。另一个原因是 Element UI 的组件生态和 Vue2 的插件兼容性非常成熟第三方组件库、图表库、拖拽库基本都对齐过不需要像 Vue3 生态那样反复排查兼容问题。Vuex 在框架里承担的角色比一般项目更重。它不只是存用户昵称头像而是存了登录令牌、用户角色、动态路由表、侧边栏展开状态、标签页缓存。这些状态之间是有依赖关系的登录之后拿到角色角色决定路由表路由表决定菜单菜单决定页面能否访问。用 Vuex 管理这些联动状态比单纯放在 localStorage 里更容易控制更新时序。Vue Router 的配置方式也很有特点路由不是一次性全部注册的而是先注册公共路由再在登录后根据用户角色动态添加剩余的路由。这个设计贯穿整个权限体系理解它之后后面看权限代码就不会觉得绕。Element UI 的使用方式也影响项目体积。框架默认支持按需引入组件配合 babel-plugin-component 插件打包时只引入实际用到的组件和样式。实际项目中很多人图省事直接全量引入首屏会多出几百 KB后台管理系统不在乎这点体积但如果要优化首屏按需引入这条路径是通的。2.2 把项目跑起来的最小操作与目录定位拿到项目代码后第一步不是读源码而是先跑起来。仓库根目录下的 package.json 里已经写好了启动命令安装依赖用 npm 或 yarn 都可以但要注意 Node 版本。Vue2 项目对 Node 版本有隐性要求Node 17 以上容易在构建时报 OpenSSL 错误常见的做法是用 Node 14 到 Node 16 之间的 LTS 版本。# 安装依赖注意锁定 Node 版本在 14.x ~ 16.x npm install # 启动开发服务默认端口一般配在 9527 npm run dev安装依赖时如果卡在 node-sass 或者 node-gyp 这类编译型依赖上基本可以判定是 Node 版本问题或者网络问题。node-sass 需要从 GitHub 下载二进制文件下载失败会导致整个安装流程退到源码编译非常耗时。我一般会优先换成 cnpm 镜像或者直接把 node-sass 换成 sass 的 dart 实现版本前者解决网络后者解决编译兼容。# 如果 node-sass 编译失败常见做法是换包管理器重装 npm install --registryhttps://registry.npmmirror.com项目跑起来之后目录定位比读代码更重要。src 下的结构划分很清晰views 放页面组件router 放路由配置store 放全局状态utils 放工具函数api 放接口请求定义layout 放整体布局组件。新手最容易迷失的是 permission.js 这个文件它不在 store 里也不在 router 里而是挂在 src 目录下作为路由守卫的入口。整个项目的权限逻辑都从这里开始。这个阶段的目标不是看懂每一行代码而是在脑海里建立一张地图登录时发生了什么、路由守卫拦了什么、菜单是从哪里渲染出来的。地图建立起来之后改权限需求才不会改崩。3. 权限路由的设计动态菜单在 vue-element-admin 里如何生效3.1 从登录到路由挂载的一次完整链路权限路由是这套框架的核心资产也是大多数团队选择它的第一理由。先看一条完整的链路用户在登录页输入账号密码提交后请求后端拿到令牌令牌存进 cookie路由守卫接收到跳转动作发现没有令牌就强制跳回登录页有了令牌之后守卫放行到首页同时触发获取用户信息的请求拿到当前用户的角色和权限标识权限模块根据角色从完整路由表里筛出可访问的路由再调用 Vue Router 的 addRoutes 方法动态挂载侧边栏组件监听路由表变化自动渲染出对应该用户的菜单。这条链路的关键在于“先有权限数据再挂路由”。如果顺序反过来比如在没有拿到角色信息时就先放行页面就会因为路由不存在而白屏。框架用 Promise 把这几步串起来保证在 addRoutes 完成之后才调用 next() 放行导航。// src/permission.js 里的核心逻辑简化描述 router.beforeEach(async (to, from, next) { const hasToken getToken() if (!hasToken) { next(/login?redirect${to.path}) return } // 判断 Vuex 里是否已经生成过路由表 if (store.getters.permission_routes.length 0) { const roles await store.dispatch(user/getInfo) const accessRoutes await store.dispatch(permission/generateRoutes, roles) router.addRoutes(accessRoutes) next({ ...to, replace: true }) } else { next() } })这段代码里有几个容易忽略的细节。next 里的 replace: true 是为了在动态路由添加完成后重新进入目标路由避免用户停留在空白页判断不出来。permission_routes 这个 getter 是缓存标记如果刷新页面后 Vuex 状态清空路由表长度为 0守卫就会重新走一遍完整流程。这两个细节直接关系到后面要讲的刷新页面 404 问题。3.2 基于角色的路由筛选与侧边栏菜单联动路由表被分成了两部分constantRoutes 和 asyncRoutes。前者永远注册包括登录页、404 页、首页这些所有角色都能访问的页面后者是权限路由只有被当前角色允许的路由才会被 addRoutes 挂载。asyncRoutes 里的每个路由通过 meta.roles 字段声明“谁能访问”管理模式与后端的角色字段一一对应。筛选用的是递归过滤因为路由配置可能是多级嵌套的父路由有权限不代表子路由都有权限需要逐层判断。这个过程在 store/modules/permission.js 里完成核心是 filterAsyncRoutes 函数。// store/modules/permission.js 中的路由过滤逻辑 function filterAsyncRoutes(routes, roles) { const res [] routes.forEach(route { const tmp { ...route } if (hasPermission(roles, tmp)) { if (tmp.children) { tmp.children filterAsyncRoutes(tmp.children, roles) } res.push(tmp) } }) return res } function hasPermission(roles, route) { if (route.meta route.meta.roles) { return roles.some(role route.meta.roles.includes(role)) } return true }这里有一个默认策略值得注意如果路由的 meta 里没有定义 roles那么这个路由对所有登录用户都开放。写业务路由时很多人忘记加 meta.roles结果权限过滤形同虚设。另一个要注意的是子路由过滤后可能变成空数组父路由仍然会被保留菜单上就会出现一个点了没反应的空白菜单项这个后面排查时经常遇到。菜单渲染的机制是侧边栏组件遍历 Vuex 里的 permission_routes 状态生成菜单项meta.title 是菜单显示名meta.icon 是显示图标。因为菜单和路由表共用同一份数据源所以不需要单独维护一份菜单配置改路由就是改菜单。这个设计非常方便但也带来一个副作用路由表的嵌套层级直接变成菜单的嵌套层级隐藏路由或者非菜单路由需要额外标记 hidden: true 才能不显示在侧边栏。这个阶段最容易出问题的点集中在两个地方一是动态路由生成之后刷新页面Vuex 状态清空导致路由表丢失二是 addRoutes 和 next() 的执行顺序导致页面白屏。这两类问题太常见了后面专开一章详细讲排查思路。4. 把框架接到真实后端axios 封装、环境变量与代理配置4.1 统一请求封装里要改的四个关键位置框架内置了一套 axios 封装位置在 src/utils/request.js。这套封装的目的是让业务代码里不需要关心令牌怎么带、错误怎么提示、状态码怎么处理统一收敛在一个文件里。接到真实后端时不需要重写这套逻辑只要改四个关键位置baseURL、请求拦截器里的令牌字段、响应拦截器里的解包规则、错误处理里的状态码判断。// src/utils/request.js 的关键配置按需调整 const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) service.interceptors.request.use(config { const token getToken() if (token) { config.headers[Authorization] Bearer ${token} } return config }) service.interceptors.response.use( response { const res response.data if (res.code ! 20000) { Message.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res.data }, error { if (error.response error.response.status 401) { removeToken() location.reload() } return Promise.reject(error) } )baseURL 一般不会从 .env.development 里直接读成绝对路径而是读到空字符串再用 devServer 的 proxy 转发这样开发环境不会跨域。Authorization 头的字段名要看后端要求有的后端用 token有的用 Authorization这里的 Bearer 前缀也要和后端对齐否则登录后会一直报 401。响应拦截器里的 res.code 判断的是业务状态码每个公司的编码规则不一样需要问后端要接口文档后逐项核对最怕的是后端把成功状态码定义成 200而拦截器里写的是 20000。超时时间 10000 毫秒也值得确认。后台管理系统经常有批量导出的接口耗时可能超过 30 秒超时时间不做调整的话导出请求会在中途被前端主动断开后端处理不出结果。我一般会按接口类型区分普通查询保持默认超时导出类接口单独走一个放开超时限制的 axios 实例。4.2 从 mock 切到真实接口时最容易出错的两个断点框架内置了 mock 数据本地开发时所有登录、用户信息、列表接口都返回假数据。切到真实后端时两个断点最容易踩第一个是登录接口的返回结构第二个是接口路径的前缀。mock 模式下登录接口返回的是 { code: 20000, data: { token } }真实后端不一定按这个结构返回。有的后端把 token 放在 data 里的 token 字段有的放在 token 字段下的 access_token 里有的甚至直接用账号密码作为令牌。为了兼容把登录页的代码从“固定取 data.token”改成“取配置好的字段路径”是第一步。登录成功之后用户信息接口的返回结构同样要对齐特别是 roles 字段它直接驱动权限路由生成如果后端返回的是 roles: [admin] 而前端判断的是 roles.includes(admin)只要字符串大小写不一致权限就全部失效。第二个断点是接口路径前缀。mock 请求走的是 /mock/api 这样的路径真实接口走的是 /api/user/login 或 /prod-api/user/login。要把 .env.development 里的 VUE_APP_BASE_API 改成和后端网关一致的前缀再在 vue.config.js 里配置代理把该前缀转发到后端服务器地址。# .env.development 里常见的配置 VUE_APP_BASE_API/api// vue.config.js 里的 devServer 代理配置 devServer: { port: 9527, proxy: { /api: { target: http://192.168.1.100:8080, changeOrigin: true } } }配置代理时常犯的错误是忘记 changeOrigin: true。不加这个配置时后端接收请求的 Host 头是前端的地址有的后端服务器会基于 Host 做校验直接拒绝请求现象就是浏览器能看到请求发出、控制台里却是 403。加上 changeOrigin 后Host 头会改成 target 的地址脏活交给 webpack 的代理机制处理前端不用操心。环境变量这块建议顺手把 .env.production 也确认一遍。生产环境不需要代理VUE_APP_BASE_API 直接写后端网关的完整域名再把静态资源路径 publicPath 改成相对路径或 CDN 路径避免部署到子目录时找不到 JS 文件。5. 实战中的高频坑位排查记录5.1 刷新页面就 404动态路由与 404 兜底路由的“打架”现象登录后正常访问页面一按 F5 刷新就跳 404只能重新登录。原因404 页面路由原本会被 addRoutes 动态挂载但它如果被放在 asyncRoutes 里刷新时 Vuex 状态清空动态路由还没有重新生成导航就先匹配到了静态路由表里的 404 兜底路由于是直接跳去了 404 页面。解决把 404 路由从 asyncRoutes 挪到 constantRoutes 里让它成为静态公共路由。这样刷新时路由立即存在等动态路由重新生成后再进入目标页面。修改之后要验证一下动态路由的匹配顺序确保 addRoutes 的路由里不要有 path: * 之类的通配路由否则通配把真实页面全部吃掉依然 404。5.2 页面白屏路由守卫的“先放行后挂载”竞态现象登录成功之后偶尔白屏控制台不报错刷新一次又恢复正常在弱网环境或者接口响应慢时白屏概率明显上升。原因permission.js 里的路由守卫没有等 addRoutes 执行完就调用了 next()。动态路由挂载是异步动作页面组件会在路由表里找不到对应路由时渲染空白。常见写法是拿到 accessRoutes 后直接 next()以为 addRoutes 是同步的实际上路由匹配发生在导航确认时。解决在 addRoutes 完成之后再调用 next并且使用 next({ ...to, replace: true }) 重新触发一次导航。这一步的键是保证导航重新匹配的是已经挂载好的完整路由表。排查时可以打开 Vue Router 的导航日志看路由是否匹配到该路由还是匹配到了空路由。5.3 数据解包层级对不上响应拦截器的一次性统一现象所有列表页面的表格都是空的控制台没报错Network 面板里接口返回正常数据结构看起来也没问题。原因框架的响应拦截器统一返回 res.data也就是把外层包了一层再塞给业务代码。如果后端实际返回的是 { code: 0, data: { list: [...] } }框架的拦截器再取一次 res.data 就拿到了 { list: [...] }可业务代码里还按 res.data.list 去取结果自然多了一层查找得到 undefined。解决先到 request.js 里确认拦截器返回的层级再回去看页面里的取值。后端返回结构写成什么样的都有有的是 { code, data, message }有的是 { status, result, msg }统一在拦截器里对齐一次让业务代码永远面对同一份数据结构。改完拦截器后全局接口都会变需要跑一遍主流程确认没有别的地方依赖旧层级。5.4 图标不渲染动态路由 meta 里的 icon 不是万能的现象菜单能正常显示文字但图标位置是一块空白后台管理页面里 Element UI 的其余图标正常唯独菜单图标加载不出来。原因框架的菜单图标并不直接使用 Element UI 的 icon 类名而是依赖一组 svg 图标组件。动态路由 meta.icon 里写的名字必须在项目的 svg 文件夹里有对应文件或者需要显式注册到图标组件里。写了一个不存在的名字或者直接写了 element-icon 的类名菜单里虽然能挂上名称但图标找不到渲染源。解决先确认菜单里用的图标名是否真的存在于项目内部图标集合里。需要新增图标时把 svg 文件放进指定目录再全局注册之后 meta.icon 写对应文件名就能显示。如果团队习惯用 Element UI 自带图标可以改造菜单渲染组件让它走 element-icon 类名的方式二选一不要两种混用。5.5 Element UI 按需引入后组件样式丢失现象引入 Dialog、Select、DatePicker 之后功能正常但样式要么完全没有要么只加载了一部分页面看起来像裸 HTML。原因按需引入的 babel 插件没有正确匹配到使用的组件样式文件或者组件的样式在打包时被 tree-shaking 掉。特别是使用第三方扩展组件时它们依赖的 Element UI 基础组件没有显式引入基础组件的样式也不会被自动带出来。解决确认 babel.config.js 里 element-ui 按需引入的配置存在且生效。更稳妥的做法是在全局 bundle 里引入 element-ui 的完整样式文件用体积换稳定性。后台管理系统通常在登录页和内容页都重度依赖 Element UI避免在一个小问题上消耗排查时间全量引入是性价比更高的选择。6. 让它落地成你自己项目的骨架改造顺序与验证方法接手这套框架后按什么顺序改造直接决定项目后期维护是否舒服。我通常建议隔离改动先改配置后改业务具体步骤是第一步锁定环境变量和代理把 mock 与真实接口切换开关理顺保证前端同事不需要改代码就能切换环境第二步对齐后端返回结构把拦截器的 code 判断和解包层级一次性做对第三步梳理角色与路由的映射关系确认后端返回的角色字段与前端 meta.roles 完全一致最后一步才是写业务页面和列表此时基础链路已经通了写页面不需要反复回查登录和权限逻辑。验证方法也很直接登录一个普通角色账号打开侧边栏确认只显示该角色的菜单按 F5 刷新确认当前页面还在而不是跳到 404 或登录页打开 A 角色的页面去访问 B 角色的路由 URL确认被拦在 404最后清掉 cookie 重进登录页确认会话失效后不能直接通过改 URL 进入任何内页。这四条验证完事权限体系的骨架就可以交给业务开发去填内容了。有一个小习惯我一直坚持新接手这套框架的第一周不要往项目里塞任何业务代码只做“登录 → 拉菜单 → 进页面 → 刷新 → 退出”的完整链路演练。等链路里的每个环节都清楚对应代码在哪再开始动工。这个习惯帮我避开了很多后续的返工。做后台管理系统权限链路没理顺之前越急着写页面后期改权限时返工的代价就越大。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站