Kurs Vue.js · Moduł 2: Składnia szablonów

Projekt: Interaktywny Panel Hologramów NOVA LAB

4 min czytania
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, .once upraszczają 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-if dodaje/usuwa z DOM, v-show tylko przełącza display.
  • v-for - renderowanie list z :key, iteracja po tablicach i obiektach, unikanie v-for z v-if na tym samym elemencie.
  • Template Refs - bezpośredni dostęp do elementów DOM przez ref, function refs, refs na komponentach z defineExpose.
  • 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?

Przydatne artykuły