
简介基于Vue 3、Vite 6、TypeScript与Element Plus构建的后台管理前端模板并配套后端源码适合需要快速搭建中后台系统或希望系统学习前后端分离开发流程的开发者。压缩包共含271个文件其中包含90个Vue组件、88个TypeScript模块、57个SVG图标、6个SCSS样式文件及JSON等工程化配置整体体积约380KB结构清晰易于直接导入项目使用。模板内部已集成路由、状态管理、组件封装与常用工具函数等基础能力可在此基础上直接开展二次开发配套后端源码则呈现了接口联调、权限验证等交互逻辑帮助读者建立完整的前后端协作认知。目前已有115人学习下载适合希望借助真实项目提升工程化水平的前端开发者。 做后台管理系统开发这个方向最难熬的其实不是业务逻辑有多复杂而是每个新项目都要把脚手架重新搭一遍路由配置、菜单权限、请求封装、状态管理、组件引入、环境变量……一套下来大半天就没了。所以我一直有个习惯——沉淀一套属于自己的后台管理模板新项目直接拿来改省下来的时间全花在业务上。这套vue-element-adm就是我最近整理出来的一套前后端配套模板前端基于 Vue3 Vite6 TypeScript Element Plus后端也有对应源码整体打成 zip 包分发。我把自己在实际项目中反复调整过的路由方案、权限控制、请求封装、自动注册机制都沉淀进了这套模板里这篇文章就把它的设计思路和关键实现拆开讲讲给同样在做中后台项目的朋友一个参考。1. 这套模板解决的核心问题1.1 为什么是 Vue3 Vite6 TypeScript Element Plus 的组合先说技术选型。前端框架从 Vue2 到 Vue3 的迁移这都多少年了Composition API 的复用能力、Teleport 传送门、Fragment 多根节点这些特性在后台系统里用起来是真舒服。Vue3 的组合式函数Composables特别适合抽离业务逻辑比如一个useTable就能把列表页的加载态、分页、刷新全管起来不用再像 Vue2 那样写一堆 mixin 然后到处找数据来源。Vite6 是构建工具里体验最好的那一档冷启动秒开HMR 快到几乎无感。做后台系统时后端接口经常在调改了代码浏览器立刻跟着变开发体验比 Webpack 时代舒服了不是一点半点。Vite6 对moduleResolution: bundler的支持也更完善TypeScript 类型解析出错的情况少了很多。TypeScript 在这里不是锦上添花而是刚需。后台系统的状态管理涉及用户信息、权限、路由、缓存十几个模块之间的数据流转没有类型约束很容易改一个字段引发连锁报错。尤其当接口返回的数据结构复杂时TS 的接口定义就是一份活的接口文档后端改了字段前端编译直接报错比联调时对着 Postman 手工核对高效得多。Element Plus 则是自带业务属性的组件库表格、表单、弹窗、树形控件、分页后台系统需要的它基本都覆盖了风格统一文档也全。它不是技术选型里最“潮”的但一定是最稳的。放在这套模板里做基础 UI 层新成员上手成本极低。1.2 模板的功能全景与适合人群这套模板不只是一个最小可运行的前端工程它把后台管理系统里出现频率最高的能力都预置进去了登录与注销流程基于 Token 的鉴权体系刷新后自动恢复登录态动态路由与菜单权限后端返回路由标识前端动态注册页面级权限与按钮级权限指令Axios 请求封装统一错误处理、Token 携带、取消重复请求Pinia 状态管理用户信息、主题、标签页缓存多环境变量配置开发、测试、生产三个环境一键切换自动注册 Element Plus 图标和常用业务组件全套后端源码Java 技术栈提供认证与基础业务接口适合谁用刚入行两三年、想看看成熟后台项目长什么样的初级前端自己接私活、需要快速出后台管理端的外包开发者以及团队里需要一套基础模板来统一项目规范的技术负责人。它不是一个只能跑通流程的 demo而是能直接往里面加业务模块的底座。2. 项目整体架构与目录设计2.1 目录结构设计与分层思路目录结构是一个项目的骨架设计得好不好直接决定后面加业务模块时是痛还是爽。这套模板的目录结构是这样的src/ ├── api/ # 接口定义层 │ ├── auth/ │ ├── system/ │ └── types/ # 接口相关 TS 类型 ├── assets/ # 静态资源 ├── components/ # 通用业务组件 │ ├── Table/ # 封装列表组件 │ ├── Form/ # 封装表单组件 │ └── SvgIcon/ ├── composables/ # 组合式函数 │ ├── usePagination.ts │ └── useTable.ts ├── directives/ # 自定义指令 │ └── permission.ts ├── layout/ # 布局组件 │ ├── components/ │ └── index.vue ├── router/ # 路由配置 │ ├── routes.ts │ └── guard.ts ├── stores/ # Pinia 状态 ├── styles/ # 全局样式 ├── utils/ # 工具函数 └── views/ # 页面组件 ├── dashboard/ ├── system/ └── login/分层的核心思路是“关注点分离”api层只干一件事——定义接口请求函数和对应的 TS 类型。视图层从来不发请求发起请求是调用api层暴露出来的函数。好处是如果后端接口变了你只需要改api里的一个函数所有引用它的页面自动修正不会出现同一个接口在三个页面里各写一遍 axios 的乱象。views层只负责 UI 渲染和交互逻辑不关心数据从哪里来。页面内拿到api层返回的数据通过stores或组件内部状态管理展示。composables层是 Vue3 组合式 API 的最大红利。以前写列表页每写一个页面就要复制粘贴一遍 loading、分页、搜索的逻辑现在把useTable抽出来传入加载函数即可返回数据、loading、分页对象和刷新方法。2.2 关键依赖与版本规划依赖版本不是越新越好稳定配合才是关键。这套模板选型的版本组合如下依赖版本作用Vue3.4.x核心框架Vite6.x构建工具TypeScript5.6.x类型系统Element Plus2.8.xUI 组件库Pinia2.2.x状态管理Vue Router4.4.x路由管理Axios1.7.xHTTP 请求Sass1.79.x样式预处理很多朋友喜欢直接把依赖升级到最新版但在实际项目中最新版往往意味着生态里的其他库还没跟上节奏动不动就碰到兼容性问题。比如 Element Plus 某个大版本升级后样式写法变了表格组件行为有了调整线上系统升完级测出一堆回归 bug难受得很。这套模板锁定的是一套经过实际项目检验的稳定组合尽量避开已知坑点。2.3 工程化规范与基础配置模板在工程化层面做了几件事保证团队协作时不会乱ESLint Prettier 统一代码风格。特别注意vue/multi-word-component-names以外的规则配置以及 TypeScript Vue 的解析器vue-eslint-parser和typescript-eslint/parser的配合写组件名时不会因为单单词文件名报错这算是个小经验点。路径别名。指向src避免../../../../一串地狱。这个几乎是标配了但这里的配置细节值得注意vite.config.ts里要配resolve.alias同时tsconfig.json里要配paths两个地方必须保持一致否则 Vite 能跑起来但 TS 类型检查报错。环境变量。项目里设置了三套.env文件# .env.development VITE_API_BASE_URL/api VITE_USE_MOCKtrue # .env.test VITE_API_BASE_URLhttps://test-api.example.com VITE_USE_MOCKfalse # .env.production VITE_API_BASE_URLhttps://api.example.com VITE_USE_MOCKfalse所有环境相关配置统一走import.meta.env读取杜绝在业务代码里硬编码接口地址。一套团队多人参与的配置规范性的作用不亚于业务代码本身。3. 核心功能模块的落地实现3.1 组件与图标自动注册解放双手后台系统的组件加载频率其实不高Element Plus 全家桶全部全局注册的话首包体积会大不少。这套模板采用按需自动注册的方案。Element Plus 组件通过unplugin-vue-components配合ElementPlusResolver实现自动按需加载// vite.config.ts import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default defineConfig({ plugins: [ Components({ resolvers: [ElementPlusResolver()], }), ], })这样在模板里直接写el-table、el-dialog构建时自动只引入用到的组件和样式不用再手动import { ElButton } from element-plus。图标这块Element Plus 的图标本身是用 SVG 渲染的全部全局注册也不见得有多大负担但按需更优// 在 main.ts 中自动注册所有图标 import * as ElementPlusIconsVue from element-plus/icons-vue const app createApp(App) for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) }这样在模板里可以直接用el-iconSearch //el-icon或者Search /不需要手动 import。注意一个细节图标组件全部注册后Vite 的 dev server 首次启动可能会慢 100ms 左右因为需要扫描图标模块这是正常的不用焦虑。3.2 动态路由与菜单权限设计权限是后台系统绕不开的功能。这套模板的动态路由方案是后端登录成功后会返回当前用户的角色标识和菜单权限列表前端根据权限列表动态生成路由。具体做法不是后端直接返回完整路由 path而是返回路由名称集合// 后端返回的权限数据 { roles: [admin], permissions: [system:user:list, system:role:list], menus: [ { name: Dashboard, path: /dashboard, component: dashboard/index }, { name: System, path: /system, component: layout, children: [ { name: UserManage, path: /system/user, component: system/user/index } ]} ] }前端这边路由表拆分两类constantRoutes是所有人可见的基础路由比如登录页、404 页、首页 DashboardasyncRoutes是需要权限判断的动态路由放在views目录下按模块组织。权限校验的核心逻辑在路由守卫src/router/guard.ts里router.beforeEach((to, from, next) { const userStore useUserStore() if (userStore.token) { if (to.path /login) { next({ path: / }) } else { if (!userStore.roles.length) { // 拉取用户信息并动态添加路由 userStore .getUserInfo() .then(() { const accessRoutes generateRoutes(userStore.menus) accessRoutes.forEach((route) { router.addRoute(route) }) next({ ...to, replace: true }) }) .catch(() { userStore.resetToken() next(/login?redirect${to.path}) }) } else { next() } } } else { if (to.path /login) { next() } else { next(/login?redirect${to.path}) } } })这里有个关键细节动态添加路由之后要立刻执行next({ ...to, replace: true })而不是直接next()。因为addRoute后路由表已经变了但当前导航仍在进行中直接next()会出现页面加载空白的问题。按钮级权限通过自定义指令v-permission实现// directives/permission.ts import type { Directive, DirectiveBinding } from vue const permission: Directive { mounted(el: HTMLElement, binding: DirectiveBinding) { const { value } binding const userStore useUserStore() const permissions userStore.permissions if (value Array.isArray(value)) { const hasPermission value.some((perm: string) permissions.includes(perm)) if (!hasPermission) { el.parentNode?.removeChild(el) } } }, } export default permission模板中这样使用el-button v-permission[system:user:add]新增用户/el-button没有权限时按钮会被直接从 DOM 中移除。顺便说一句纯前端权限控制只能算“体验优化”真正拦截非法请求还要靠后端接口控制前端只是把不需要看到的入口藏起来了这点要心里有数。3.3 接口请求封装与多环境配置Axios 封装是后台项目最重要的基础设施之一。这套模板的封装包含以下能力请求拦截器自动附加 Token不存在 Token 时跳转登录页响应拦截器统一处理 HTTP 状态码和业务状态码401 时清除登录态业务错误统一提示后端返回{ code: 500, message: 操作失败 }时自动弹出 ElMessage重复请求取消同一请求在短时间内重复提交时只保留最后一次请求失败重试临时网络故障时自动重试一次核心封装代码如下简化版// utils/request.ts import axios from axios import { ElMessage } from element-plus import { useUserStore } from /stores/user const service axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000, }) service.interceptors.request.use((config) { const userStore useUserStore() if (userStore.token) { config.headers.Authorization Bearer ${userStore.token} } return config }) service.interceptors.response.use( (response) { const res response.data if (res.code ! 0) { ElMessage.error(res.message || 请求失败) if (res.code 401) { const userStore useUserStore() userStore.resetToken() window.location.href /login } return Promise.reject(new Error(res.message)) } return res.data }, (error) { let message error.message || 网络异常 if (error.response?.status 500) { message 服务器内部错误 } else if (error.response?.status 404) { message 接口不存在 } else if (error.code ECONNABORTED) { message 请求超时 } ElMessage.error(message) return Promise.reject(error) } ) export default service多环境配置这块在 2.4 里已经提过这里补充一个重点环境变量名必须以VITE_开头这是 Vite 的硬性约定否则在代码里读不到。很多刚上手 Vite 的朋友在这个问题上卡半天明明.env文件写了变量import.meta.env里却怎么都读不到多半就是这个原因。4. 配套后端源码与联调协作4.1 后端模块划分与配套能力很多前端模板只管前端接口全靠 Mock等到真正对接后端时发现接口字段对不上自嗨了一整个开发周期。这套模板特意配套了后端源码基于 Spring Boot 实现包含以下模块auth登录认证与 Token 签发system用户管理、角色管理、菜单管理common通用工具类与统一响应体后端启动后提供完整的认证体系和基础 CRUD 接口前端模板直接对接就能跑通登录、获取用户信息、加载菜单权限的完整链路。这个价值在做技术选型时就能明显感受到——你不必再费劲找 Mock 工具直接一个后端服务全搞定。4.2 登录鉴权与数据交互的对接思路模板的登录流程和后端的对接方式设计得很直白前端拿到用户名密码后调api/auth/login接口后端校验通过后返回accessToken和用户基本信息。前端把 Token 存到 Pinia 里并持久化到localStorage之后每次请求在拦截器里带上Authorization头。后端做 Token 鉴权接口返回 401 时前端自动跳回登录页。这里有个值得说的细节模板的登录接口约定返回的 Token 有过期时间前端在响应体里同时拿到expiresIn通过定时器在 Token 即将过期前弹出提示或自动刷新 Token。虽然这套模板默认用的是 accessToken refreshToken 双 Token 方案但我建议实际项目里改成单 Token 刷新接口的模式逻辑更简单安全性也不差。双 Token 的好处是可以降低 Token 被窃取的窗口期但实现复杂度更高对普通后台系统来说性价比一般。5. 常见问题与实战排查5.1 依赖安装与运行报错这套模板虽然把配置都做好了但不同环境下还是会有一些差异问题这里把常见坑整理出来问题原因解决方案npm install后运行报错找不到模块依赖版本不兼容或 Node 版本过低使用 Node 18删除node_modules后重新安装Vite 启动后页面可以打开但 TS 类型检查报错tsconfig.json的paths与vite.config.ts的alias不一致检查两处配置的别名映射确保完全对应Element Plus 组件样式丢失按需加载的样式没被正确引入确认unplugin-vue-components已正确配置构建后检查产物 CSS 是否包含组件样式登录后刷新页面丢失登录态Token 没有持久化或路由守卫逻辑顺序不对检查 Pinia store 里是否把 Token 同步到了localStorage路由渲染逻辑是否在每次刷新后重新拉取用户信息5.2 TypeScript 类型相关的几个高频坑配合组件库使用 TS 时最常见的问题就是给组件绑定事件时类型推不出来。比如给ElTable绑定selection-change事件const handleSelectionChange (rows: UserInfo[]) { selectedRows.value rows }这时如果 IDE 里提示类型不匹配大概率是因为你没有给UserInfo加上interface声明。Element Plus 的表格事件参数类型是any数组所以你需要显式声明一个接口来约束它。这个不算 Bug但确实是刚用 TS Element Plus 时最容易困惑的地方。另一个坑是环境变量的类型问题。import.meta.env.VITE_API_BASE_URL在 TypeScript 下默认是any类型如果开了strict模式IDE 会标红。解决办法是在src/vite-env.d.ts里补充类型声明/// reference typesvite/client / interface ImportMetaEnv { readonly VITE_API_BASE_URL: string readonly VITE_USE_MOCK: boolean } interface ImportMeta { readonly env: ImportMetaEnv }这样写相关环境变量时就有完整的类型提示了拼错变量名也能第一时间发现。5.3 动态路由刷新后 404 的经典坑动态路由方案还有个经典问题页面刷新后直接访问一个动态路由地址比如/system/user路由守卫还没来得及把动态路由挂上去Vue Router 已经对这个路径打上了“未匹配”的标签于是直接跳到了 404 页面。这个坑几乎每个做权限系统的人都会踩一次。解决思路是在路由守卫里捕获这种情况// 在 404 路由添加前判断 if (whiteList.includes(to.path)) { next() } else if (!userStore.roles.length !whiteList.includes(to.path)) { // 刷新时重新拉取用户信息并添加路由 try { await userStore.getUserInfo() const accessRoutes generateRoutes(userStore.menus) accessRoutes.forEach((route) router.addRoute(route)) next({ ...to, replace: true }) } catch (error) { await userStore.resetToken() next(/login?redirect${to.path}) } } else { next() }核心是刷新时先确认用户状态再决定是否放行到目标路由。这套模板里的guard.ts已经处理了这个逻辑拿来即用。6. 模板扩展方向最后聊几句这套模板做出来后我亲身使用中的一些经验。一直以来后台管理系统的技术栈演进其实有一个主线框架从 Vue2 到 Vue3构建工具从 Webpack 到 Vite语言从 JavaScript 到 TypeScriptUI 从自研组件库到成熟组件库的二次封装。这套模板的定位是帮你把这四件事一次性准备好让你把精力集中在业务逻辑本身。实际跑过几个项目之后最强烈的感受是一个工程最大的成本往往不是初始搭建而是后续维护。模板里 TypeScript 类型定义、接口层的统一封装、Composable 逻辑抽离这些设计看起来前期要多花一点时间但到后期加功能、换成员、接手项目时省下来的排查时间才是大头。如果你要把这套模板用在真实项目里我的建议是把api层替换成你自己后端的接口定义把views/system下的用户、角色、菜单管理保留下来作为基础功能再按照你团队的习惯调整一下样式变量。用不了半天一套能跑通前后端联调的基础工程就能支棱起来后续所有新模块都在这个骨架上生长。模板的实际使用扩展还可以做很多比如接入 ESLint 的eslint-plugin-vue推荐规则做更细的代码规范把useTable再拆出搜索表单联动或者在构建配置里加上 CDN 分包策略减少首屏加载时间。这些都是后话了等模板在你的项目里跑起来之后自然会知道下一刀应该切在哪里。本文还有配套的精品资源点击获取