☰
Vue + ElementUI后台管理系统源码设计:从权限到二次开发的完整实践
2026/10/31 11:35:26 网站建设 项目流程

简介:这套基于Vue和ElementUI的后台管理系统源码,适合前端学习者、初中级Vue工程师以及需要快速搭建管理后台的开发者参考,重点展示了中后台常见业务模块的工程化实现方式。压缩包共92个文件,涵盖23个JavaScript脚本、22个Vue组件、20张PNG图片,以及CSS样式、HTML页面、字体文件和源码映射等辅助内容,整体约10.27MB,目录按工程化惯例拆分为api、utils、store、router、views、components等层次,便于对照学习;环境配置、构建脚本与开发变量文件也一并提供,适合从零还原工程。项目已经完成用户管理、菜单管理、角色管理、公司管理、权限管理和支付配置等核心功能,并包含登录、商品管理、交易订单、图表页面等界面素材,涉及axios请求封装、Vuex状态管理、路由配置、组件复用等常见实践。目前已有1280人学习下载,既可作为课程设计或毕业设计的参考源码,也能在此基础上进行二次开发与功能扩展。 后台管理系统大概是前端领域里最不缺需求、也最不缺模板的一类项目。前阵子我整理了一套基于Vue和ElementUI的后台管理系统设计源码,有个朋友问了我一句:市面上现成模板那么多,直接改一改就能用,为什么还要自己从头设计一套?我当时给他说了一句话——你不是在写一个后台,你是在给后面半年的自己减少加班。如果这套源码的目录结构、权限设计、路由管理、组件封装是乱的,那每接一个需求就多踩一次坑。这篇博客我不聊大而全的框架理论,就说说我实际设计这套Vue + ElementUI后台管理系统源码时的思路、落地的核心功能,以及那些不跑一遍源码根本发现不了的问题。适合正在做后台管理项目、打算自己搭一套可复用模板的开发者参考。

1. 为什么后台管理系统选 Vue + ElementUI 而不是其他组合

1.1 这个组合解决的真正问题

很多人选技术栈的时候,习惯性先看热度。Vue在国内开发者里的普及程度确实高,ElementUI又几乎是后台管理系统的“标配”,但这套组合真正解决的问题不是“流行”,而是开发效率与可维护性的平衡。

后台管理系统和C端产品是两种完全不同的东西。C端产品要追求极致的首屏性能、交互创新和动效体验,而后台管理系统90%的页面就是表格、表单、弹窗、树形结构、数据统计卡片。这类页面的特点是重复度高、业务逻辑重、对美观要求不是第一位,但对开发速度要求极高。一个需求下来,界面长得和上个模块差不多,逻辑却完全不一样,这时候组件库的丰富程度直接决定你加班到几点。

ElementUI(以及它在Vue3里的继任者Element Plus)把后台最常用的控件都做好了:表格的排序、筛选、分页,表单的校验、级联、日期选择,布局的栅格系统,反馈的弹窗和消息提示。你不需要从零写样式和交互,只需要把精力花在业务逻辑上。配合Vue的响应式数据绑定和组件化能力,一份源码拆成几十个组件,每个组件管一块业务,维护起来非常清晰。

1.2 Vue2 配 ElementUI 还是 Vue3 配 Element Plus

这是我在设计源码时第一个要做的决定。网上搜“后台管理系统模板”的时候,Vue2历史的存量项目非常多,很多人还在看老项目,而新项目已经全面转向Vue3了。

我的选择建议这样考虑:如果你的需求是维护老系统、公司存量代码全是Vue2,那就老老实实用Vue2 + ElementUI,不要强行升级,稳定压倒一切。如果你是从零开始设计一套自己的源码模板,或者公司新项目启动,直接上Vue3 + Element Plus + Vite,这是目前最顺手的组合。

具体到源码设计里,Vue3和Vue2的差距不仅仅是语法层面。组合式API(Composition API)让同一块业务逻辑可以聚在一起写,不用像选项式API那样散落在data、methods、computed、watch里。路由方面Vue Router 4的createRouter、createWebHistory写起来更现代,状态管理用Pinia,比Vuex省掉了一大笔样板代码。Element Plus在组件API上也做了改良,比如el-dialog的v-model就比Vue2时代的visible更符合直觉。

