【Vue Router】+ 中后台前端实战:从path/name/meta配置到动态嵌套路由,掌握统一可落地的路由规范,避开权限与刷新丢失高频坑!

📑 文章目录
- 一、开篇
- [二、path、name、meta 该咋用?](#二、path、name、meta 该咋用?)
- [2.1 三者分别是什么](#2.1 三者分别是什么)
- [2.2 path:URL 的门面](#2.2 path:URL 的门面)
- [2.3 name:编程式跳转的「身份证」](#2.3 name:编程式跳转的「身份证」)
- [2.4 meta:业务信息的「行李舱」](#2.4 meta:业务信息的「行李舱」)
- 三、动态路由:权限与按需加载
- [3.1 什么是动态路由](#3.1 什么是动态路由)
- [3.2 实现思路](#3.2 实现思路)
- [3.3 登录后加载动态路由](#3.3 登录后加载动态路由)
- [3.4 动态路由常见坑](#3.4 动态路由常见坑)
- 四、嵌套路由:多级页面结构
- [4.1 什么是嵌套路由](#4.1 什么是嵌套路由)
- [4.2 配置示例](#4.2 配置示例)
- [4.3 父组件里放 router-view](#4.3 父组件里放 router-view)
- [4.4 redirect 的常见用法](#4.4 redirect 的常见用法)
- 五、统一规范总结
- [5.1 路由配置规范](#5.1 路由配置规范)
- [5.2 完整示例:一个规范的路由文件](#5.2 完整示例:一个规范的路由文件)
- [5.3 和状态管理的关系](#5.3 和状态管理的关系)
- 六、结语
- [🔍 系列模块导航](#🔍 系列模块导航)
同学们好,我是 Eugene(尤金),一名多年中后台前端开发工程师。
(Eugene 发音 /juːˈdʒiːn/,大家怎么顺口怎么叫就好)
很多前端开发者都会遇到一个瓶颈:
代码能跑,但不够规范;功能能实现,但维护起来特别痛苦;一个人写没问题,一到团队协作就各种混乱、踩坑、返工。
想写出干净、优雅、可维护 的专业代码,靠的不是天赋,而是体系化的规范 + 真实实战经验。
这一系列《前端规范实战》,我会用大白话 + 真实业务场景,不讲玄学、不堆理论,只分享能直接落地的规范、标准与避坑指南。
帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。
一、开篇
Vue Router 是每个 Vue 项目都会用到的路由方案,很多人虽然能写,但经常出现:
path和name混用,跳转方式不统一meta只随手加字段,没有约定- 动态路由和权限混在一起,结构乱
- 嵌套路由
children和redirect写不明白
本文从「日常怎么选、为什么这么选、容易踩的坑」出发,给出一套可落地的 Vue Router 使用规范,适合:
- 会写 JS,但对路由概念不够清晰的同学
- 刚开始接触 Vue 的初学者
- 有经验、想巩固基础、统一团队习惯的前端工程师
不追求底层原理,目标是:看完就能照着用,减少踩坑。
[⬆ 返回目录](#⬆ 返回目录)
二、path、name、meta 该咋用?
2.1 三者分别是什么
| 属性 | 作用 | 是否必填 |
|---|---|---|
| path | URL 路径,浏览器地址栏显示的 | 必填(根路由可为 /) |
| name | 路由名称,用于编程式跳转和组件复用 | 建议必填 |
| meta | 自定义元信息,存面包屑、权限、标题等 | 按需使用 |
简单理解:
- path :给人看的 URL,
/user/profile - name :给代码用的标识,
'UserProfile' - meta :给业务用的配置,如
{ title: '个人中心', requireAuth: true }
[⬆ 返回目录](#⬆ 返回目录)
2.2 path:URL 的门面
规范建议:
- 用 kebab-case(短横线):
/user-order-list,不要/userOrderList - 层级清晰:
/user/profile、/user/settings - 动态参数用冒号:
/user/:id
示例:
js
// router/index.js
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue'),
},
{
path: '/user/:id',
name: 'UserDetail',
component: () => import('@/views/UserDetail.vue'),
},
];
访问 /user/123 时,this.$route.params.id 为 '123'。
[⬆ 返回目录](#⬆ 返回目录)
2.3 name:编程式跳转的「身份证」
为什么推荐每个路由都加 name?
- 用
name跳转,不依赖 URL 变更,后期改 path 不影响跳转 - 可配合
<keep-alive>做缓存 - 便于在守卫、面包屑里做逻辑判断
规范:用 PascalCase,语义清晰。
js
// 推荐:语义清晰
{ name: 'UserProfile', path: '/user/profile', ... }
{ name: 'OrderDetail', path: '/order/:id', ... }
// 不推荐:过于简单
{ name: 'user', ... }
{ name: 'detail', ... }
跳转对比:
js
// 用 path 跳转(path 改了就失效)
this.$router.push('/user/profile');
// 用 name 跳转(推荐)
this.$router.push({ name: 'UserProfile' });
// 带参数
this.$router.push({
name: 'OrderDetail',
params: { id: '123' }, // 对应 path 里的 :id
query: { tab: 'shipping' }, // ?tab=shipping
});
踩坑提醒:params 和 query 的区别
- params :对应 path 中的
:id,不会出现在 URL 的 query 里 - query :会变成
?key=value
js
// 跳转
this.$router.push({
name: 'UserDetail',
params: { id: '123' },
query: { from: 'home' },
});
// 实际 URL:/user/123?from=home
// this.$route.params.id === '123'
// this.$route.query.from === 'home'
[⬆ 返回目录](#⬆ 返回目录)
2.4 meta:业务信息的「行李舱」
meta 用来放与路由相关的业务配置,常见用途:
| 用途 | 示例 |
|---|---|
| 页面标题 | meta: { title: '个人中心' } |
| 是否需要登录 | meta: { requireAuth: true } |
| 面包屑 | meta: { breadcrumb: ['首页', '用户'] } |
| 缓存 | meta: { keepAlive: true } |
| 权限码 | meta: { permission: 'user:edit' } |
规范建议:字段命名统一,团队共用一套约定。
js
const routes = [
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: {
title: '控制台',
requireAuth: true,
keepAlive: true,
},
},
{
path: '/user/edit/:id',
name: 'UserEdit',
component: () => import('@/views/UserEdit.vue'),
meta: {
title: '编辑用户',
requireAuth: true,
permission: 'user:edit',
},
},
];
在路由守卫里使用:
js
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth && !store.state.user.token) {
next({ name: 'Login' });
} else {
next();
}
});
在布局里设置标题:
js
// 在 layout 或 App.vue 的 watch 里
watch(
() => route.meta.title,
(title) => {
document.title = title || '默认标题';
},
{ immediate: true }
);
[⬆ 返回目录](#⬆ 返回目录)
三、动态路由:权限与按需加载
3.1 什么是动态路由
动态路由 = 路由在运行时通过 router.addRoute() 动态添加,而不是一开始全部写死在配置里。
常见场景:根据用户角色/权限加载不同菜单和页面。
[⬆ 返回目录](#⬆ 返回目录)
3.2 实现思路
- 写一套「基础路由」:登录、404 等,不依赖权限
- 用户登录后,拿菜单/权限数据
- 用
addRoute把对应路由逐个加入 - 再跳转到目标页
js
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Layout from '@/layouts/MainLayout.vue';
// 1. 基础路由(所有人都能访问)
const constantRoutes = [
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
meta: { title: '登录', noAuth: true },
},
{
path: '/404',
name: 'NotFound',
component: () => import('@/views/404.vue'),
meta: { title: '页面不存在', noAuth: true },
},
];
// 2. 动态路由配置(根据权限筛选后 addRoute)
const asyncRouteMap = [
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { title: '控制台', icon: 'dashboard' },
},
{
path: '/user',
name: 'User',
component: Layout,
meta: { title: '用户管理', icon: 'user' },
children: [
{
path: 'list',
name: 'UserList',
component: () => import('@/views/user/List.vue'),
meta: { title: '用户列表' },
},
],
},
];
const router = createRouter({
history: createWebHistory(),
routes: constantRoutes,
});
export { constantRoutes, asyncRouteMap };
export default router;
[⬆ 返回目录](#⬆ 返回目录)
3.3 登录后加载动态路由
js
// store/modules/user.js 或 登录逻辑处
import router, { asyncRouteMap } from '@/router';
// 模拟:根据权限过滤
function filterAsyncRoutes(routes, permissionList) {
const res = [];
routes.forEach((route) => {
const tmp = { ...route };
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, permissionList);
}
// 这里简化:实际可按 permission、role 等过滤
res.push(tmp);
});
return res;
}
export async function login(account, password) {
const res = await api.login(account, password);
const { token, permissions } = res.data;
// 存 token
localStorage.setItem('token', token);
// 按权限过滤路由
const accessRoutes = filterAsyncRoutes(asyncRouteMap, permissions);
// 动态添加
accessRoutes.forEach((route) => {
router.addRoute(route);
});
// 404 放最后
router.addRoute({
path: '/:pathMatch(.*)*',
redirect: '/404',
});
}
[⬆ 返回目录](#⬆ 返回目录)
3.4 动态路由常见坑
坑 1:刚 addRoute 就 push,可能匹配不到
js
// 错误示例
router.addRoute(route);
router.push({ name: 'Dashboard' }); // 可能失败
addRoute 是同步的,但最好等下一次 tick 再跳转:
js
router.addRoute(route);
await nextTick();
router.push({ name: 'Dashboard' });
坑 2:刷新后动态路由丢失
因为 addRoute 只存在内存中,刷新后路由会重置。正确做法:
- 登录成功后把「可访问路由」或权限存到
localStorage/sessionStorage - 在
router初始化或App.vue的onMounted里,先恢复 token,再根据存储的数据重新addRoute
js
// main.js 或 router 入口
if (localStorage.getItem('token')) {
const accessRoutes = getStoredRoutes(); // 从 storage 读出
accessRoutes.forEach((route) => router.addRoute(route));
}
坑 3:404 路由要最后加
404 的通配路由会匹配所有未定义 path,必须放在最后 add,否则后面的路由都会被当成 404。
[⬆ 返回目录](#⬆ 返回目录)
四、嵌套路由:多级页面结构
4.1 什么是嵌套路由
嵌套路由 = 父路由下挂子路由,形成多级页面结构。
典型结构:
/dashboard → 控制台(父)
/dashboard/overview → 控制台/概览(子)
/dashboard/analytics→ 控制台/分析(子)
实现方式:父路由用 children,父组件里用 <router-view> 渲染子路由。
[⬆ 返回目录](#⬆ 返回目录)
4.2 配置示例
js
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/layouts/DashboardLayout.vue'), // 父布局
redirect: '/dashboard/overview', // 访问 /dashboard 时重定向到第一个子路由
meta: { title: '控制台' },
children: [
{
path: 'overview',
name: 'DashboardOverview',
component: () => import('@/views/dashboard/Overview.vue'),
meta: { title: '概览' },
},
{
path: 'analytics',
name: 'DashboardAnalytics',
component: () => import('@/views/dashboard/Analytics.vue'),
meta: { title: '数据分析' },
},
],
}
注意:
- 子路由的
path不要以/开头,否则会变成根路径 path: 'overview'实际是/dashboard/overview
[⬆ 返回目录](#⬆ 返回目录)
4.3 父组件里放 router-view
html
<!-- layouts/DashboardLayout.vue -->
<template>
<div class="dashboard-layout">
<aside class="sidebar">
<router-link :to="{ name: 'DashboardOverview' }">概览</router-link>
<router-link :to="{ name: 'DashboardAnalytics' }">数据分析</router-link>
</aside>
<main class="content">
<router-view />
</main>
</div>
</template>
子路由的组件会渲染在 <router-view /> 的位置。
[⬆ 返回目录](#⬆ 返回目录)
4.4 redirect 的常见用法
访问 /dashboard 时,若没有默认子路由,可能看到空白。建议加 redirect:
js
{
path: '/dashboard',
redirect: '/dashboard/overview', // 或 { name: 'DashboardOverview' }
children: [...]
}
多级嵌套时,每层都可以有 redirect,指向当前层第一个有意义的子路由。
[⬆ 返回目录](#⬆ 返回目录)
五、统一规范总结
5.1 路由配置规范
| 项目 | 规范 |
|---|---|
| path | kebab-case,层级清晰,动态参数用 :id |
| name | PascalCase,每个路由都要有 |
| meta | 统一字段:title、requireAuth、keepAlive、permission 等 |
| 跳转 | 优先用 name,需要参数时用 params + query |
| 动态路由 | 基础路由 + 按权限 addRoute,404 放最后 |
| 嵌套路由 | 子 path 不加 /,父组件提供 <router-view>,合理使用 redirect |
[⬆ 返回目录](#⬆ 返回目录)
5.2 完整示例:一个规范的路由文件
js
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import MainLayout from '@/layouts/MainLayout.vue';
const routes = [
{
path: '/',
redirect: { name: 'Home' },
},
{
path: '/home',
name: 'Home',
component: () => import('@/views/Home.vue'),
meta: { title: '首页', noAuth: true },
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
meta: { title: '登录', noAuth: true },
},
{
path: '/dashboard',
name: 'Dashboard',
component: MainLayout,
redirect: { name: 'DashboardOverview' },
meta: { title: '控制台', requireAuth: true },
children: [
{
path: 'overview',
name: 'DashboardOverview',
component: () => import('@/views/dashboard/Overview.vue'),
meta: { title: '概览', keepAlive: true },
},
{
path: 'user/:id',
name: 'DashboardUserDetail',
component: () => import('@/views/dashboard/UserDetail.vue'),
meta: { title: '用户详情', requireAuth: true },
},
],
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
redirect: '/404',
},
{
path: '/404',
name: 'NotFoundPage',
component: () => import('@/views/404.vue'),
meta: { title: '页面不存在' },
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth && !localStorage.getItem('token')) {
next({ name: 'Login', query: { redirect: to.fullPath } });
} else {
document.title = to.meta.title || '我的应用';
next();
}
});
export default router;
[⬆ 返回目录](#⬆ 返回目录)
5.3 和状态管理的关系
- 路由:管「当前在哪个页面」、URL、面包屑、权限控制
- 状态管理(如 Pinia/Vuex):管「页面里的业务数据」
典型分工:
- 路由守卫里做:是否登录、是否有权限、重定向
- Store 里做:用户信息、列表数据、表单数据
js
// 路由守卫里:只做校验和跳转
if (to.meta.requireAuth && !store.state.user.token) {
next({ name: 'Login' });
return;
}
// Store 里:业务数据
store.dispatch('user/fetchProfile');
不要把大量业务状态塞进 meta,meta 只放和路由本身相关的配置。
[⬆ 返回目录](#⬆ 返回目录)
六、结语
Vue Router 日常使用只要抓住几条主线:
- path / name / meta 各司其职,命名统一
- 动态路由:基础路由 + 登录后按权限 addRoute,刷新要能恢复
- 嵌套路由 :
children+ 父组件里的<router-view>,配合redirect - 与 Store 分工:路由管「去哪」,Store 管「数据」
按这些规范写,可读性和可维护性都会好很多,也方便团队协作。
🔍 系列模块导航
📝 状态管理与路由规范
一、《Vue3 Pinia 状态管理规范:状态拆分、Actions 写法、持久化实战,避坑状态污染|状态管理与路由规范篇》
二、《Vue3 Pinia 状态管理规范:何时用 Pinia 何时用本地状态|状态管理与路由规范篇》
三、《Vue Router 实战规范:path/name/meta 配置 + 动态 / 嵌套路由,统一团队标准|状态管理与路由规范篇》
四、《Vue3 + Vue Router + Pinia 路由守卫规范:beforeEach 应做 / 不应做,避死循环、防重复请求|状态管理与路由规范篇》
五、《Vue keep-alive 实战避坑:include/exclude + 路由 meta 标记,中后台路由缓存精准可控|状态管理与路由规范篇》
👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~
📚 系列总览
「前端规范实战系列 」正在持续更新中,后续会整理一篇《前端规范实战系列全系列目录导航》,包含每篇文章简介 + 直达链接,方便大家按顺序、体系化学习。
更新中,敬请期待~
[⬆ 返回目录](#⬆ 返回目录)
技术成长,从来不是比谁写得快,而是比谁写得稳、规范、可维护。
哪怕每次只吃透一条规范,长期下来,差距会非常明显。
后续我会持续更新前端规范、工程化、可维护代码相关实战干货,帮你告别面条代码、维护噩梦,在开发与面试中更有底气。
觉得有用欢迎 点赞 + 收藏 + 关注,不错过每一篇实战内容。
我是 Eugene,与你一起写规范、写优质代码,我们下篇干货见~