
简介这是一套基于 Vue 2.0 Vuex Element-UI 构建的完整后台管理系统源码面向前端初学者及中级开发者旨在帮助其掌握企业级管理系统的工程化开发流程与核心架构设计。资源包含121个文件主体为50个Vue组件文件实现路由视图与业务模块、33个JS脚本含Vuex store、API封装与工具函数、16个PNG/JPG/SVG图片资源用于图标、头像与背景以及less样式、JSON配置、环境变量与HTML入口等配套文件整体压缩包仅1.5MB轻量易读。已有386人下载学习适合快速上手实践。读者可直接运行调试获得标准目录结构含router、store、views、components分层、完整的权限路由控制逻辑、基于Element-UI的表单/表格/弹窗等交互实现以及Vuex模块化状态管理范例是理解Vue 2生态协同开发的典型参考项目。1. 这不是一套“拿来即用”的模板而是一套 Vue 2 生态下状态驱动型后台系统的典型骨架当你解压基于vue2.0vuexelement-ui后台管理系统.rar看到src/store/下整齐的index.js、modules/和mutations.js看到src/router/index.js里按角色划分的路由守卫再点开src/views/dashboard/里那个带v-loading和el-table的首页——你拿到的其实是一份被反复验证过的「Vue 2 后台系统最小可行架构」。它不解决具体业务逻辑比如订单审核流或库存预警算法但精准覆盖了权限隔离、表单联动、列表分页、全局加载态、错误拦截、多 tab 导航等 90% 的中后台共性需求。适合刚从 Vue CLI 脚手架起步、需要快速交付内部管理工具的前端工程师也适合正在维护存量 Vue 2 项目的团队做架构对齐。注意它和 Vue 3 Pinia 的新项目不是替代关系而是同一类问题在不同技术代际下的标准解法——理解这套结构才能判断何时该升级、何时该复用、何时该重构。2. 搭建可运行的 Vue 2 Vuex Element UI 后台骨架从初始化到路由守卫落地2.1 初始化项目并锁定核心依赖版本Vue 2 后台系统对依赖版本极其敏感。Element UI 官方明确要求 Vue 2.5.2而 Vuex 3.x 是 Vue 2 的最终稳定版。使用vue-cli2.9.6非 Vue CLI 3初始化最稳妥# 全局安装旧版 vue-cli避免与新版冲突 npm install -g vue-cli2.9.6 # 创建基于 webpack 模板的项目 vue init webpack vue2-admin-system # 进入目录并安装核心依赖注意版本号 cd vue2-admin-system npm install vue2.6.14 vuex3.6.2 element-ui2.15.14 axios0.21.4 --save npm install babel-plugin-transform-runtime6.23.0 --save-dev提示vue2.6.14是 Vue 2 最后一个长期支持版修复了 IE11 下v-model在自定义组件中的兼容性问题element-ui2.15.14是其最后一个功能完整版后续仅修安全漏洞vuex3.6.2支持命名空间模块的完整 API且与 Vue Devtools 5.x 兼容。这三个版本组合是当前线上 Vue 2 后台系统的事实标准。2.2 配置 Element UI 全局引入与主题定制Element UI 不建议按需引入会破坏el-container等布局组件的样式继承链需全局注册。在main.js中import Vue from vue import App from ./App.vue import router from ./router import store from ./store // 引入 Element UI 样式与组件 import ElementUI from element-ui import element-ui/lib/theme-chalk/index.css // 默认主题 // 可选替换为自定义主题需提前编译好 custom-theme.css // import ./assets/custom-theme.css // 注册全局组件与指令 Vue.use(ElementUI, { size: medium, // 统一设置组件默认尺寸 zIndex: 2000 // 全局弹层 z-index 基准值 }) new Vue({ el: #app, router, store, render: h h(App) })参数说明size参数统一控制el-button、el-input等组件的尺寸避免页面中混用mini/small/medium导致视觉割裂zIndex设为2000是为后续可能集成的第三方弹窗如富文本编辑器浮层预留空间低于3000常见 Modal 层级可避免遮挡。2.3 构建分层式 Vuex Store 结构模块化状态管理落地Vuex 3 的模块化能力是管理复杂后台状态的核心。src/store/目录结构应严格遵循以下约定store/ ├── index.js # 根 store 实例注册 modules ├── modules/ │ ├── user.js # 用户登录态、权限码、个人信息 │ ├── menu.js # 动态菜单数据、侧边栏折叠状态 │ └── app.js # 全局加载态、错误消息、多 tab 页签 └── mutations-types.js # 常量定义避免字符串硬编码以modules/user.js为例实现登录态持久化与权限校验// store/modules/user.js import { login, logout, getUserInfo } from /api/user // 接口封装见 3.1 节 import { setToken, removeToken, getToken } from /utils/auth const state { token: getToken(), // 从 localStorage 读取 name: , avatar: , roles: [], // 权限角色数组如 [admin, editor] permissions: [] // 权限码数组如 [user:list, order:edit] } const mutations { SET_TOKEN: (state, token) { state.token token setToken(token) // 同时写入 localStorage }, SET_USER_INFO: (state, info) { state.name info.name state.avatar info.avatar state.roles info.roles state.permissions info.permissions }, RESET_STATE: (state) { state.token state.name state.avatar state.roles [] state.permissions [] removeToken() // 清除本地 token } } const actions { // 登录 action调用接口 → 存 token → 获取用户信息 → 提交 mutation login({ commit }, userInfo) { const { username, password } userInfo return new Promise((resolve, reject) { login({ username, password }).then(response { const { data } response commit(SET_TOKEN, data.token) resolve() }).catch(error { reject(error) }) }) }, // 获取用户信息携带 token 请求 → 解析权限 → 提交 mutation getInfo({ commit, state }) { return new Promise((resolve, reject) { getUserInfo(state.token).then(response { const { data } response if (!data) { reject(Verification failed, please Login again.) } commit(SET_USER_INFO, data) resolve(data) }).catch(error { reject(error) }) }) } } export default { namespaced: true, // 必须开启命名空间避免模块间 mutation 冲突 state, mutations, actions }关键设计点namespaced: true是模块化前提否则user/SET_TOKEN会与app/SET_LOADING冲突getToken()和setToken()封装了 localStorage 操作避免直接调用localStorage.setItemloginaction 返回 Promise便于在组件中.then()处理登录成功后的跳转逻辑。2.4 实现动态路由与权限守卫让菜单和页面按角色自动呈现后台系统的核心是「权限即路由」。Element UI 的el-menu需要渲染服务端返回的菜单树而 Vue Router 需根据权限码过滤路由。流程如下登录后请求菜单数据getUserMenu()返回类似[ { path: /dashboard, name: Dashboard, component: dashboard/index, meta: { title: 首页, icon: el-icon-s-home } }, { path: /user, name: User, component: user/list, meta: { title: 用户管理, icon: el-icon-user, roles: [admin] } } ]动态添加路由在router/index.js中定义asyncRouterMap登录成功后调用router.addRoutes()// router/index.js import Vue from vue import Router from vue-router import Layout from /views/layout/Layout // 通用布局组件 Vue.use(Router) // 基础路由无需权限如登录页、404 export const constantRouterMap [ { path: /login, component: () import(/views/login/index), hidden: true }, { path: /404, component: () import(/views/404), hidden: true }, { path: *, redirect: /404, hidden: true } ] // 异步路由需权限控制 export const asyncRouterMap [ { path: /, component: Layout, redirect: /dashboard, children: [ { path: dashboard, component: () import(/views/dashboard/index), name: Dashboard, meta: { title: 首页, icon: el-icon-s-home } } ] } ] export default new Router({ mode: history, scrollBehavior: () ({ y: 0 }), routes: constantRouterMap })路由守卫中生成权限路由在src/permission.js中import router from ./router import store from ./store import { Message } from element-ui import NProgress from nprogress // 进度条 import nprogress/nprogress.css router.beforeEach(async(to, from, next) { NProgress.start() const token store.getters.token if (token) { if (to.path /login) { next({ path: / }) NProgress.done() } else { // 检查 store 中是否已有用户信息 if (store.getters.roles.length 0) { try { await store.dispatch(user/getInfo) // 触发 getInfo action const accessedRouters await store.dispatch(permission/generateRoutes, store.getters.roles) router.addRoutes(accessedRouters) // 动态添加路由 next({ ...to, replace: true }) // hack 方案确保 addRoutes 完成后再跳转 } catch (error) { await store.dispatch(user/resetToken) Message.error(error || Has Error) next(/login?redirect${to.path}) NProgress.done() } } else { next() } } } else { if (whiteList.indexOf(to.path) ! -1) { next() } else { next(/login?redirect${to.path}) NProgress.done() } } })注意router.addRoutes()在 Vue Router 3.5 中已废弃但 Vue 2 生态仍广泛使用实际项目中需配合store.dispatch(permission/generateRoutes)将菜单数据转换为RouteConfig[]并过滤掉当前角色无权访问的路由通过meta.roles字段比对。3. 实战构建一个带搜索、分页、批量操作的用户管理列表页3.1 封装标准化 API 请求与响应拦截所有后台接口应统一处理 token 注入、错误码解析、loading 状态。在src/api/user.js中import request from /utils/request // 封装的 axios 实例 // 用户列表接口GET /api/users?page1limit10usernameadmin export function userList(params) { return request({ url: /api/users, method: get, params // 自动序列化为 query string }) } // 删除用户DELETE /api/users/123 export function deleteUser(id) { return request({ url: /api/users/${id}, method: delete }) } // 批量删除POST /api/users/batch-delete export function batchDeleteUser(ids) { return request({ url: /api/users/batch-delete, method: post, data: { ids } // 注意批量操作用 POST body }) }src/utils/request.js封装核心逻辑import axios from axios import { Message } from element-ui import store from /store import { getToken } from /utils/auth // 创建 axios 实例 const service axios.create({ baseURL: process.env.BASE_API, // 从 .env.development 读取 timeout: 5000 }) // 请求拦截器注入 token service.interceptors.request.use( config { if (store.getters.token) { config.headers[X-Token] getToken() // 后端约定的 token header } return config }, error { console.log(error) return Promise.reject(error) } ) // 响应拦截器统一错误处理 service.interceptors.response.use( response { const res response.data if (res.code ! 200) { // 后端约定的成功 code Message({ message: res.message || Error, type: error, duration: 5 * 1000 }) return Promise.reject(new Error(res.message || Error)) } else { return res } }, error { console.log(err error) Message({ message: error.response?.data?.message || Request Error, type: error, duration: 5 * 1000 }) return Promise.reject(error) } ) export default service参数说明baseURL从环境变量读取便于开发/测试/生产环境切换X-Token是常见后端鉴权 Header 名若后端用Authorization: Bearer xxx则改为config.headers.Authorization Bearer getToken()res.code ! 200是典型后端响应结构需根据实际 API 文档调整判断逻辑。3.2 用户列表组件Element UI 表格与 Vuex 状态联动src/views/user/list.vue实现搜索、分页、选择、操作template div classapp-container !-- 搜索区域 -- div classfilter-container el-input v-modellistQuery.username placeholder用户名 stylewidth: 200px; classfilter-item / el-button classfilter-item typeprimary clickhandleFilter搜索/el-button el-button classfilter-item typeprimary clickhandleCreate新增用户/el-button /div !-- 表格区域 -- el-table :keytableKey v-loadinglistLoading :datalist border fit highlight-current-row stylewidth: 100%; selection-changehandleSelectionChange el-table-column typeselection width55 aligncenter / el-table-column labelID propid aligncenter width80 / el-table-column label用户名 propusername aligncenter / el-table-column label昵称 propnickname aligncenter / el-table-column label邮箱 propemail aligncenter / el-table-column label状态 propstatus aligncenter template slot-scope{row} el-tag :typerow.status 1 ? success : info {{ row.status 1 ? 启用 : 禁用 }} /el-tag /template /el-table-column el-table-column label操作 aligncenter class-namesmall-padding fixed-width template slot-scope{row} el-button typeprimary sizemini clickhandleUpdate(row)编辑/el-button el-button sizemini typedanger clickhandleDelete(row)删除/el-button /template /el-table-column /el-table !-- 分页区域 -- div classpagination-container el-pagination background size-changehandleSizeChange current-changehandleCurrentChange :current-pagelistQuery.page :page-sizes[10,20,30,50] :page-sizelistQuery.limit layouttotal, sizes, prev, pager, next, jumper :totaltotal / /div /div /template script import { userList, deleteUser, batchDeleteUser } from /api/user import { mapGetters, mapActions } from vuex export default { name: UserList, data() { return { tableKey: 0, list: [], total: 0, listLoading: true, listQuery: { page: 1, limit: 10, username: undefined }, multipleSelection: [] } }, computed: { ...mapGetters([permissions]) }, created() { this.getList() }, methods: { ...mapActions([setLoading]), // 映射 store/app 模块的 setLoading action getList() { this.listLoading true userList(this.listQuery).then(response { this.list response.data.items this.total response.data.total this.listLoading false }) }, handleFilter() { this.listQuery.page 1 this.getList() }, handleSizeChange(val) { this.listQuery.limit val this.listQuery.page 1 this.getList() }, handleCurrentChange(val) { this.listQuery.page val this.getList() }, handleSelectionChange(val) { this.multipleSelection val }, handleDelete(row) { this.$confirm(此操作将永久删除该用户, 是否继续?, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }).then(() { deleteUser(row.id).then(() { this.$message({ type: success, message: 删除成功! }) this.getList() }) }) }, handleBatchDelete() { if (this.multipleSelection.length 0) { this.$message.warning(请至少选择一项) return } const ids this.multipleSelection.map(item item.id) batchDeleteUser(ids).then(() { this.$message.success(批量删除成功) this.getList() this.multipleSelection [] }) } } } /script关键交互逻辑handleSelectionChange将选中行存入multipleSelection供handleBatchDelete使用handleFilter重置页码为 1避免搜索后显示空页v-loadinglistLoading绑定表格加载态由getList()控制开关mapActions([setLoading])可同步更新全局 loading 状态如顶部进度条。3.3 权限指令 v-permission细粒度控制按钮显隐Element UI 的v-if无法复用权限判断逻辑需封装自定义指令。在src/directives/permission.js中import store from /store export default { inserted(el, binding) { const { value } binding const permissions store.getters store.getters.permissions if (value value instanceof Array value.length 0) { const permissionRoles value const hasPermission permissions.some(role permissionRoles.includes(role)) if (!hasPermission) { el.parentNode el.parentNode.removeChild(el) // 移除 DOM 节点 } } else { throw new Error(need roles! Like v-permission[admin,editor]) } } }在main.js中全局注册import permission from ./directive/permission Vue.use(permission)在用户列表页中使用!-- 只有 admin 或 editor 角色可见 -- el-button v-permission[admin, editor] typeprimary clickhandleCreate新增用户/el-button !-- 只有 admin 角色可见 -- el-button v-permission[admin] typedanger clickhandleBatchDelete批量删除/el-button优势相比v-ifpermissions.includes(admin)指令将权限逻辑抽离组件内只关注业务el.parentNode.removeChild(el)比v-if更彻底地移除节点避免 SSR 渲染残留。4. 进阶技巧Vuex 模块热重载与 Element UI 表单验证优化4.1 开发阶段 Vuex 模块热重载避免每次修改都要刷新页面Vue 2 的 Vuex 3 支持模块热重载HMR但需手动配置。在src/store/index.js中import Vue from vue import Vuex from vuex import createPersistedState from vuex-persistedstate // 持久化插件 Vue.use(Vuex) // 动态导入 modules const requireModule require.context(./modules, false, /\.js$/) const modules {} requireModule.keys().forEach(fileName { if (fileName ./index.js) return const moduleName fileName.replace(/(\.\/|\.js)/g, ) modules[moduleName] requireModule(fileName).default }) const store new Vuex.Store({ modules, plugins: [createPersistedState()] // 保持用户登录态 }) // HMR 热重载支持 if (module.hot) { module.hot.accept([./modules], () { const requireModule require.context(./modules, false, /\.js$/) const newModules {} requireModule.keys().forEach(fileName { if (fileName ./index.js) return const moduleName fileName.replace(/(\.\/|\.js)/g, ) newModules[moduleName] requireModule(fileName).default }) store.hotUpdate({ modules: newModules }) }) } export default store效果修改store/modules/user.js中的mutations保存后浏览器不会刷新Vuex Devtools 中的状态会实时更新commit新增的 mutation 也能立即生效。这是提升 Vue 2 后台开发效率的关键技巧。4.2 Element UI 表单验证规则复用避免重复书写正则与提示语后台表单验证规则高度相似手机号、邮箱、密码强度。在src/utils/validate.js中集中管理// 验证手机号 export function validatePhone(rule, value, callback) { const phoneReg /^1[3-9]\d{9}$/ if (!value) { callback(new Error(请输入手机号)) } else if (!phoneReg.test(value)) { callback(new Error(请输入正确的手机号)) } else { callback() } } // 验证邮箱 export function validateEmail(rule, value, callback) { const emailReg /^([a-zA-Z0-9][-_\.]?)*[a-zA-Z0-9]([a-zA-Z0-9][-_\.]?)*[a-zA-Z0-9]\.[a-zA-Z0-9]{2,3}$/ if (!value) { callback(new Error(请输入邮箱)) } else if (!emailReg.test(value)) { callback(new Error(请输入正确的邮箱)) } else { callback() } } // 密码强度至少8位含大小写字母和数字 export function validatePassword(rule, value, callback) { const pwdReg /^(?.*[a-z])(?.*[A-Z])(?.*\d)[a-zA-Z\d]{8,}$/ if (!value) { callback(new Error(请输入密码)) } else if (!pwdReg.test(value)) { callback(new Error(密码需包含大小写字母和数字长度至少8位)) } else { callback() } }在用户编辑表单中复用template el-form :modeltemp :rulesrules refdataForm el-form-item label手机号 propphone el-input v-modeltemp.phone / /el-form-item el-form-item label邮箱 propemail el-input v-modeltemp.email / /el-form-item /el-form /template script import { validatePhone, validateEmail } from /utils/validate export default { data() { return { temp: { phone: , email: }, rules: { phone: [{ validator: validatePhone, trigger: blur }], email: [{ validator: validateEmail, trigger: blur }] } } } } /script优势规则集中管理一处修改全局生效trigger: blur指定失焦时验证避免输入过程频繁报错validator函数接收rule、value、callback三个参数符合 Element UI 表单验证规范。4.3 生产环境性能优化Element UI 组件按需加载与 CSS 抽离虽然全局引入更简单但生产包体积过大。可通过babel-plugin-component实现按需加载npm install babel-plugin-component -D修改.babelrc{ presets: [ [env, { modules: false, targets: { browsers: [ 1%, last 2 versions, not ie 8] } }], stage-2 ], plugins: [ transform-vue-jsx, transform-runtime, [component, { libraryName: element-ui, styleLibraryName: theme-chalk }] ] }然后在main.js中// 替换原来的 Vue.use(ElementUI) import { Button, Select, Table, TableColumn, Pagination, MessageBox } from element-ui Vue.component(Button.name, Button) Vue.component(Select.name, Select) Vue.component(Table.name, Table) Vue.component(TableColumn.name, TableColumn) Vue.component(Pagination.name, Pagination) // MessageBox 需单独挂载 Vue.prototype.$msgbox MessageBox Vue.prototype.$alert MessageBox.alert Vue.prototype.$confirm MessageBox.confirm效果对比全局引入 Element UI 约增加 300KB gzip 后体积按需加载后仅引入Button、Table等常用组件体积可降至 80KB 以内。这对后台系统首屏加载速度提升显著。最后检查webpack.base.conf.js中new HtmlWebpackPlugin的minify配置确保 CSS 被正确抽离new HtmlWebpackPlugin({ filename: index.html, template: index.html, inject: true, minify: { removeComments: true, collapseWhitespace: true, removeAttributeQuotes: true } }), // 确保 extract-text-webpack-plugin 或 mini-css-extract-plugin 已启用本文还有配套的精品资源点击获取