1.3 它的边界在哪里

任何技术选型都有边界,这套组合也不例外。后台管理系统如果遇到特别复杂的自定义可视化大屏、大量拖拽编排的图形化配置页面,纯靠ElementUI是撑不起来的,得配合ECharts、AntV这类可视化和拖拽库。另外如果你的项目对首屏加载、打包体积极度敏感,后台管理系统这种一个页面塞几十个组件的场景,必须配合路由懒加载、按需引入组件库来优化。

我在源码里默认就开了路由懒加载和ElementUI组件的按需导入,因为后台管理系统随着业务扩展,页面数量增长非常快。你不做懒加载的话,首屏白屏时间会越来越严重——最开始的几百毫秒用户还能忍,到后面页面多了,白屏爬到三秒以上,那就不是一个“能用”的系统了。

所以这套组合的适用边界是:常规业务后台、中后台管理系统、企业内部工具平台。超出这个范围,你就得往工程化、微前端、可视化方向扩展,那已经是另一套体系了。

2. 一套可维护的后台管理系统源码骨架是怎么搭出来的

2.1 目录结构要按“业务”分,不按“文件类型”分

很多新手写源码喜欢这样组织目录:components目录放所有组件,views目录放所有页面,utils目录放所有工具函数。简洁是简洁,但业务一多,找一个按钮的代码可能要翻五个文件夹。

我在设计这套源码时采用的是按业务模块聚类的结构。核心是这个思路:

src/ api/ # 接口请求,按业务模块拆分文件 assets/ # 静态资源 components/ # 公共组件(只放被多个模块复用的) composables/ # 组合式函数,拆业务逻辑用 layout/ # 后台整体布局(侧边栏、顶栏、标签栏) router/ # 路由配置 store/ # 状态管理 styles/ # 全局样式 utils/ # 工具函数(请求封装、鉴权工具等) views/ # 页面,按业务模块建子目录

views目录下不是直接堆页面文件,而是按业务模块分文件夹。比如用户管理模块是一个文件夹,里面的列表页、新增编辑页、角色配置页都在这个文件夹下。api目录也按同样风格拆,用户管理相关的接口请求就放api/user.js里。这样当你维护用户模块时,页面、接口、逻辑都聚集在相邻的目录里,找代码的时间能省一半。

2.2 路由拆分:静态路由和动态路由分开写

后台管理系统的路由是整个源码的骨架,路由写乱了,权限控制、菜单渲染、页面跳转全都会出问题。

我在这套源码里把路由分成两部分。第一部分是静态路由,也就是不需要权限就能访问的东西,比如登录页、404页、无权限提示页。第二部分是动态路由,那些需要登录后并且有对应权限才能访问的页面,比如用户管理、订单管理、报表统计这类。

静态路由和动态路由分开写,核心是一个动态添加路由的机制。前端在用户登录后拿到后端返回的权限码和用户角色,根据权限码过滤出他能访问的路由表,再把这些路由动态注册进去。Vue Router 3用的是addRoutes,Vue Router 4改成了addRoute,一次性传路由数组的方式变成了逐个添加。我写源码的时候封装了一个动态注册路由的工具函数,循环调用addRoute把过滤后的路由加进去。

还有一个容易忽略的点:动态路由注册以后,如果用户退出登录,路由不会自动销毁。我在源码里会记录原本的静态路由表,退出登录时重置路由到初始状态,避免下次登录一个低权限用户钻进别人的菜单里。

2.3 状态管理:不是非用 Vuex 不可

后台管理系统的全局状态,老实说没有C端项目那么多。常见的全局状态无非是用户信息、token、权限码、侧边栏折叠状态、标签页缓存。

如果你用Vue3,我强烈建议直接用Pinia,理由非常现实:Pinia的API更简单,没有mutations的概念,修改状态直接调用action,还天然支持模块化,每个store就是一个独立的文件。Vuex那一套commit、dispatch的逻辑写起来太啰嗦,在我维护过的项目里,大部分状态其实根本不需要时间旅行调试这种高级特性,Pinia足够用。

