基于Vue2与Element UI的后台管理系统模板:从零搭建到功能集成 1. 环境准备与项目初始化在开始构建基于Vue2和Element UI的后台管理系统前我们需要先准备好开发环境。这里我推荐使用Vue CLI作为脚手架工具它能帮我们快速初始化项目结构。首先确保你的系统已安装Node.js建议版本12.x以上然后通过npm全局安装Vue CLInpm install -g vue/cli接下来创建项目时记得选择Vue2版本vue create vue2-admin-template在功能选择界面建议勾选Babel、Router和VuexCSS预处理器推荐选择Sass/SCSS。安装完成后进入项目目录安装Element UIcd vue2-admin-template npm install element-ui2.15.13 -S在main.js中引入Element UI并注册import Vue from vue import ElementUI from element-ui import element-ui/lib/theme-chalk/index.css import App from ./App.vue import router from ./router import store from ./store Vue.use(ElementUI) new Vue({ router, store, render: h h(App) }).$mount(#app)2. 基础布局搭建2.1 整体布局设计后台管理系统通常采用经典的左右布局左侧导航菜单右侧内容区。我们在src/layout目录下创建BasicLayout.vuetemplate el-container classapp-wrapper !-- 左侧导航 -- el-aside :widthisCollapse ? 64px : 200px div classlogo-container img v-if!isCollapse src/assets/logo-text.png img v-else src/assets/logo-mini.png /div el-menu :default-active$route.path :collapseisCollapse background-color#304156 text-color#bfcbd9 active-text-color#409EFF !-- 菜单项 -- /el-menu /el-aside !-- 右侧主区域 -- el-container !-- 顶部导航栏 -- el-header div classheader-left el-button typetext clicktoggleCollapse :iconisCollapse ? el-icon-s-unfold : el-icon-s-fold /el-button el-breadcrumb separator/ !-- 面包屑导航 -- /el-breadcrumb /div div classheader-right !-- 用户信息等 -- /div /el-header !-- 内容区域 -- el-main router-view / /el-main /el-container /el-container /template script export default { data() { return { isCollapse: false } }, methods: { toggleCollapse() { this.isCollapse !this.isCollapse } } } /script style langscss scoped .app-wrapper { height: 100vh; .el-aside { background-color: #304156; transition: width 0.3s; } .el-header { border-bottom: 1px solid #e6e6e6; display: flex; justify-content: space-between; align-items: center; } } /style2.2 动态路由配置在router/index.js中配置动态路由实现权限控制import Vue from vue import Router from vue-router import Layout from /layout/BasicLayout Vue.use(Router) const constantRoutes [ { path: /login, component: () import(/views/login/index), hidden: true }, { path: /, component: Layout, redirect: /dashboard, children: [ { path: dashboard, component: () import(/views/dashboard/index), name: Dashboard, meta: { title: 控制台, icon: dashboard } } ] } ] const asyncRoutes [ { path: /system, component: Layout, redirect: /system/user, meta: { title: 系统管理, icon: setting }, children: [ { path: user, component: () import(/views/system/user), name: User, meta: { title: 用户管理 } }, { path: role, component: () import(/views/system/role), name: Role, meta: { title: 角色管理 } } ] } ] const createRouter () new Router({ scrollBehavior: () ({ y: 0 }), routes: constantRoutes }) const router createRouter() export function resetRouter() { const newRouter createRouter() router.matcher newRouter.matcher } export default router3. 核心功能模块实现3.1 增删改查功能实现以用户管理为例我们实现一个完整的CRUD功能。首先创建src/views/system/user.vuetemplate div classapp-container !-- 搜索区域 -- div classfilter-container el-input v-modellistQuery.username placeholder用户名 stylewidth: 200px classfilter-item / el-button typeprimary classfilter-item clickhandleFilter 搜索 /el-button el-button typeprimary clickhandleCreate 新增用户 /el-button /div !-- 表格区域 -- el-table :datalist border fit highlight-current-row stylewidth: 100% el-table-column propid labelID width80 / el-table-column propusername label用户名 / el-table-column proprole label角色 / el-table-column label操作 width200 template slot-scope{row} el-button sizemini clickhandleUpdate(row)编辑/el-button el-button sizemini typedanger clickhandleDelete(row)删除/el-button /template /el-table-column /el-table !-- 分页 -- pagination v-showtotal0 :totaltotal :page.synclistQuery.page :limit.synclistQuery.limit paginationgetList / !-- 新增/编辑对话框 -- el-dialog :titledialogStatuscreate?新增用户:编辑用户 :visible.syncdialogFormVisible el-form refdataForm :modeltemp label-positionleft label-width100px el-form-item label用户名 propusername el-input v-modeltemp.username / /el-form-item el-form-item label密码 proppassword el-input v-modeltemp.password typepassword / /el-form-item /el-form div slotfooter classdialog-footer el-button clickdialogFormVisible false取消/el-button el-button typeprimary clickdialogStatuscreate?createData():updateData()确认/el-button /div /el-dialog /div /template script import Pagination from /components/Pagination import { fetchList, createUser, updateUser, deleteUser } from /api/user export default { components: { Pagination }, data() { return { list: null, total: 0, listQuery: { page: 1, limit: 20, username: undefined }, temp: { id: undefined, username: , password: }, dialogFormVisible: false, dialogStatus: } }, created() { this.getList() }, methods: { getList() { fetchList(this.listQuery).then(response { this.list response.data.items this.total response.data.total }) }, handleFilter() { this.listQuery.page 1 this.getList() }, resetTemp() { this.temp { id: undefined, username: , password: } }, handleCreate() { this.resetTemp() this.dialogStatus create this.dialogFormVisible true this.$nextTick(() { this.$refs[dataForm].clearValidate() }) }, createData() { this.$refs[dataForm].validate((valid) { if (valid) { createUser(this.temp).then(() { this.dialogFormVisible false this.getList() this.$message.success(创建成功) }) } }) }, handleUpdate(row) { this.temp Object.assign({}, row) this.dialogStatus update this.dialogFormVisible true this.$nextTick(() { this.$refs[dataForm].clearValidate() }) }, updateData() { this.$refs[dataForm].validate((valid) { if (valid) { updateUser(this.temp).then(() { this.dialogFormVisible false this.getList() this.$message.success(更新成功) }) } }) }, handleDelete(row) { this.$confirm(确认删除该用户?, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }).then(() { deleteUser(row.id).then(() { this.getList() this.$message.success(删除成功) }) }) } } } /script3.2 分页组件封装在src/components/Pagination.vue中封装可复用的分页组件template div classpagination-container el-pagination :backgroundbackground :current-page.synccurrentPage :page-size.syncpageSize :layoutlayout :page-sizespageSizes :totaltotal v-bind$attrs size-changehandleSizeChange current-changehandleCurrentChange / /div /template script export default { name: Pagination, props: { total: { required: true, type: Number }, page: { type: Number, default: 1 }, limit: { type: Number, default: 20 }, pageSizes: { type: Array, default() { return [10, 20, 30, 50] } }, layout: { type: String, default: total, sizes, prev, pager, next, jumper }, background: { type: Boolean, default: true } }, computed: { currentPage: { get() { return this.page }, set(val) { this.$emit(update:page, val) } }, pageSize: { get() { return this.limit }, set(val) { this.$emit(update:limit, val) } } }, methods: { handleSizeChange(val) { this.$emit(pagination, { page: this.currentPage, limit: val }) }, handleCurrentChange(val) { this.$emit(pagination, { page: val, limit: this.pageSize }) } } } /script style scoped .pagination-container { padding: 32px 16px; text-align: center; } /style4. 高级功能集成4.1 导入导出功能实现在用户管理页面中添加导入导出功能!-- 在filter-container中添加按钮 -- el-button typesuccess clickhandleExport :loadingdownloadLoading 导出Excel /el-button el-upload action :show-file-listfalse :before-uploadbeforeUpload :http-requesthandleImport styledisplay:inline-block;margin-left:10px el-button typeprimary导入Excel/el-button /el-upload script import { exportExcel } from /utils/export2Excel methods: { handleExport() { this.downloadLoading true const tHeader [ID, 用户名, 角色] const filterVal [id, username, role] const data this.list.map(v filterVal.map(j v[j])) exportExcel({ header: tHeader, data, filename: 用户列表, autoWidth: true, bookType: xlsx }).then(() { this.downloadLoading false }) }, beforeUpload(file) { const isExcel file.type application/vnd.openxmlformats-officedocument.spreadsheetml.sheet if (!isExcel) { this.$message.error(请上传.xlsx格式文件) } return isExcel }, handleImport({ file }) { this.importLoading true import(/utils/import2Excel).then(excel { excel.importExcel(file).then(data { // 处理导入数据 this.importLoading false }) }) } } /script4.2 权限控制实现在src/permission.js中实现路由守卫import router from ./router import store from ./store import { Message } from element-ui import NProgress from nprogress import nprogress/nprogress.css NProgress.configure({ showSpinner: false }) const whiteList [/login] router.beforeEach(async(to, from, next) { NProgress.start() // 确定用户是否已登录 const hasToken store.getters.token if (hasToken) { if (to.path /login) { next({ path: / }) NProgress.done() } else { const hasRoles store.getters.roles store.getters.roles.length 0 if (hasRoles) { next() } else { try { // 获取用户信息 const { roles } await store.dispatch(user/getInfo) // 根据角色生成可访问路由 const accessRoutes await store.dispatch(permission/generateRoutes, roles) // 动态添加路由 router.addRoutes(accessRoutes) // 确保addRoutes已完成 next({ ...to, replace: true }) } catch (error) { // 移除token并跳转登录页 await store.dispatch(user/resetToken) Message.error(error || Has Error) next(/login?redirect${to.path}) NProgress.done() } } } } else { if (whiteList.indexOf(to.path) ! -1) { next() } else { next(/login?redirect${to.path}) NProgress.done() } } }) router.afterEach(() { NProgress.done() })5. 项目优化与部署5.1 性能优化配置在vue.config.js中添加优化配置const path require(path) const CompressionPlugin require(compression-webpack-plugin) module.exports { publicPath: ./, outputDir: dist, assetsDir: static, productionSourceMap: false, configureWebpack: { resolve: { alias: { : path.resolve(__dirname, src) } }, plugins: [ new CompressionPlugin({ test: /\.js$|\.html$|\.css$/, threshold: 10240, deleteOriginalAssets: false }) ] }, chainWebpack(config) { // 移除prefetch插件 config.plugins.delete(prefetch) // 分割代码 config.optimization.splitChunks({ chunks: all, cacheGroups: { libs: { name: chunk-libs, test: /[\\/]node_modules[\\/]/, priority: 10, chunks: initial }, elementUI: { name: chunk-elementUI, priority: 20, test: /[\\/]node_modules[\\/]_?element-ui(.*)/ }, commons: { name: chunk-commons, test: path.resolve(__dirname, src/components), minChunks: 3, priority: 5, reuseExistingChunk: true } } }) // 压缩图片 config.module .rule(images) .use(image-webpack-loader) .loader(image-webpack-loader) .options({ bypassOnDebug: true }) .end() } }5.2 部署配置添加nginx配置示例server { listen 80; server_name yourdomain.com; location / { root /path/to/your/project/dist; index index.html; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://your-backend-server; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 开启gzip gzip on; gzip_min_length 1k; gzip_comp_level 6; gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript; gzip_vary on; gzip_disable MSIE [1-6]\.; }