Cours Vue.js · Module 10: Vue Router
Routes imbriquées
Dans cette leçon5
La base NOVA LAB a des modules, les modules ont des systèmes, les systèmes ont des composants - c'est une hiérarchie. De la même manière, on peut imbriquer des routes, créant des mises en page complexes avec des sous-pages.
Configuration des routes imbriquées
La clé est la propriété children dans la définition de la route :
1const routes = [
2 {
3 path: '/mission-control',
4 name: 'mission-control',
5 component: MissionControlLayout,
6 children: [
7 {
8 path: '', // vue par défaut pour /mission-control
9 name: 'mission-overview',
10 component: MissionOverview
11 },
12 {
13 path: 'systems', // /mission-control/systems
14 name: 'mission-systems',
15 component: MissionSystems
16 },
17 {
18 path: 'crew', // /mission-control/crew
19 name: 'mission-crew',
20 component: MissionCrew
21 }
22 ]
23 }
24]Important : les chemins des children SANS slash au début sont relatifs au parent. Ainsi 'systems' devient '/mission-control/systems'.
Composant parent avec RouterView
Le composant parent DOIT contenir <RouterView> - c'est là que se rendent les children :
1<!-- MissionControlLayout.vue -->
2<template>
3 <div class="layout">
4 <aside class="sidebar">
5 <h2>Centre de contrôle</h2>
6 <nav>
7 <RouterLink :to="{ name: 'mission-overview' }">
8 Aperçu
9 </RouterLink>
10 <RouterLink :to="{ name: 'mission-systems' }">
11 Systèmes
12 </RouterLink>
13 <RouterLink :to="{ name: 'mission-crew' }">
14 Équipage
15 </RouterLink>
16 </nav>
17 </aside>
18
19 <main class="content">
20 <!-- Les routes imbriquées se rendent ici -->
21 <RouterView />
22 </main>
23 </div>
24</template>Route enfant par défaut
Un path: '' vide définit la vue par défaut affichée lorsque l'utilisateur navigue vers le chemin parent :
1children: [
2 {
3 path: '', // /mission-control
4 component: MissionOverview
5 },
6 {
7 path: 'systems', // /mission-control/systems
8 component: MissionSystems
9 }
10]Redirection
Tu peux aussi utiliser une redirection à la place d'une vue par défaut :
1children: [
2 {
3 path: '',
4 redirect: '/mission-control/dashboard'
5 },
6 {
7 path: 'dashboard',
8 component: Dashboard
9 }
10]Vues nommées (named views)
Une seule route peut rendre plusieurs composants dans différents emplacements <RouterView> :
1const routes = [
2 {
3 path: '/mission-control',
4 components: {
5 default: MissionMain,
6 sidebar: MissionSidebar,
7 footer: MissionFooter
8 }
9 }
10]1<template>
2 <RouterView name="sidebar" />
3 <RouterView /> <!-- default -->
4 <RouterView name="footer" />
5</template>Code de cette leçon : App.vue
1<script setup>
2import { ref } from 'vue'
3
4// Simulation des navigation guards
5const isAuthenticated = ref(false)
6const currentRoute = ref('home')
7const guardLog = ref([])
8const pageTitle = ref('NOVA LAB - Home')
9
10const routes = [
11 { name: 'home', label: 'Home', meta: {} },
12 { name: 'login', label: 'Login', meta: { requiresGuest: true } },
13 { name: 'dashboard', label: 'Dashboard', meta: { requiresAuth: true } },
14 { name: 'admin', label: 'Admin', meta: { requiresAuth: true, requiresAdmin: true } }
15]
16
17function navigate(routeName) {
18 const route = routes.find(r => r.name === routeName)
19 if (!route) return
20
21 guardLog.value = []
22
23 // Simulation de beforeEach
24 guardLog.value.push('beforeEach: ' + currentRoute.value + ' -> ' + routeName)
25
26 // Vérifie requiresAuth
27 if (route.meta.requiresAuth && !isAuthenticated.value) {
28 guardLog.value.push('BLOCKED: requiresAuth - redirect to login')
29 currentRoute.value = 'login'
30 pageTitle.value = 'NOVA LAB - Login'
31 return
32 }
33
34 // Vérifie requiresGuest
35 if (route.meta.requiresGuest && isAuthenticated.value) {
36 guardLog.value.push('BLOCKED: requiresGuest - redirect to home')
37 currentRoute.value = 'home'
38 pageTitle.value = 'NOVA LAB - Home'
39 return
40 }
41
42 // Navigation autorisée
43 guardLog.value.push('ALLOWED: navigation ok')
44 currentRoute.value = routeName
45 pageTitle.value = 'NOVA LAB - ' + route.label
46
47 // Simulation de afterEach
48 guardLog.value.push('afterEach: title = ' + pageTitle.value)
49}
50
51function toggleAuth() {
52 isAuthenticated.value = !isAuthenticated.value
53 guardLog.value.push('Auth: ' + (isAuthenticated.value ? 'connecté' : 'déconnecté'))
54}
55</script>
56
57<template>
58 <div class="nova-lab">
59 <h1>NOVA LAB - Navigation Guards</h1>
60
61 <div class="auth-bar">
62 <span>Statut : {{ isAuthenticated ? 'Connecté' : 'Non connecté' }}</span>
63 <button @click="toggleAuth" class="auth-btn">
64 {{ isAuthenticated ? 'Déconnexion' : 'Connexion' }}
65 </button>
66 </div>
67
68 <nav class="main-nav">
69 <button
70 v-for="route in routes"
71 :key="route.name"
72 @click="navigate(route.name)"
73 :class="{ active: currentRoute === route.name }"
74 class="nav-btn"
75 >
76 {{ route.label }}
77 <span v-if="route.meta.requiresAuth" class="badge lock">LOCK</span>
78 <span v-if="route.meta.requiresGuest" class="badge guest">GUEST</span>
79 </button>
80 </nav>
81
82 <div class="guard-log">
83 <h3>Guard Log:</h3>
84 <div v-for="(log, i) in guardLog" :key="i" class="log-entry"
85 :class="{
86 blocked: log.includes('BLOCKED'),
87 allowed: log.includes('ALLOWED')
88 }"
89 >
90 {{ log }}
91 </div>
92 </div>
93
94 <div class="page-content">
95 <h2>{{ pageTitle }}</h2>
96 <pre>
97// Configuration des guards :
98router.beforeEach((to, from) => {
99 if (to.meta.requiresAuth && !isAuthenticated) {
100 return { name: 'login' }
101 }
102})
103
104router.afterEach((to) => {
105 document.title = to.meta.title || 'NOVA LAB'
106})</pre>
107 </div>
108 </div>
109</template>
110
111<style scoped>
112.nova-lab {
113 background: #0a0e27;
114 color: #00ff88;
115 min-height: 100vh;
116 padding: 20px;
117 font-family: monospace;
118}
119h1 { color: #00ffff; margin-bottom: 10px; }
120h2 { color: #00b4d8; }
121h3 { color: #00ffff; margin-bottom: 5px; }
122.auth-bar {
123 display: flex;
124 align-items: center;
125 gap: 10px;
126 padding: 8px 12px;
127 background: #1a1e37;
128 margin-bottom: 10px;
129 color: #c0c0c0;
130}
131.auth-btn {
132 background: rgba(0, 255, 136, 0.2);
133 border: 1px solid #00ff88;
134 color: #00ff88;
135 padding: 4px 10px;
136 cursor: pointer;
137 font-family: monospace;
138}
139.main-nav { display: flex; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
140.nav-btn {
141 background: rgba(0, 180, 216, 0.1);
142 border: 1px solid #00b4d8;
143 color: #00ff88;
144 padding: 6px 12px;
145 cursor: pointer;
146 font-family: monospace;
147 position: relative;
148}
149.nav-btn.active {
150 background: #00b4d8;
151 color: #0a0e27;
152}
153.badge {
154 font-size: 9px;
155 padding: 1px 4px;
156 margin-left: 4px;
157}
158.badge.lock { color: #ff4444; }
159.badge.guest { color: #ffaa00; }
160.guard-log {
161 background: #1a1e37;
162 padding: 10px;
163 margin-bottom: 10px;
164 max-height: 120px;
165 overflow-y: auto;
166}
167.log-entry {
168 font-size: 12px;
169 color: #888;
170 padding: 2px 0;
171}
172.log-entry.blocked { color: #ff4444; }
173.log-entry.allowed { color: #00ff88; }
174.page-content {
175 background: rgba(0, 180, 216, 0.05);
176 border: 1px solid #1a1e37;
177 padding: 15px;
178}
179pre {
180 background: #1a1e37;
181 padding: 10px;
182 color: #00ff88;
183 font-size: 11px;
184 margin-top: 10px;
185}
186</style>Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Comment définit-on des routes imbriquées dans Vue Router ?
2. Où sont rendus les composants des routes imbriquées ?
Voici 2 des 4 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Éditeur de code
Configure une route parente avec un tableau children et une mise en page avec <RouterView>.
- Tri vertical
Ordonne les éléments du plus externe au plus interne :
- Clique dans l'ordre
Ordonne le code de définition d'une route imbriquée :
- Éditeur de code
Ajoute une route enfant par défaut avec un path vide et un composant overview.