Kurs Vue.js · Moduł 10: Vue Router

Zagnieżdżone Trasy

2 min czytania
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. 1. Jak zdefiniować zagnieżdżone (nested) routes w Vue Router?

  2. 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.

Przydatne artykuły