嵌套路由是 Vue Router 中一个非常强大的功能,它允许我们在组件内部嵌套其他路由,从而构建复杂的页面布局和导航结构。通过嵌套路由,我们可以创建多级的用户界面,每个级别都有自己的路由配置和组件。
嵌套路由的核心概念是在一个路由组件内部定义子路由。这使得我们可以:
嵌套路由通过 children 属性在路由配置中定义。父路由的组件模板中需要包含 <router-view> 元素来渲染子路由组件。
考虑一个典型的网站布局:
使用嵌套路由,我们可以:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/dashboard',
name: 'dashboard',
component: () => import('@/views/DashboardLayout.vue'),
// children 属性定义嵌套路由
children: [
// 当访问 /dashboard 时,默认显示的子路由
{
path: '', // 空路径,匹配父路由本身
name: 'dashboard.home',
component: () => import('@/views/dashboard/DashboardHome.vue')
},
// 当访问 /dashboard/profile 时显示
{
path: 'profile', // 相对路径,相对于父路由的路径
name: 'dashboard.profile',
component: () => import('@/views/dashboard/Profile.vue')
},
// 当访问 /dashboard/settings 时显示
{
path: 'settings',
name: 'dashboard.settings',
component: () => import('@/views/dashboard/Settings.vue')
}
]
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
<!-- DashboardLayout.vue -->
<template>
<div class="dashboard-layout">
<!-- 顶部导航栏 -->
<header class="dashboard-header">
<h2>控制面板</h2>
<nav>
<router-link to="/dashboard">首页</router-link>
<router-link to="/dashboard/profile">个人资料</router-link>
<router-link to="/dashboard/settings">设置</router-link>
</nav>
</header>
<!-- 内容区域 -->
<main class="dashboard-content">
<!-- 嵌套路由出口 -->
<router-view></router-view>
</main>
</div>
</template>
<script>
export default {
name: 'DashboardLayout'
}
</script>
<style scoped>
.dashboard-layout {
display: flex;
flex-direction: column;
height: 100vh;
}
.dashboard-header {
background-color: #35495e;
color: white;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.dashboard-header nav a {
color: white;
margin-left: 20px;
text-decoration: none;
}
.dashboard-header nav a.router-link-active {
color: #42b983;
font-weight: bold;
}
.dashboard-content {
flex: 1;
padding: 20px;
}
</style>
<!-- Profile.vue -->
<template>
<div class="profile-page">
<h3>个人资料</h3>
<div class="profile-content">
<p>这里是个人资料页面内容。</p>
<p>这个组件在 DashboardLayout 的 router-view 中渲染。</p>
</div>
</div>
</template>
<script>
export default {
name: 'ProfilePage'
}
</script>
<style scoped>
.profile-page {
padding: 20px;
}
.profile-content {
margin-top: 20px;
padding: 20px;
background-color: #f5f5f5;
border-radius: 8px;
}
</style>
嵌套路由可以多层嵌套,构建更复杂的页面结构。
const routes = [
{
path: '/admin',
component: () => import('@/views/AdminLayout.vue'),
meta: { requiresAuth: true, role: 'admin' },
children: [
{
path: '',
name: 'admin.dashboard',
component: () => import('@/views/admin/Dashboard.vue')
},
{
path: 'users',
component: () => import('@/views/admin/UsersLayout.vue'),
children: [
{
path: '',
name: 'admin.users',
component: () => import('@/views/admin/users/List.vue')
},
{
path: 'create',
name: 'admin.users.create',
component: () => import('@/views/admin/users/Create.vue')
},
{
path: ':id',
name: 'admin.users.detail',
component: () => import('@/views/admin/users/Detail.vue'),
children: [
{
path: '',
name: 'admin.users.detail.info',
component: () => import('@/views/admin/users/detail/Info.vue')
},
{
path: 'edit',
name: 'admin.users.detail.edit',
component: () => import('@/views/admin/users/detail/Edit.vue')
},
{
path: 'permissions',
name: 'admin.users.detail.permissions',
component: () => import('@/views/admin/users/detail/Permissions.vue')
}
]
}
]
},
{
path: 'products',
component: () => import('@/views/admin/ProductsLayout.vue'),
children: [
{
path: '',
name: 'admin.products',
component: () => import('@/views/admin/products/List.vue')
},
{
path: 'categories',
name: 'admin.products.categories',
component: () => import('@/views/admin/products/Categories.vue')
}
]
}
]
}
]
在嵌套路由中进行导航时,需要注意路径的写法。
<!-- 在模板中导航 -->
<!-- 方法1: 使用完整路径 -->
<router-link to="/dashboard/profile">个人资料</router-link>
<!-- 方法2: 使用命名路由 -->
<router-link :to="{ name: 'dashboard.profile' }">个人资料</router-link>
<!-- 方法3: 相对路径(相对于当前路由) -->
<router-link :to="{ path: 'profile' }">个人资料</router-link>
<!-- 在 JavaScript 中导航 -->
<script>
// Options API
export default {
methods: {
goToProfile() {
// 方法1: 使用完整路径
this.$router.push('/dashboard/profile')
// 方法2: 使用命名路由
this.$router.push({ name: 'dashboard.profile' })
// 方法3: 相对路径
this.$router.push({ path: 'profile' })
// 方法4: 带参数的嵌套路由
this.$router.push({
name: 'admin.users.detail',
params: { id: 123 }
})
// 方法5: 带查询参数的嵌套路由
this.$router.push({
name: 'dashboard.profile',
query: { tab: 'settings' }
})
}
}
}
</script>
<!-- Composition API -->
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
function goToProfile() {
router.push('/dashboard/profile')
// 或使用命名路由
router.push({ name: 'dashboard.profile' })
}
</script>
<template>
<div>
<h3>当前路由信息</h3>
<!-- 显示当前路由的层级路径 -->
<div v-if="$route.matched.length">
<h4>路由匹配链:</h4>
<ul>
<li v-for="(route, index) in $route.matched" :key="index">
{{ route.path }} - {{ route.name || '未命名' }}
</li>
</ul>
</div>
</div>
</template>
<script>
// Options API
export default {
computed: {
// 获取当前路由的所有父路由
parentRoutes() {
return this.$route.matched.slice(0, -1)
},
// 获取当前路由
currentRoute() {
return this.$route.matched[this.$route.matched.length - 1]
},
// 面包屑导航数据
breadcrumbs() {
return this.$route.matched.map((route, index) => ({
name: route.meta?.breadcrumb || route.name,
path: route.path,
isLast: index === this.$route.matched.length - 1
}))
}
},
created() {
// 访问嵌套路由信息
console.log('当前路由:', this.$route)
console.log('匹配的路由记录:', this.$route.matched)
console.log('父路由:', this.parentRoutes)
console.log('当前路由记录:', this.currentRoute)
}
}
</script>
<!-- Composition API -->
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
// 获取匹配的路由记录
const matchedRoutes = computed(() => route.matched)
const parentRoutes = computed(() => route.matched.slice(0, -1))
const currentRoute = computed(() => route.matched[route.matched.length - 1])
// 面包屑导航
const breadcrumbs = computed(() =>
route.matched.map((routeRecord, index) => ({
name: routeRecord.meta?.breadcrumb || routeRecord.name,
path: routeRecord.path,
isLast: index === route.matched.length - 1
}))
)
console.log('当前路由:', route)
console.log('匹配的路由记录:', matchedRoutes.value)
</script>
嵌套路由也支持动态路径参数,可以构建更灵活的嵌套结构。
const routes = [
{
path: '/projects',
name: 'projects',
component: () => import('@/views/ProjectsLayout.vue'),
children: [
{
path: '',
name: 'projects.list',
component: () => import('@/views/projects/List.vue')
},
{
path: ':projectId',
name: 'projects.detail',
component: () => import('@/views/projects/DetailLayout.vue'),
props: true,
children: [
{
path: '',
name: 'projects.detail.overview',
component: () => import('@/views/projects/detail/Overview.vue')
},
{
path: 'tasks',
name: 'projects.detail.tasks',
component: () => import('@/views/projects/detail/Tasks.vue'),
children: [
{
path: '',
name: 'projects.detail.tasks.list',
component: () => import('@/views/projects/detail/tasks/List.vue')
},
{
path: ':taskId',
name: 'projects.detail.tasks.detail',
component: () => import('@/views/projects/detail/tasks/Detail.vue'),
props: true
}
]
},
{
path: 'members',
name: 'projects.detail.members',
component: () => import('@/views/projects/detail/Members.vue')
},
{
path: 'settings',
name: 'projects.detail.settings',
component: () => import('@/views/projects/detail/Settings.vue')
}
]
}
]
}
]
<!-- 在嵌套的组件中访问参数 -->
<!-- Tasks.vue (第二级嵌套) -->
<template>
<div>
<h3>项目任务</h3>
<p>当前项目ID: {{ projectId }}</p>
<!-- 嵌套路由出口 -->
<router-view></router-view>
</div>
</template>
<script>
// Options API
export default {
name: 'ProjectTasks',
computed: {
projectId() {
return this.$route.params.projectId
}
}
}
</script>
<!-- TaskDetail.vue (第三级嵌套) -->
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
// 访问多级参数
const projectId = computed(() => route.params.projectId)
const taskId = computed(() => route.params.taskId)
console.log('项目ID:', projectId.value)
console.log('任务ID:', taskId.value)
console.log('所有参数:', route.params)
</script>
模拟一个控制面板的嵌套路由结构:
欢迎来到控制面板!请从左侧选择要查看的内容。
路径: /dashboard
匹配的路由记录: 1 个
嵌套路由可以与命名视图结合,创建更复杂的布局结构。
const routes = [
{
path: '/app',
component: () => import('@/views/AppLayout.vue'),
children: [
{
path: 'dashboard',
components: {
default: () => import('@/views/dashboard/MainContent.vue'),
sidebar: () => import('@/views/dashboard/Sidebar.vue'),
header: () => import('@/views/dashboard/Header.vue')
},
children: [
{
path: '',
components: {
default: () => import('@/views/dashboard/Home.vue'),
sidebar: () => import('@/views/dashboard/HomeSidebar.vue')
}
},
{
path: 'analytics',
components: {
default: () => import('@/views/dashboard/Analytics.vue'),
sidebar: () => import('@/views/dashboard/AnalyticsSidebar.vue')
}
}
]
}
]
}
]
<!-- AppLayout.vue -->
<template>
<div class="app-layout">
<router-view name="header"></router-view>
<div class="app-content">
<router-view name="sidebar"></router-view>
<main class="main-content">
<router-view></router-view>
</main>
</div>
</div>
</template>
<!-- DashboardSidebar.vue -->
<template>
<aside class="dashboard-sidebar">
<!-- 仪表板级别的侧边栏 -->
<nav>
<router-link to="/app/dashboard">首页</router-link>
<router-link to="/app/dashboard/analytics">分析</router-link>
</nav>
<!-- 子路由的命名视图出口 -->
<router-view name="sidebar"></router-view>
</aside>
</template>
const routes = [
{
path: '/admin',
redirect: '/admin/dashboard', // 重定向到子路由
component: () => import('@/views/AdminLayout.vue'),
children: [
{
path: 'dashboard',
name: 'admin.dashboard',
component: () => import('@/views/admin/Dashboard.vue')
},
{
path: 'users',
redirect: 'users/list', // 相对重定向
component: () => import('@/views/admin/UsersLayout.vue'),
children: [
{
path: 'list',
name: 'admin.users.list',
component: () => import('@/views/admin/users/List.vue')
},
{
path: ':id',
redirect: { name: 'admin.users.detail.info' }, // 命名路由重定向
component: () => import('@/views/admin/users/DetailLayout.vue'),
children: [
{
path: 'info',
name: 'admin.users.detail.info',
component: () => import('@/views/admin/users/detail/Info.vue')
},
{
path: 'edit',
name: 'admin.users.detail.edit',
component: () => import('@/views/admin/users/detail/Edit.vue')
}
]
}
]
}
]
}
]
const routes = [
{
path: '/user',
component: () => import('@/views/UserLayout.vue'),
alias: '/member', // 父路由别名
children: [
{
path: 'profile',
name: 'user.profile',
component: () => import('@/views/user/Profile.vue'),
alias: ['/profile', '/account'] // 子路由别名
},
{
path: 'settings',
name: 'user.settings',
component: () => import('@/views/user/Settings.vue'),
alias: '/preferences' // 单个别名
}
]
}
]
// 访问以下路径都会显示 UserLayout 组件:
// /user
// /member (别名)
// 访问以下路径都会显示 Profile 组件:
// /user/profile
// /profile (别名)
// /account (别名)
嵌套路由支持在各级路由上配置导航守卫。
const routes = [
{
path: '/admin',
component: () => import('@/views/AdminLayout.vue'),
meta: { requiresAuth: true, role: 'admin' },
// 父路由守卫
beforeEnter: (to, from, next) => {
console.log('进入管理后台')
// 检查管理员权限
if (!hasAdminRole()) {
next({ name: 'forbidden' })
return
}
next()
},
children: [
{
path: 'dashboard',
name: 'admin.dashboard',
component: () => import('@/views/admin/Dashboard.vue')
},
{
path: 'users',
component: () => import('@/views/admin/UsersLayout.vue'),
meta: { requiresSuperAdmin: true },
// 子路由守卫
beforeEnter: (to, from, next) => {
console.log('进入用户管理')
// 检查超级管理员权限
if (!hasSuperAdminRole()) {
next({ name: 'admin.dashboard' })
return
}
next()
},
children: [
{
path: '',
name: 'admin.users',
component: () => import('@/views/admin/users/List.vue'),
// 更深层的守卫
beforeEnter: (to, from, next) => {
console.log('进入用户列表')
// 额外的检查
next()
}
}
]
}
]
}
]
// 全局守卫也会触发
router.beforeEach((to, from, next) => {
console.log('全局守卫:', to.path)
next()
})
// 组件内守卫也会按照嵌套层级执行
// 父组件的 beforeRouteLeave → 全局 beforeEach → 子组件的 beforeRouteEnter
$route.matched 实现面包屑导航嵌套路由是 Vue Router 中构建复杂单页面应用的重要工具。通过合理的嵌套路由设计,我们可以:
掌握嵌套路由的使用技巧,能够让你在开发复杂的 Vue.js 应用时更加得心应手。记住关键概念:
<router-view>?