我在这套源码的store设计里,至少会拆成user(用户信息与鉴权)、permission(权限码与动态路由)、app(侧边栏、标签页这些界面状态)三个独立store。这样模块之间不会互相污染,维护起来也直观。设计原则是:能放在组件内部用ref管理的状态,绝不放全局store。全局状态越少,排查问题越容易。

3. 登录、权限、表格联动与Excel预览:源码里这些硬骨头怎么啃

3.1 登录鉴权与token管理

登录功能是后台管理系统第一个必须面对的硬骨头。很多初始模板只是简单地把token存到localStorage里,登录后跳转首页,看起来一切正常,但项目一旦上线,问题就来了:token过期了该怎么处理?用户刷新页面后用户信息丢了怎么办?接口返回401时要不要主动跳登录页?

我在这套源码里做了一个完整的鉴权闭环。登录请求成功后,把token保存起来的同时,调用一次获取当前用户信息的接口,把用户昵称、头像、权限码一次性拿回来存到Pinia里。路由守卫(beforeEach)里做三重判断:有没有token,没有就跳登录页;有token但没有用户信息,就先去拉用户信息;用户信息拉完后再判断当前路由是否在权限列表里,不在就跳无权限页。

token的存储位置我选择了localStorage。sessionStorage在关闭浏览器后token就没了,实际上很多后台用户刷新页面都不希望被强制重新登录;cookie更适合服务端渲染场景,纯前端spa项目用localStorage最直接。注意别把所有用户信息都塞到localStorage里,占空间倒是其次,主要是任何脚本通过XSS拿到localStorage的内容,等于把用户画像全泄露了,所以localStorage里只放token,用户信息放内存。

3.2 动态权限:按钮级和路由级分开控制

权限设计是后台管理系统源码里最容易写乱的部分。路由级权限控制的是这个用户能不能访问某个页面,按钮级权限控制的是即使进了这个页面,能不能看到或点击“新增”“删除”之类的按钮。

我在源码里把这两种权限拆成两个独立的机制。路由级权限走的是动态路由注册,用户登录后遍历菜单树,能访问的路由才addRoute进去。按钮级权限挂了一个自定义指令,模板里写v-permission="'user:add'",如果当前用户权限码里没有user:add这个值,就直接把元素从DOM里移除。

这个设计最直观的好处是:判断逻辑从业务组件里彻底抽离出来。不在每个页面的created钩子里写一大段权限判断代码,而是在模板声明层面就完成控制。同样一套页面组件,不同权限的人打开,看到的是不同密度的操作按钮。

3.3 el-select全选联动:一个容易翻车的交互

热搜里有个词条是“vue2和elementui中el-select全部选择后其他也选上”,这正好是后台管理系统里很典型的一个交互坑。

场景是这样的:用一个el-select做多选筛选,下拉数据项里第一项是“全部”,后面跟着“部门A”“部门B”“部门C”。用户点“全部”时,预期是所有部门都被选中,但el-select默认的逻辑不是这样,你选中“全部”后,其他部门可能也莫名其妙被带上了,或者只选中了“全部”这一项,部门实际没选上。

问题本质在于:手动控制勾选值和el-select内部维护的选中值之间发生了不一致。ElementUI的el-select接收的是绑定value,它自己内部的时候,其实有两种处理逻辑:一种是当前选中项本身就是“全部”,那直接替换成全部选项的value数组;另一种是用户手动取消了某个部门选项,那“全部”这个虚拟选项就要自动被剔除。

我在这套源码里遇到这个场景时,采用了一个更稳的方案:把“全部”设计为value等于'all'的特殊选项,不在选项列表里混在一起,单独用一行或者分组显示。在change事件回调里判断:

