Kurs Vue.js · Moduł 2: Składnia szablonów
Projekt: Interaktywny Panel Hologramów NOVA LAB
W tej lekcji3
Czas na projekt końcowy! Operator Pracowni Hologramów ma pod opieką kilkanaście projekcji: mapy, schematy reaktora, dane z atmosfery. Część z nich jest wyłączona, a nowe dochodzą w trakcie zmiany. Potrzebuje panelu, który pokaże wszystkie hologramy, przefiltruje je według stanu i zareaguje na każde kliknięcie, a przy tym będzie trzymał dane w jednym miejscu, żeby lista, filtr i licznik nigdy się nie rozjechały.
W tym projekcie połączysz wszystkie techniki szablonów Vue, które poznałeś w tym module: od wyświetlania danych, przez filtrowanie i stylowanie, po obsługę zdarzeń i bezpośredni dostęp do DOM.
Podsumowanie modułu
W tym module nauczyłeś się:
- Interpolacja
{{ }}- wyświetlanie danych reaktywnych i wyrażeń JavaScript w szablonach. Pamiętaj o ograniczeniach: tylko wyrażenia, nie instrukcje. - v-bind (
:) - dynamiczne wiązanie atrybutów HTML::src,:class,:style,:disabled. Skrócona składnia:jest standardem. - v-on (
@) - obsługa zdarzeń użytkownika:@click,@submit,@keyup. Modyfikatory jak.prevent,.stop,.onceupraszczają typowe operacje. - Wiązanie klas i stylów - składnia obiektowa (
:class="{ active: isActive }"), składnia tablicowa (:class="[classA, classB]") i style inline przez:style. - v-if / v-else-if / v-else / v-show - warunkowe renderowanie.
v-ifdodaje/usuwa z DOM,v-showtylko przełączadisplay. - v-for - renderowanie list z
:key, iteracja po tablicach i obiektach, unikaniev-forzv-ifna tym samym elemencie. - Template Refs - bezpośredni dostęp do elementów DOM przez
ref, function refs, refs na komponentach zdefineExpose. - v-html - renderowanie surowego HTML (uwaga na XSS!).
Jak podejść do projektu
Zacznij od danych, a szablon dokładaj dopiero potem. Hologramy to tablica obiektów z polami id, name, type i active, trzymana w ref([...]). Listę wyświetlasz przez v-for z :key="h.id". Indeks nie nadaje się na klucz, bo po przefiltrowaniu ten sam numer dostanie inny hologram i Vue pomyli karty. To ta sama pułapka, którą w lekcji o v-for pokazało sortowanie czujników.
Filtr to osobny ref, na przykład filter z wartościami 'all', 'active' i 'inactive'. Przefiltrowaną listę zwraca computed, który czyta filter i tablicę hologramów, a v-for przechodzi właśnie po nim. Nie dopisuj v-if do elementu z v-for: v-if ma wyższy priorytet i nie widzi zmiennej pętli. W starterze filteredHolograms jest zwykłym refem z kopią tablicy, więc zamień go na computed.
Wygląd karty zależy od jej stanu, więc użyj :class="{ 'is-active': h.active, 'is-inactive': !h.active }", a kolory ramek i neonową poświatę opisz w CSS. :style zostaw dla wartości naprawdę liczonych z danych, na przykład szerokości paska mocy.
Zdarzenia łączą panel z operatorem. @click na karcie przełącza jej pole active, Enter w polu nazwy (@keyup.enter) i przycisk dodają nowy hologram, a "Usuń ostatni" wywołuje pop() na tablicy. Licznik "X aktywnych z Y całkowitych" to kolejny computed, więc przeliczy się sam po każdej zmianie.
Na koniec przewijanie: nowy hologram ląduje na końcu listy, często poza widocznym obszarem. Tu przydaje się template ref z poprzedniej lekcji i funkcja nextTick, którą opisuje punkt 5 poniżej.
Wymagania projektu
Zbuduj Panel Kontrolny Hologramów, który zawiera:
1. Lista hologramów z v-for
Wyświetl listę hologramów stacji. Każdy hologram ma nazwę, typ (np. "navigation", "diagnostic", "communication") i status (aktywny/nieaktywny). Użyj :key z unikalnym ID.
2. Filtrowanie przez computed
Dodaj trzy przyciski filtrów: "Wszystkie", "Aktywne", "Nieaktywne". Na podstawie wybranego filtra pokaż odpowiednie hologramy. Użyj właściwości computed do filtrowania listy.
3. Dynamiczne klasy i style
Użyj :class, aby hologramy aktywne miały zieloną ramkę (border-color: #00ff88), a nieaktywne czerwoną (border-color: #ff4444). Dodaj efekt neonowego świecenia (box-shadow) dla aktywnych elementów.
4. Obsługa zdarzeń
- Kliknięcie na hologram przełącza jego status (aktywny/nieaktywny)
- Przycisk "Dodaj hologram" dodaje do listy hologram o nazwie wpisanej w polu
- Przycisk "Usuń ostatni" usuwa ostatni element z listy
- Wyświetl licznik: "X aktywnych z Y całkowitych"
5. Template Ref
Oznacz kontener listy atrybutem ref (np. ref="listRef", a w skrypcie const listRef = ref(null)). Po dodaniu hologramu Vue aktualizuje DOM dopiero chwilę później, więc przewiń listę w nextTick (import z vue): nextTick(() => { listRef.value.scrollTop = listRef.value.scrollHeight }). Bez nextTick przewiniesz do starego dołu listy i nowy element zostanie pod krawędzią.
W funkcji, która dodaje hologram, wygląda to tak:
1function addHologram() {
2 if (!newName.value.trim()) return
3 holograms.value.push({ id: Date.now(), name: newName.value, type: 'custom', active: true })
4 newName.value = ''
5 nextTick(() => {
6 listRef.value.scrollTop = listRef.value.scrollHeight
7 })
8}Tablica holograms zmienia się od razu, opóźnione jest tylko odświeżenie strony, dlatego scrollHeight czytasz dopiero w funkcji przekazanej do nextTick. Żeby kontener w ogóle dało się przewinąć, nadaj mu w CSS ograniczoną wysokość, na przykład max-height: 360px i overflow-y: auto.
Rozbuduj starter poniżej o wszystkie wymagane funkcjonalności!
Zapamiętaj: dobry panel trzyma dane w jednym miejscu, a szablon tylko je pokazuje i przekazuje rozkazy operatora.
Kod do tej lekcji: App.vue
1<script setup>
2import { ref } from 'vue'
3
4// Panel Hologramów NOVA LAB - projekt końcowy modułu 2
5const filter = ref('all')
6const newName = ref('')
7
8const holograms = ref([
9 { id: 1, name: 'Mapa Marsa', type: 'navigation', active: true },
10 { id: 2, name: 'Schemat Reaktora', type: 'engineering', active: true },
11 { id: 3, name: 'Dane Atmosferyczne', type: 'science', active: false },
12 { id: 4, name: 'Plan Habitatu', type: 'architecture', active: true }
13])
14
15// TODO: Dodaj filtrowanie hologramów według statusu
16// TODO: Dodaj przełączanie statusu hologramu
17// TODO: Dodaj tworzenie nowego hologramu
18// TODO: Użyj template ref do przewijania listy
19
20const filteredHolograms = ref(holograms.value)
21
22function toggleStatus(id) {
23 const h = holograms.value.find(h => h.id === id)
24 if (h) h.active = !h.active
25}
26
27function addHologram() {
28 if (!newName.value.trim()) return
29 holograms.value.push({
30 id: Date.now(),
31 name: newName.value,
32 type: 'custom',
33 active: true
34 })
35 newName.value = ''
36}
37</script>
38
39<template>
40 <div class="hologram-panel">
41 <h1>Panel Hologramów NOVA LAB</h1>
42
43 <div class="filters">
44 <button @click="filter = 'all'" :class="{ active: filter === 'all' }">Wszystkie</button>
45 <button @click="filter = 'active'" :class="{ active: filter === 'active' }">Aktywne</button>
46 <button @click="filter = 'inactive'" :class="{ active: filter === 'inactive' }">Nieaktywne</button>
47 </div>
48
49 <div class="add-form">
50 <input v-model="newName" placeholder="Nazwa nowego hologramu" @keyup.enter="addHologram" />
51 <button @click="addHologram">Dodaj</button>
52 </div>
53
54 <div class="hologram-list">
55 <!-- TODO: Użyj v-for z przefiltrowanymi hologramami -->
56 <!-- TODO: Dodaj dynamiczne klasy :class -->
57 <div
58 v-for="h in holograms"
59 :key="h.id"
60 class="hologram-card"
61 :class="{ 'is-active': h.active, 'is-inactive': !h.active }"
62 @click="toggleStatus(h.id)"
63 >
64 <h3>{{ h.name }}</h3>
65 <span class="type">{{ h.type }}</span>
66 <span class="status">{{ h.active ? 'AKTYWNY' : 'NIEAKTYWNY' }}</span>
67 </div>
68 </div>
69 </div>
70</template>
71
72<style scoped>
73.hologram-panel { background: #0d0d2b; color: #e0e0ff; padding: 2rem; min-height: 100vh; font-family: monospace; }
74h1 { color: #7b68ee; text-align: center; }
75.filters { display: flex; gap: 0.5rem; margin: 1rem 0; justify-content: center; }
76.filters button { background: #1a1a4e; color: #aaa; border: 1px solid #333; padding: 0.5rem 1rem; cursor: pointer; border-radius: 4px; }
77.filters button.active { background: #7b68ee; color: white; }
78.add-form { display: flex; gap: 0.5rem; margin: 1rem 0; }
79.add-form input { flex: 1; background: #1a1a4e; border: 1px solid #333; color: white; padding: 0.5rem; border-radius: 4px; }
80.add-form button { background: #7b68ee; color: white; border: none; padding: 0.5rem 1rem; cursor: pointer; border-radius: 4px; }
81.hologram-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; }
82.hologram-card { padding: 1rem; border-radius: 8px; cursor: pointer; background: #1a1a4e; transition: all 0.3s; }
83.hologram-card.is-active { border: 2px solid #00ff88; }
84.hologram-card.is-inactive { border: 2px solid #ff4444; opacity: 0.7; }
85.type { color: #888; font-size: 0.8rem; display: block; }
86.status { font-weight: bold; margin-top: 0.5rem; display: block; }
87</style>Widzisz błąd w tej lekcji?