经过前面 15 篇文章的逐步铺垫,你手里已经集齐了现代 Vue 后台开发的核心装备:HTML/CSS/JS、Vite、Vue 3、Axios、Vue Router、Pinia、Element Plus、权限系统与 TypeScript。
但如果知识只停留在单个 Demo 里,很快就会遗忘。真实职场里,Vue 工程师最常接触的场景之一,就是中后台管理系统。
这类项目并不追求炫酷动画,而更强调:
- 页面骨架是否清晰
- 路由和菜单是否规范
- 登录态是否稳定
- 网络层是否可维护
- 列表、表单、弹窗等业务是否可复用
这篇文章就把前面学过的能力串起来,带你理解一个企业级后台管理系统从 0 到 1 的核心搭建过程。
1. 项目初始化与目录规范
1.1 创建项目
使用 Vite 快速初始化一个 Vue 项目:
npm create vite@latest my-admin-app -- --template vue
cd my-admin-app
npm install
npm install vue-router pinia element-plus axios
如果你准备继续走企业级路线,也建议尽早补上 nprogress:
npm install nprogress
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
router.beforeEach((to, from, next) => {
// 路由切换开始时开启顶部进度条
NProgress.start()
next()
})
router.afterEach(() => {
// 路由切换完成时关闭进度条
NProgress.done()
})
💡 为什么需要 nprogress? 在单页应用(SPA)中,页面跳转是通过前端路由完成的,浏览器不会显示原生的加载圈。如果页面组件较大或需要请求权限,跳转会有延迟。
nprogress是一个轻量级的顶部进度条,配合路由守卫使用,能给用户明确的加载反馈,极大地提升系统的“专业感”。
1.2 为什么中后台项目特别强调目录规范
后台项目通常页面多、模块多、协作人数多。如果目录规划混乱,后续维护成本会急剧上升。
一个比较常见的结构如下:
src/
├── api/ # 接口请求
├── assets/ # 图片、字体、静态资源
├── components/ # 通用组件
├── layout/ # 整体布局
├── router/ # 路由配置与守卫
├── stores/ # Pinia 状态管理
├── styles/ # 全局样式
├── utils/ # 工具函数
├── views/ # 页面级组件
├── App.vue
└── main.ts
1.3 目录设计背后的关键思想
这不是“为了看起来专业”,而是为了让职责更清晰:
api/只负责请求stores/只负责状态router/只负责导航与权限layout/只负责页面骨架views/只负责业务页面
你越早形成这种拆分意识,后面做大型项目越轻松。
2. 核心骨架:Layout 是后台项目的门面
后台系统最典型的结构就是:
- 左侧菜单
- 顶部导航
- 中间内容区
这类结构应该抽成一个统一的 Layout 组件,而不是在每个页面里重复写。
2.1 一个典型的 Layout 结构
<template>
<el-container class="app-wrapper">
<el-aside width="220px" class="sidebar-container">
<div class="logo">My Admin</div>
<SidebarMenu />
</el-aside>
<el-container>
<el-header class="header-container">
<Navbar />
</el-header>
<el-main class="main-container">
<router-view />
</el-main>
</el-container>
</el-container>
</template>
2.2 为什么 Layout 要尽早抽出来
因为在后台项目里,很多页面只是“内容区不同”,外层骨架几乎不变。
提前抽出 Layout 的好处有:
- 菜单、面包屑、顶部用户区都能统一维护
- 页面切换逻辑更清晰
- 后续接入标签页、缓存、权限控制更方便
这属于中后台项目最常见的基础架构知识点。
3. 路由设计:菜单、页面、面包屑都依赖它
很多人第一次做后台时,会把路由只当成“页面跳转工具”。但在真实项目里,路由还承担很多职责:
- 定义页面入口
- 驱动侧边栏菜单
- 生成面包屑
- 标记页面是否需要登录
- 标记页面是否需要缓存
3.1 推荐的路由 Meta 设计
interface RouteMeta {
title?: string
icon?: string
hidden?: boolean
requiresAuth?: boolean
keepAlive?: boolean
}
例如:
import Layout from '@/layout/index.vue'
export const routes = [
{
path: '/login',
component: () => import('@/views/login/index.vue'),
meta: { hidden: true }
},
{
path: '/',
component: Layout,
redirect: '/dashboard',
meta: { requiresAuth: true },
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index.vue'),
meta: {
title: '工作台',
icon: 'Odometer',
keepAlive: true
}
},
{
path: 'users',
name: 'UserList',
component: () => import('@/views/users/index.vue'),
meta: {
title: '用户管理',
icon: 'User'
}
}
]
}
]
3.2 为什么要给路由加 meta
因为中后台不是只有“能跳转”就够了,你还需要:
- 菜单显示标题
- 高亮当前菜单
- 面包屑展示当前路径
- 控制页面访问权限
- 决定哪些页面进入缓存
这就是路由在后台系统里的“工程化价值”。
4. 登录闭环:网络、状态、路由三者协同
登录功能看上去只是一个表单,但它实际上会串起整个后台系统的主干能力。
4.1 登录闭环的完整过程
一个标准流程通常是:
- 用户输入账号密码
- 调用登录接口
- 拿到 Token
- 保存到 Pinia 和本地缓存
- 跳转到首页
- 路由守卫判断已登录,允许访问
- Axios 请求拦截器为后续请求自动挂载 Token
4.2 一个典型的登录页面逻辑
import { reactive } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { login } from '@/api/user'
import { useUserStore } from '@/stores/user'
const router = useRouter()
const userStore = useUserStore()
// 登录表单数据
const form = reactive({
username: '',
password: ''
})
const handleLogin = async () => {
// 1. 发送登录请求
const res = await login(form)
// 2. 保存 token 到全局状态
userStore.setToken(res.token)
ElMessage.success('登录成功')
// 3. 登录成功后进入首页
router.push('/')
}
4.3 登录这件事真正的关键点
最重要的不是“请求能不能发出去”,而是:
- Token 是否能稳定持久化
- 刷新页面后是否还能恢复登录态
- 退出登录时是否能清理干净
这些才是后台项目中最常踩坑的地方。
5. 路由守卫:后台系统的闸门
如果用户没登录,却能直接访问 /users、/dashboard,那这个系统就不完整。
5.1 最基本的权限拦截
router.beforeEach((to, from, next) => {
// 每次路由跳转前都先读取本地 token
const token = localStorage.getItem('token')
if (to.path === '/login') {
next()
return
}
if (!token) {
next('/login')
return
}
next()
})
5.2 企业项目里通常还会加什么
除了登录校验,很多项目还会在守卫中加入:
NProgress顶部加载条- 动态修改页面标题
- 权限路由加载
- 用户信息初始化
例如:
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
router.beforeEach((to, from, next) => {
// 路由切换开始时开启顶部进度条
NProgress.start()
document.title = to.meta.title ? `${to.meta.title} - My Admin` : 'My Admin'
next()
})
router.afterEach(() => {
NProgress.done()
})
这类细节不复杂,却能明显提升项目的“专业感”。
6. 侧边栏菜单:不能写死,必须跟路由联动
后台系统里,左侧菜单绝对不能只靠手写静态数组长期维护。
更推荐的做法是:
让菜单尽量基于路由表生成。
6.1 菜单渲染示例
<template>
<el-menu :default-active="$route.path" router>
<template v-for="route in menuRoutes" :key="route.path">
<el-menu-item
v-if="route.meta?.title && !route.meta?.hidden"
:index="`/${route.path}`"
>
<span>{{ route.meta.title }}</span>
</el-menu-item>
</template>
</el-menu>
</template>
const menuRoutes = computed(() => {
// 从根路由的 children 中提取侧边栏菜单
const rootRoute = router.options.routes.find((item) => item.path === '/')
return rootRoute?.children || []
})
6.2 为什么菜单要跟路由绑定
这样做的好处非常明显:
- 菜单和页面入口来源统一
- 新增页面时不需要额外维护多套配置
- 后期接权限系统时扩展更顺
这也是后面做“动态菜单”和“权限菜单”的基础。
7. 请求层:Axios 封装不是可选项,而是标配
后台项目的接口非常多,如果每个页面都手写完整请求逻辑,很快就会乱。
7.1 为什么要封装请求层
统一封装的价值包括:
- 统一
baseURL - 统一超时设置
- 统一错误处理
- 统一 Token 注入
- 统一响应数据格式
7.2 一个典型的请求封装思路
import axios from 'axios'
// 创建统一请求实例
const service = axios.create({
baseURL: '/api',
timeout: 10000
})
service.interceptors.request.use((config) => {
// 请求发出前统一注入 token
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
service.interceptors.response.use((response) => {
// 只把真正的业务数据返回给调用方
return response.data
})
export default service
7.3 这一步最常见的实际价值
一旦后端接口变多,请求封装会让你:
- 不必每次都重复写请求头
- 不必每次都自己拆
response.data - 错误处理可以集中管理
这就是“Demo”和“项目”的区别。
8. 标准 CRUD 页面:后台项目的核心业务形态
在中后台系统里,最常见的页面就是 CRUD:
- 列表查询
- 条件筛选
- 新增
- 编辑
- 删除
8.1 一页标准的用户管理界面通常有哪些部分
- 查询表单
- 操作按钮区
- 数据表格
- 分页器
- 新增/编辑弹窗
8.2 一个典型的数据加载流程
import { onMounted, reactive, ref } from 'vue'
import { getUserList } from '@/api/user'
const loading = ref(false)
const tableData = ref([])
const total = ref(0)
// 列表查询参数
const queryParams = reactive({
keyword: '',
pageNum: 1,
pageSize: 10
})
const fetchData = async () => {
loading.value = true
// 根据当前筛选条件拉取列表
const res = await getUserList(queryParams)
tableData.value = res.list
total.value = res.total
loading.value = false
}
onMounted(() => {
fetchData()
})
8.3 CRUD 页面里常用但容易忽略的细节
- 搜索后通常要把分页重置回第一页
- 删除成功后要重新加载列表
- 弹窗关闭时要清空表单
- 表格 loading 状态要和请求绑定
- 分页参数要和接口字段保持一致
这些细节看起来小,但往往决定页面体验是否专业。
9. 组件拆分:不要把一个页面写成上千行
后台页面非常容易越写越大,尤其是表格和表单混合页面。
9.1 推荐的拆分思路
例如“用户管理页”可以拆成:
views/users/
├── index.vue # 页面容器
├── components/
│ ├── SearchForm.vue
│ ├── UserTable.vue
│ └── UserDialog.vue
这样拆分的好处:
- 搜索区域、表格区域、弹窗区域职责清晰
- 更便于后续维护和复用
- 页面主文件更容易阅读
9.2 什么逻辑适合继续抽成 composable
如果某些逻辑在多个页面都会复用,可以进一步抽成 useXxx():
- 分页逻辑
- 弹窗开关逻辑
- 表格加载逻辑
- 权限判断逻辑
这是从“会写页面”走向“会组织项目”的关键一步。
10. 状态持久化与页面缓存
后台系统里还有两个很高频的工程点:
- 状态持久化
- 页面缓存
10.1 状态持久化
像用户 Token、用户信息、主题配置这类数据,往往需要在刷新后保留。
最简单的方式是:
- 用 Pinia 管理响应式状态
- 再配合
localStorage做持久化
10.2 页面缓存
对于列表页,如果用户从详情页返回时还想保留筛选条件和滚动位置,可以考虑配合 keep-alive。
<router-view v-slot="{ Component }">
<keep-alive :include="['UserList']">
<component :is="Component" />
</keep-alive>
</router-view>
当然,缓存并不是越多越好。缓存过多会带来状态混乱和内存负担,所以要结合业务场景使用。
11. 常用知识点与关键知识点总结
11.1 常用知识点
这些是你做后台项目几乎一定会遇到的:
- 标准目录结构
- 全局 Layout
- 路由配置与嵌套路由
- 路由守卫
- Pinia 管理用户状态
- Axios 请求封装
- Element Plus 表格、表单、弹窗
- CRUD 页面基本模式
11.2 关键知识点
这些决定你做出来的是不是“企业项目骨架”:
- 路由不只是跳转工具,也是菜单和权限的基础
- 登录功能必须和状态管理、守卫、请求层联动
- 菜单最好跟路由表统一管理
- 页面要及时拆分组件,避免主文件失控
- 列表页、表单页、弹窗页要形成可复用模式
掌握这些,你的后台项目就不再只是一个散装 Demo,而会越来越接近真正的业务系统。
12. 结语
到这里,你已经具备了搭建一个 Vue 3 后台管理系统骨架所需的核心认知。
这篇文章最重要的,不是让你抄一份完整代码,而是让你理解一个真实后台项目背后的组织方式:
- 页面如何布局
- 路由如何设计
- 登录如何闭环
- 请求如何统一
- 页面如何拆分
在真实职场中,后台项目的下一个高频专题通常就是:权限系统、动态路由、按钮级权限控制。
目前我们的菜单是写死的,所有人看到的都一样。在下一篇《企业级后台实战(下):Vue 3 权限系统与动态路由深度解析》中,我们将引入 RBAC 模型,对现在的静态路由进行改造,实现真正的动态菜单和按钮级权限。