const handleSelectChange = (val) => { // 选了“全部”,直接把所有选项的value填进去 if (val.includes('all')) { form.value.departments = departmentList.value.map(item => item.id) return } // 手动取消到选中数少于总数时,自动把“全部”从选中项里剔除 //(el-select的选中值是纯数组,不会自动处理all,需要手动维护) }

具体实现上,更推荐的做法是用el-select的collapse-tags配合手动维护的selectedAll标记,而不是真的把“全部”作为value塞进数组里。原因很简单:后端接口不认“全部”这个值,提交的部门id列表里掺了一个all,后端就会报参数错误。所以前端的做法应该是:界面显示上把“全部”做得像一个可勾选项,但实际提交时永远提交真实的部门id数组。

3.4 树形表格多勾选:父子关联没你想的那么简单

另一个高频坑是“elementui树形表格多勾选”。el-table有树形数据的懒加载和展开功能,结合type="selection"多选列时,默认的父子节点勾选是联动的:勾选父节点,子节点全部选中;取消父节点,子节点全部取消。这个默认行为在小权限树、组织架构树里挺好用,但业务稍微复杂一点就会出现两个绕不开的问题。

第一个问题:父节点勾选时,父节点的id会不会跟着被提交?默认情况会。但在组织架构这种场景里,用户想选的是“这个部门下的所有人”,如果提交请求时把父部门id也带上,后端就要额外判断,是把这个父节点当组织处理还是把人当成员处理。我在源码里会在提交前做一个过滤,把父节点id从勾选集合里剔除,只保留叶子节点。

第二个问题:部分勾选时怎么处理?如果你希望实现“只勾选某些子节点,父节点严格不联动但是有半选中状态”,默认行为是做不到的。这时候要设置el-table的check-strictly属性。注意,传true表示父子节点不联动,父节点的选中状态完全独立,但又不显示半选状态了,这就跟需求冲突了。

我的折中方案是:check-strictly保持默认false(父子联动),但在变更事件里根据勾选状态动态调整提交数据。后端需要什么就提交什么。而不是跟前端表格的展示状态纠缠。因为el-table的树形多选,本质上是“选择组的展示”,提交的数据结构才决定业务逻辑是否正确。

3.5 在线预览Excel:后台管理系统里被低估的功能

很多后台管理系统的业务场景里,用户会要求“能直接在浏览器里预览Excel表格”,而不是下载下来再用本地软件打开。这个需求听着小,真做起来比想象中麻烦。

网上能搜到“后台管理系统在线预览excel表格”这类词,说明大家普遍在找现成方案。我在这套源码里用了xlsx库解析Excel数据,再配合HTML表格渲染的方案。核心思路是:解析.xlsx文件,把每个sheet里的单元格数据读成二维数组,然后把第一行当表头生成HTML表格样式。这样做的优点是不依赖任何在线服务,本地就能跑,也适合企业内网离线环境。

要注意的是,这种解析方案难免有样式丢失的问题。Excel里合并单元格、背景色、边框、公式,xlsx库能识别一部分,但还原度有限。如果业务对样式还原要求很高,只能上服务端转换成HTML或者PDF再返回给前端。我通常的做法是给预览功能加一个“excel解析中”的加载状态提示,同时把文件名和sheet切换功能一起做进去,让用户至少能看数据,样式要求高的场景另说。很多后台项目在这块只是“能用”和“较好用”的差距,花半天做一下,比被业务方吐槽强很多。

4. 源码拿到手之后:环境配置、调试与二次开发的落地姿势

4.1 安装依赖阶段就翻车?先看版本

很多开发者在跑别人源码的时候,最容易倒在第一关:npm安装依赖。尤其是老项目的源码,node-sass和node版本不匹配是最大的坑。node-sass是在安装时编译本地二进制文件的,Node.js版本一升级它就可能编译失败,报错信息一长串,新手看一眼就懵。

我整理这套源码时把样式方案换成了官方推荐的dart-sass(sass),彻底避开node-sass的版本地狱。如果你是从旧源码二次开发,遇到node-sass报错,最快的解决办法是把package.json里的node-sass替换成sass或者sass-embedded,然后再npm安装。语法上大部分scss代码都是兼容的,唯一要注意的是旧版@import规则可能需要改成新版推荐的@use/@forward。

还有一个常见的“白屏坑”:装完依赖、npm run dev跑起来,页面一片空白,控制台报了一个错误。这个问题往往不是源码问题,而是你的Node.js版本太新,或者Vite版本和Node版本不匹配。Vite 2要求Node 12以上,Vite 3要求Node 14.18以上,Vite 4要求Node 14.18+或者16+。如果版本跨度太大,建议直接把lock文件删了重装,让依赖版本按package.json里面允许的语义化版本范围重新解析一遍。

4.2 Vue Devtools的正确用法

拿到一套后台管理系统源码,第一步肯定不是上来就看代码,而是把项目跑起来,点一点界面,看数据流。

我强烈建议调试环境装上Vue Devtools浏览器插件。Vue3项目要装对应版本的插件,Vue2项目要装Vue.js devtools的相应版本,装错版本插件根本不会生效。Devtools最大的价值是看组件状态,你点开某一个表格页面,可以看到当前组件实例的data、props、computed、pinia状态,一目了然。

实际排查问题时,我常用的几个标签页分工是这样:组件树看组件嵌套关系,找“这个弹窗为什么显示不出来”之类跟渲染层级相关的问题;Pinia/Vuex标签页看全局状态,排查“侧边栏折叠状态刷新后怎么丢了”这类问题;Router标签页可以看当前路由记录、导航守卫跳转是否正常。还有一个非常实用的场景是,排查el-select全选联动这种问题的时候,直接在Devtools里改选中数组,立刻就能验证是不是渲染层的问题还是事件回调逻辑的问题,省去一遍遍重新操作界面。

4.3 二次开发时别在源码里硬改

这是我最想强调的一点。很多人拿到源码,第一反应是直接改node_modules里的依赖,或者把公共组件内部逻辑改得面目全非,结果项目一升级依赖,所有改动灰飞烟灭。

我的原则是:能不动公共源码就不动,实在要动,通过扩展、二次封装实现。比如ElementUI的table表格满足不了当前业务的列拖拽需求,不要直接改node_modules里element-ui的源码,而是封装一个业务表格组件,在内部基于el-table做二次包装,上层页面使用这个业务组件。这样升级依赖时,你只改一个封装文件就行,不影响几十个页面。

同理,接口请求封装、权限指令、动态路由工具函数这些基础设施,在源码里尽量做成可配置的,参数从配置文件或接口返回值里读,不要写死。我在这套源码里做了一个约定:凡是标注了FIXME或者CUSTOM的代码区域,就是二次开发时需要重点查看的扩展点,其他区域可以放心升级依赖。

5. 写这套源码之后,我的一些个人体会

5.1 选项式还是组合式?不要盲目跟风

热搜里有“vue选项式和组合式区别”,说明这是很多人面试和实际开发都绕不开的话题。我的观点是:同一个项目里最好统一风格,但不必强行全用组合式API。选项式的data/methods/computed在页面逻辑简单的场景下非常直观,组合式API在复杂业务逻辑复用的场景下优势明显。如果团队水平参差不齐,我经历过的一个比较稳的方案是:页面组件用选项式保持直观,公共逻辑用组合式函数(composables)来抽取复用,两种模式各司其职。

5.2 设计源码不是写给自己看的,是写给维护者看的

写这套源码的过程中,我最大的体会是:好的源码,最重要的评价标准不是技术多炫,而是你两个月后再打开这个项目,还能不能快速定位到一个功能的入口。所以我在源码里加了大量的中文注释,不是那种解释代码语法的废话注释,而是说明业务意图的注释,比如“这里的权限码来自后端的menu表,不能随意改动”“此处修改会影响角色配置页的数据回显”。

5.3 如果是从零开始学,一条更省力的路径

最后分享一个学习路径上的小建议。如果你没有公司项目练手,想靠后台管理系统源码提升水平,不要一上来就研究动态路由和权限设计。先照着官方文档把ElementPlus的表格、表单、弹窗这几个核心组件玩熟,然后用Vite手动搭一个只有登录页和首页的极简后台,跑通了之后再逐步加权限、加路由懒加载、加组件封装。一到两周时间,你会发现市面上大部分后台管理系统模板的源码,你都能看懂了。这就是这篇文章想传达的最终价值——源码不是一个结果,而是一套思考过程,你把它拆开看懂了,下一次遇到类似需求,就能自己设计了。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询