拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
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 18+,Element Plus 2.x在较新的Node下体验会更好。如果你还在用Node 14,大概率会碰到各种兼容性报错,比如ERR_OSSL_EVP_UNSUPPORTED这种经典问题。我的建议是直接用nvm切换到一个长期支持版本,比如Node 18.20或Node 20.x,能省掉很多不必要的麻烦。

确认Node没问题后,执行下面的命令创建一个Vue3项目:

npm create vite@latest 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-router@4 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 router

Pinia的安装和使用也很简单,在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 class="app-wrapper"> <el-aside :width="sidebarWidth" class="sidebar-container"> <Sidebar /> </el-aside> <el-container class="main-container"> <el-header class="navbar-container" height="56px"> <Navbar /> </el-header> <el-main class="app-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-if="item.children && item.children.length" :index="item.path"> <template #title> <el-icon v-if="item.meta.icon"><component :is="item.meta.icon" /></el-icon> <span>{{ item.meta.title }}</span> </template> <SidebarItem v-for="child in item.children" :key="child.path" :item="child" :base-path="item.path" /> </el-sub-menu> <el-menu-item v-else :index="resolvePath(item.path)"> <el-icon v-if="item.meta.icon"><component :is="item.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了。我一般用默认的teleported(true),并给弹层加一个自定义类名来控制样式。

当前菜单高亮绑定的是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 :is="item.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 trigger="click" @command="handleCommand"> <div class="avatar-wrapper"> <el-avatar :size="28" src="https://..." /> <span class="user-name">{{ userStore.name }}</span> </div> <template #dropdown> <el-dropdown-menu> <el-dropdown-item command="profile">个人中心</el-dropdown-item> <el-dropdown-item command="logout" divided>退出登录</el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown>

这里有两个细节值得提一下。其一,trigger="click"比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标签上添加class="dark",同时引入对应样式:

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来控制列宽。我常用的分配方式是:

  • 统计卡片:四个卡片,每个span="6",正好一行四个;
  • 中部内容:左侧占span="16",右侧占span="8",形成2:1的视觉宽度。

关键在于响应式适配。Element Plus的栅格支持xs、sm、md、lg、xl几个断点值,可以给不同屏幕宽度设置不同的span。比如统计卡片在1920px屏幕上四个并列很舒服,但在1366px的笔记本上就会显得拥挤。我用了这样一组配置:

<el-row :gutter="16"> <el-col :xs="12" :sm="12" :md="6" v-for="item in stats" :key="item.label"> <el-card class="stat-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做类似的后台项目,我建议从这套布局入手,先跑通主干,再根据业务需求慢慢加细节。

返回列表