ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Vue3 + Element Plus 后台管理系统首页布局实战

Vue3 + Element Plus 后台管理系统首页布局实战 1. 从P9说起后台首页到底要承载什么后台管理系统的首页说难不难说简单也真不简单。P9这个项目最初给我的需求就是一句做一个后台首页布局但真正动起手来你会发现这里面的决策点非常密集菜单怎么组织、头部放哪些操作、内容区如何拆分布局、组件怎么选型、如何保证后续扩展性。这些看似都是布局问题实际上一旦定错后面改起来全是泪。我个人的经验是先把首页拆成几个功能模块来看。首先是导航层包括侧边菜单和顶部栏这决定了用户在整个后台里怎么走其次是内容层也就是首页默认展示的Dashboard通常包含统计卡片、图表、快捷入口、最近动态等最后是状态层比如用户登录信息、系统通知、全屏开关、主题切换等。这三个层次就是Vue3 Element Plus实现后台首页布局的核心骨架。在技术选型上P9采用了Vue3 Vite Element Plus的组合。这个组合在目前的Vue生态里属于最主流的路线之一。Vite带来的开发启动速度、Element Plus组件库的丰富程度、组合式API的灵活性让整个首页布局实现起来比Vue2时代要顺手很多。如果你正在学习Vue3或者接手一个需要从零搭建后台的项目这篇内容应该能给你一个完整的参考。2. 初始化一个能跑的Vue3 Element Plus工程2.1 Vite创建项目避免踩node版本坑创建项目的第一件事不是直接npm create vite而是先检查Node版本。Vite 5以上版本要求Node 18Element Plus 2.x在较新的Node下体验会更好。如果你还在用Node 14大概率会碰到各种兼容性报错比如ERR_OSSL_EVP_UNSUPPORTED这种经典问题。我的建议是直接用nvm切换到一个长期支持版本比如Node 18.20或Node 20.x能省掉很多不必要的麻烦。确认Node没问题后执行下面的命令创建一个Vue3项目npm create vitelatest p9-admin -- --template vue cd p9-admin npm install这里选择vue模板生成的是Vue3 JavaScript版本的初始工程。如果你更习惯TypeScript也可以把--template vue-ts加上。P9这个项目考虑到团队里有人对TS不够熟悉最终选了JS版本但这个不影响布局的整体逻辑TS版本只是在后续定义接口类型时会更严格。2.2 引入Element Plus的两种方式Element Plus的接入方式有两种全量引入和按需自动导入。对于后台管理系统这种场景我建议直接使用官方推荐的自动按需导入方案配合unplugin-vue-components和unplugin-auto-import这两个插件。原因是Element Plus的组件非常多全量引入会让首屏包体多出几百KB而按需导入在开发和编译时都能保持较小的体积。先在命令行安装依赖npm install element-plus npm install -D unplugin-vue-components unplugin-auto-import然后在vite.config.js里配置import { defineConfig } from vite import vue from vitejs/plugin-vue import AutoImport from unplugin-auto-import/vite import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default defineConfig({ plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()] }), Components({ resolvers: [ElementPlusResolver()] }) ] })配置完成后在组件里直接使用el-button这种标签就行插件会自动把对应的样式和JS引入。需要注意的是如果你用了ElMessage、ElNotification这类函数式组件仅仅靠组件的自动导入还不够还需要在用到的地方手动引入样式import { ElMessage } from element-plus import element-plus/es/components/message/style/css ElMessage.success(操作成功)如果你不想折腾只想先把项目跑起来也可以在全量引入的main.js里写import { createApp } from vue import ElementPlus from element-plus import element-plus/dist/index.css import App from ./App.vue const app createApp(App) app.use(ElementPlus) app.mount(#app)全量引入的好处是方便缺点是首屏体积大。P9项目最终选择了按需自动导入并且把vite.config.js里的配置稳定下来后团队开发时几乎感知不到Element Plus的安装过程组件直接写就行。2.3 路由与状态管理的迷你安装首页布局还需要router和pinia这是后台管理系统的基础设施。P9的路由结构不复杂直接安装npm install vue-router4 pinia在src/router/index.js里配置一个基础路由把登录页和布局页分开。这里先给布局页路由加一个空的children后面首页和各种业务页面都挂在它下面。import { createRouter, createWebHistory } from vue-router const routes [ { path: /login, name: Login, component: () import(/views/login/index.vue) }, { path: /, component: () import(/layout/index.vue), redirect: /dashboard, children: [ { path: dashboard, name: Dashboard, component: () import(/views/dashboard/index.vue) } ] } ] const router createRouter({ history: createWebHistory(), routes }) export default routerPinia的安装和使用也很简单在main.js里挂载import { createPinia } from pinia const pinia createPinia() app.use(pinia)为什么首页布局需要Pinia核心用途有两个一是保存侧边栏的折叠状态用户在刷新页面后还能记住二是保存用户信息头部用户下拉菜单和权限控制都会用到。像这种全局状态用Pinia比用localStorage直接操作要优雅得多。到这里一个能跑的前端工程就已经准备好了。接下来进入正题开始实现首页布局。3. 首页布局第一步Container容器的分区与栅格3.1 用el-container搭出整体骨架Element Plus提供了一套现成的布局容器组件el-container、el-aside、el-header、el-main。这套组件的语义非常直观我习惯把它们理解成房子的各个房间el-container是整栋楼el-aside是靠墙的储物间el-header是玄关el-main是客厅。P9项目的布局文件在src/layout/index.vue整体结构是这样的template el-container classapp-wrapper el-aside :widthsidebarWidth classsidebar-container Sidebar / /el-aside el-container classmain-container el-header classnavbar-container height56px Navbar / /el-header el-main classapp-main router-view / /el-main /el-container /el-container /template这里设计成左侧固定侧边栏、右侧是顶部栏加内容区的经典结构。为什么不用上下布局因为对于后台管理系统来说导航的层级一般都有两级甚至三级侧边栏能够承载更长的菜单列表也更符合桌面端用户的操作习惯。需要注意el-aside的宽度是动态的通过sidebarWidth计算属性来控制。折叠时是64px展开时是210px。这个宽度需要和侧边栏内部菜单的宽度一致否则会出现菜单内容被截断的问题。3.2 为什么用el-container而不是纯CSS Grid有的人可能会问既然CSS Grid也能做出同样的布局为什么还要用Element Plus的容器组件我的答案是为了语义统一和后续维护成本低。el-container本身其实就是flex布局的封装但它把头部侧边主体这些概念用组件标签的形式固化下来团队里的人一看就知道哪个区域对应哪个标签不用去数样式类名。而且el-container还能自动处理嵌套布局的高度问题。比如外层el-container默认会占满整个视口你不需要额外写height: 100vh内层的el-main也会自动撑开并具备滚动条。这个特性在首页内容超出屏幕高度时非常有用内容区的滚动条只出现在el-main内部侧边栏的菜单不会跟着上下滚动。P9项目在实现时还做了一个小优化给el-aside内加了一个overflow-x: hidden给el-main加了background: #f0f2f5。后台系统内容的底色用浅灰视觉上更清爽也能把白色卡片区域衬托出来。4. 侧边栏菜单用el-menu把导航做扎实4.1 菜单数据结构与递归渲染侧边栏是后台首页布局里最核心的部分之一。P9项目的菜单不是写死的而是根据路由配置自动生成的。这样做的好处是新增一个页面时只需要在路由表里加一个对象侧边栏会自动多出一个菜单项不需要手动去维护两套数据。路由表里的meta字段用来存放菜单相关信息比如title菜单名、icon图标、hidden是否在菜单中隐藏。举个例子{ path: dashboard, name: Dashboard, component: () import(/views/dashboard/index.vue), meta: { title: 首页, icon: Odometer } }侧边栏组件需要支持多级嵌套所以不能直接在模板里写死两层循环而是定义了一个递归组件SidebarItem。如果你不熟悉递归组件的写法可以把它理解成自己调用自己的组件当菜单项有children时继续渲染下一层没有children时渲染一个可直接点击的el-menu-item。下面是一个简化的递归组件逻辑template el-sub-menu v-ifitem.children item.children.length :indexitem.path template #title el-icon v-ifitem.meta.iconcomponent :isitem.meta.icon //el-icon span{{ item.meta.title }}/span /template SidebarItem v-forchild in item.children :keychild.path :itemchild :base-pathitem.path / /el-sub-menu el-menu-item v-else :indexresolvePath(item.path) el-icon v-ifitem.meta.iconcomponent :isitem.meta.icon //el-icon template #title{{ item.meta.title }}/template /el-menu-item /template这里有个细节子菜单的index不能简单使用相对路径要基于父级路径拼接。我在resolvePath方法里用path.posix.join处理import path from path function resolvePath(routePath) { return path.posix.join(basePath, routePath) }4.2 侧边栏折叠与当前激活项折叠功能是后台布局的标配。P9项目在Pinia里存了一个sidebarCollapsed状态折叠时el-aside的宽度变成64px同时向el-menu组件传入collapse属性。这里有个Element Plus的坑要注意折叠状态下el-sub-menu的悬浮弹层需要设置popper-class否则样式会怪怪的。如果你遇到折叠后子菜单弹出位置偏移或样式丢失去排查一下teleported属性是否设置成false了。我一般用默认的teleportedtrue并给弹层加一个自定义类名来控制样式。当前菜单高亮绑定的是el-menu的default-active属性值应该是当前路由的path。我在侧边栏组件里用watch监听route.path变化并同步更新const activeMenu computed(() route.path)这里有一个容易被忽略的细节如果你的路由路径包含动态参数比如/user/1而菜单项的index是/user那么高亮会失效。解决办法是让菜单项的index指向路由的name或者使用路由的matched数组取最后一级的path。P9项目因为首页和导航菜单都相对规整直接用path就够了但如果你做的是复杂业务系统建议在早期就把菜单和路由的绑定关系想清楚。4.3 菜单图标el-icon的两种用法Element Plus的图标库和组件库是分开的需要单独安装npm install element-plus/icons-vue图标的使用有两种方式。第一种是全局注册所有图标在main.js里import * as ElementPlusIconsVue from element-plus/icons-vue for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) }这种方式简单但会把几百个图标全部打包进项目。对于后台管理系统来说我更推荐按需引入在递归组件里动态渲染import { Odometer, Setting, User } from element-plus/icons-vue然后在模板里用component :isitem.meta.icon动态绑定。前提是菜单数据meta.icon里存的变量名要和导入的图标组件名一致。P9项目由于菜单数量不多最终使用了按需引入然后通过一个映射对象把字符串转成组件import * as Icons from element-plus/icons-vue const iconComponent computed(() Icons[item.meta.icon] || null)这样做既保留了字符串配置的灵活性又不会全量打包。5. 顶栏组合面包屑、折叠按钮、用户下拉的联动5.1 顶栏左侧的菜单折叠与面包屑顶栏区域通常分三块左侧是功能操作区中间可以放面包屑右侧放用户信息和系统操作。P9的顶栏左侧不光是折叠按钮还加了一个面包屑导航。折叠按钮用el-icon里的Fold和Expand两个图标来切换点击时调用Pinia里的方法翻转状态const sidebarCollapsed storeToRefs(useAppStore()) function toggleSidebar() { appStore.toggleSidebar() }面包屑导航用Element Plus的el-breadcrumb组件实现。面包屑的数据来源是当前路由的matched数组。在Vue Router 4中route.matched会包含从顶级路由到当前匹配路由的所有记录。我使用了一个计算属性const breadcrumbs computed(() { return route.matched.filter(item item.meta item.meta.title) })注意要把没有meta.title的记录过滤掉否则布局路由本身也会出现在面包屑里显得多余。另外面包屑的最后一栏应该显示当前页面的标题且不可点击前面的栏目标题可以点击跳转。如果当前页面没有配置标题那么面包屑会自动隐藏避免出现空内容。5.2 右侧用户下拉头像、退出登录与个人页顶栏右侧我习惯放一个用户头像加下拉菜单下拉菜单里包括个人中心项目文档退出登录这三类操作。P9项目用el-dropdown来实现el-avatar作为触发元素el-dropdown triggerclick commandhandleCommand div classavatar-wrapper el-avatar :size28 srchttps://... / span classuser-name{{ userStore.name }}/span /div template #dropdown el-dropdown-menu el-dropdown-item commandprofile个人中心/el-dropdown-item el-dropdown-item commandlogout divided退出登录/el-dropdown-item /el-dropdown-menu /template /el-dropdown这里有两个细节值得提一下。其一triggerclick比hover更安全鼠标悬停容易误触尤其在用户从顶栏移到其他区域时下拉菜单突然弹出的体验很差。其二退出登录不仅仅是跳转路由还要清掉存储在Pinia和localStorage里的token、用户信息甚至要调用一次后端的登出接口避免token在服务端残留。P9项目在handleCommand里按命令分发处理而不是每个操作单独绑定事件代码更整洁。5.3 全局全屏与暗黑模式切换顶栏右侧还有一个常驻的功能全屏切换。这个我推荐直接用浏览器原生API不需要引入额外依赖function toggleFullscreen() { if (document.fullscreenElement) { document.exitFullscreen() } else { document.documentElement.requestFullscreen() } }暗黑模式则要依赖Element Plus的dark模式工具。Element Plus 2.2.0之后支持了暗黑模式做法是在html标签上添加classdark同时引入对应样式import element-plus/theme-chalk/dark/css-vars.css然后在切换时document.documentElement.classList.toggle(dark)这个方案会把所有Element Plus组件的颜色变量切换到暗黑系。但要注意你自己写的业务样式里如果有硬编码的颜色值暗黑模式下可能不协调。P9项目在开发规范里明确规定颜色一律使用Element Plus暴露的CSS变量如var(--el-bg-color)禁止写死#fff或#000。这样暗黑模式切换后整体视觉才是一致的。6. 内容区首页卡片、统计数字、表格与图表的摆放思路6.1 用el-row和el-col搭建Dashboard网格首页内容区Dashboard是布局的重头戏。P9的Dashboard第一版参考了市面上大多数后台管理系统的做法顶部一行统计卡片下面左边放一个表格右边放一个图表。这里用el-row和el-col的栅格系统来划分。栅格系统把一行分成24份通过span来控制列宽。我常用的分配方式是统计卡片四个卡片每个span6正好一行四个中部内容左侧占span16右侧占span8形成2:1的视觉宽度。关键在于响应式适配。Element Plus的栅格支持xs、sm、md、lg、xl几个断点值可以给不同屏幕宽度设置不同的span。比如统计卡片在1920px屏幕上四个并列很舒服但在1366px的笔记本上就会显得拥挤。我用了这样一组配置el-row :gutter16 el-col :xs12 :sm12 :md6 v-foritem in stats :keyitem.label el-card classstat-card.../el-card /el-col /el-row:gutter用来设置列与列之间的间距它会给每个el-col添加左右padding。这个间距值需要和卡片内部间距协调P9统一用了16px视觉上刚刚好。6.2 统计卡片避免为了炫技而过度复杂统计卡片的实现看起来简单但里面的信息层级很值得斟酌。一个合格的统计卡片至少要包含标题、数值、变化趋势图标、辅助说明。我用el-card加slot来自定义内容。卡片里的数字用大号字体和普通文本形成对比。变化趋势用el-tag来标红或标绿。这里有一个可访问性上的小细节表示上升下降的箭头加上文字说明不要只给一个颜色。P9项目里遇到过用户抱怨分不清绿色到底是涨了还是跌了因为不同行业对颜色的直觉不一样。后来我们统一在数字旁边加上较昨日这样的文案再配合箭头语义就清楚了。6.3 表格区和图表区用卡片隔离内容Dashboard中部的表格和图表不要直接暴露在页面背景上最好各自放进el-card里。卡片自带的阴影和圆角在后台系统里几乎是标配它能清晰地划分区域边界。P9项目在表格区放了一个最近订单列表只展示最重要的几列点击行可以跳转到详情。这里的表格不用把el-table的全部能力都用上只保留数据渲染和简单的格式化即可。图表我推荐使用ECharts配合vue-echarts或直接封装一个ECharts的Vue组件。ECharts的体积较大建议按需引入要用到的图表类型而不是import * as echarts这样全量引入。比如只用到折线图和柱状图就单独引入import { LineChart, BarChart } from echarts/charts import { GridComponent, TooltipComponent, LegendComponent } from echarts/components import { use } from echarts/core首页放图的目的是让用户一眼看到关键趋势所以图例、坐标轴标签这些辅助元素不要太干扰数据本身。我的经验是图表组件外层包一层自适应容器监听window.resize事件调用chart.resize()否则浏览器窗口改变后图表会变形。7. 收尾响应式加持与几个容易翻车的细节7.1 侧边栏在窄屏下的处理策略后台管理系统虽然主要面向PC但也不能完全放弃窄屏适配。P9项目的方案是当窗口宽度小于768px时侧边栏默认折叠并且给整个布局加了一层遮罩。用户点击折叠按钮展开菜单时遮罩出现点击遮罩或菜单后自动收起。这个效果类似移动端的抽屉导航。实现方式不复杂用一个监听窗口宽度的onMounted和onUnmounted组合const handleResize () { const width window.innerWidth if (width 768 !sidebarCollapsed.value) { appStore.toggleSidebar() } }这部分逻辑放在layout/index.vue里只处理窄屏的自动折叠。遮罩层用v-show绑定一个状态点击时调用相同的方法收起菜单。7.2 Element Plus版本差异阴影与日期组件近期有同事反馈过一个诡异问题Element Plus 2.11.4版本下表格偶尔会出现莫名其妙的阴影。这个问题我在热搜词里也看到了说明不少人遇到。我排查后发现这多半不是组件本身渲染错误而是因为el-table的滚动条和box-shadow组合时引发的视觉残留。常见触发条件有两个一是表格外层容器设置了overflow: auto二是页面里有全局的自定义box-shadow样式影响了表格的sticky列。解决办法是给表格的外层加一个overflow: hidden或者对.el-table设置border-radius和明确的background-color。如果你一定要给表格容器加滚动那么建议用el-table自带的height属性来控制滚动区域而不是在外部套一层可滚动的div。另外el-date-picker在判断结束时间大于起始时间时比较常见的做法是在disabled-date回调里限制可选日期const disabledEndDate (date) { if (!form.startTime) return false return date.getTime() form.startTime.getTime() }热搜里提到的el-date-picker判断结束时间大约起始时间其实是个笔误大概率是大于。这个问题在首页布局里不一定会遇到但如果是做后台系统的表单页面基本绕不开提前了解一下没坏处。7.3 404页面和路由容错布局的最后一个模块我还加了404页面的兜底。在路由表的最后捕获所有未匹配路径{ path: /:pathMatch(.*)*, name: NotFound, component: () import(/views/error/404.vue) }很多后台项目的首页布局只关注了正常路径忽略了用户手输错误URL的情况。如果没有兜底路由页面会白屏。404页面不需要复杂一个大图标加一句提示文案加一个返回首页的按钮就够用了。7.4 最后分享一个节省时间的小技巧在P9这个项目里我踩过一个不算深但挺烦的坑el-main默认的padding是20px但如果你在el-main里又嵌套了el-container那么内外两层间距叠加导致内容区左边和右边的空白不对称。后来我们的统一约定是el-main只负责滚动区域里面的页面内容全部用el-card的body-style来控制间距不在el-main上重复设置padding。这样布局的间距逻辑就很清晰了。另外开发阶段打开Vue的DevTools把Pinia的状态面板固定在一个显眼位置调试侧边栏折叠和用户登录状态的时候会非常方便。很多时候菜单不展开、高亮不对不是代码逻辑问题而是Pinia里的状态已经被之前的操作污染了看起来像灵异事件实际是状态残留。P9的首页布局做到这里已经具备了完整的骨架、导航、内容展示和响应式能力。后续要扩展业务模块只需要在路由表里追加配置菜单会自动生成。这套模式在中小规模的后台系统里非常实用既不会过度设计又能保证后续迭代的顺畅。如果你正在用Vue3和Element Plus做类似的后台项目我建议从这套布局入手先跑通主干再根据业务需求慢慢加细节。
返回列表