Kurs Vue.js · Moduł 10: Vue Router
Zagnieżdżone Trasy
W tej lekcji5
Baza NOVA LAB ma moduły, moduły mają systemy, systemy mają komponenty - to hierarchia. Podobnie możemy zagnieżdżać trasy, tworząc złożone layouty z podstronami.
Konfiguracja Nested Routes
Kluczem jest właściwość children w definicji route:
1const routes = [
2 {
3 path: '/mission-control',
4 name: 'mission-control',
5 component: MissionControlLayout,
6 children: [
7 {
8 path: '', // domyślny widok dla /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]Ważne: ścieżki children BEZ slasha na początku są relatywne do rodzica. Więc 'systems' staje się '/mission-control/systems'.
Parent Component z RouterView
Komponent rodzica MUSI zawierać <RouterView> - to tam renderują się children:
1<!-- MissionControlLayout.vue -->
2<template>
3 <div class="layout">
4 <aside class="sidebar">
5 <h2>Centrum Kontroli</h2>
6 <nav>
7 <RouterLink :to="{ name: 'mission-overview' }">
8 Przegląd
9 </RouterLink>
10 <RouterLink :to="{ name: 'mission-systems' }">
11 Systemy
12 </RouterLink>
13 <RouterLink :to="{ name: 'mission-crew' }">
14 Załoga
15 </RouterLink>
16 </nav>
17 </aside>
18
19 <main class="content">
20 <!-- Nested routes renderują się tutaj -->
21 <RouterView />
22 </main>
23 </div>
24</template>Domyślny Child Route
Pusty path: '' definiuje domyślny widok wyswietlany gdy uzytkownik wchodzi na ścieżkę rodzica:
1children: [
2 {
3 path: '', // /mission-control
4 component: MissionOverview
5 },
6 {
7 path: 'systems', // /mission-control/systems
8 component: MissionSystems
9 }
10]Redirect
Można tez uzyc przekierowania zamiast domyślnego widoku:
1children: [
2 {
3 path: '',
4 redirect: '/mission-control/dashboard'
5 },
6 {
7 path: 'dashboard',
8 component: Dashboard
9 }
10]Named Views
Jeden route może renderowac wiele komponentow w różnych <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>Kod do tej lekcji: App.vue
1<script setup>
2import { ref } from 'vue'
3
4// Symulacja 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 // Symulacja beforeEach
24 guardLog.value.push('beforeEach: ' + currentRoute.value + ' -> ' + routeName)
25
26 // Sprawdz 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 // Sprawdz 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 // Nawigacja dozwolona
43 guardLog.value.push('ALLOWED: navigation ok')
44 currentRoute.value = routeName
45 pageTitle.value = 'NOVA LAB - ' + route.label
46
47 // Symulacja 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 ? 'zalogowany' : 'wylogowany'))
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>Status: {{ isAuthenticated ? 'Zalogowany' : 'Niezalogowany' }}</span>
63 <button @click="toggleAuth" class="auth-btn">
64 {{ isAuthenticated ? 'Wyloguj' : 'Zaloguj' }}
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// Konfiguracja 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>Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Jak zdefiniować zagnieżdżone (nested) routes w Vue Router?
2. Gdzie renderują się komponenty zagnieżdżonych routes?
To 2 z 4 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Skonfiguruj parent route z children array i layout z <RouterView>.
- Układanie w pionie
Uporządkuj elementy od zewnętrznego do wewnętrznego:
- Klikanie w kolejności
Ułóż kod definicji nested route:
- Edytor kodu
Dodaj domyślny child route z pustym path i komponent overview.