Kurs Vue.js · Moduł 2: Składnia szablonów
Dyrektywy Vue
W tej lekcji4
Dyrektywy to specjalne atrybuty zaczynające się od v-. Każda dyrektywa to jak instrukcja dla systemu kontrolnego bazy na Marsie.
Wąsy z poprzedniej lekcji mają dwie luki. Nie sięgają do atrybutów, więc adres obrazka czujnika czy blokada przycisku nie mogą zależeć od danych. I nie słyszą operatora: kliknięcie w hologram niczego nie uruchamia. Dyrektywy łatają obie luki. Ich budowa jest zawsze taka sama: nazwa po przedrostku v-, argument po dwukropku, modyfikatory po kropkach i wyrażenie w cudzysłowie. W zapisie v-on:submit.prevent="onSubmit" nazwą jest on, argumentem submit, modyfikatorem prevent, a wartością funkcja onSubmit.
v-bind - Wiązanie Atrybutów
v-bind utrzymuje atrybut w zgodzie z danymi: po dwukropku podajesz nazwę atrybutu, a w cudzysłowie wyrażenie. Ponieważ ta dyrektywa pojawia się bez przerwy, ma skrót - sam dwukropek. Oto szablon panelu z czterema typowymi przypadkami:
1<template>
2 <div class="control-panel" style="background: #0a0e27; padding: 1rem;">
3 <!-- Pełna składnia -->
4 <img v-bind:src="sensorImage" v-bind:alt="sensorName">
5
6 <!-- Skrócona składnia (zalecana) -->
7 <img :src="sensorImage" :alt="sensorName">
8
9 <!-- Dynamiczne atrybuty -->
10 <a :href="documentationUrl" :class="linkClass">Dokumentacja</a>
11
12 <!-- Atrybuty logiczne -->
13 <button :disabled="isProcessing" class="btn-neon">Uruchom</button>
14 </div>
15</template>Oba obrazki dają identyczny HTML - skrót niczego nie zmienia poza długością zapisu. W cudzysłowie stoi wyrażenie, a nie tekst: :alt="sensorName" wstawi wartość zmiennej, a zwykłe alt="sensorName" wpisałoby dosłownie słowo sensorName. Atrybut logiczny disabled pojawia się, gdy wartość jest prawdziwa, i znika dla false. Gdy wiązanie da null lub undefined, Vue usunie atrybut całkiem.
Dane do tych wiązań to zwykłe refy z sekcji skryptu - nic nowego w porównaniu z modułem 1:
1<script setup>
2import { ref } from 'vue'
3
4const sensorImage = ref('/images/oxygen-sensor.png')
5const sensorName = ref('Oxygen Sensor MK-7')
6const documentationUrl = ref('https://nova-lab.mars/docs')
7const linkClass = ref('neon-link')
8const isProcessing = ref(false)
9</script>Wystarczy ustawić isProcessing.value = true, a przycisk dostanie atrybut disabled - szablonu nie dotykasz. Wygląd przycisku zapewnia zwykły CSS, bo v-bind nie zmienia zasad działania stylów:
1<style>
2.btn-neon {
3 background: transparent;
4 border: 2px solid #00ff88;
5 color: #00ff88;
6 padding: 0.5rem 1rem;
7 cursor: pointer;
8}
9.btn-neon:hover {
10 background: #00ff8822;
11 box-shadow: 0 0 10px #00ff88;
12}
13</style>W sekcji stylów nie ma ani jednego elementu Vue. Polecam zawsze pisać skrót : - w gęstym szablonie od razu widać, które atrybuty żyją. Od Vue 3.4 działa nawet krótszy zapis: gdy atrybut i zmienna mają tę samą nazwę, :id znaczy to samo co :id="id".
v-on - Obsługa Zdarzeń
v-on podpina funkcję pod zdarzenie przeglądarki; skrótem jest małpa @. Wartością może być nazwa funkcji albo krótka instrukcja, na przykład wywołanie z argumentem. Specjalna zmienna $event przekazuje obiekt zdarzenia, gdy wywołujesz funkcję sam:
1<template>
2 <div class="reactor-controls" style="background: #0a0e27; color: #00b4d8;">
3 <!-- Pełna składnia -->
4 <button v-on:click="handleActivation">Aktywuj</button>
5
6 <!-- Skrócona składnia (zalecana) -->
7 <button @click="handleActivation" class="btn-reactor">Aktywuj Reaktor</button>
8
9 <!-- Instrukcja w szablonie -->
10 <button @click="powerLevel++">Zwiększ Moc</button>
11
12 <!-- Z argumentem -->
13 <button @click="sendSignal('ALPHA-BASE')">Wyślij Sygnał</button>
14
15 <!-- Z obiektem zdarzenia -->
16 <button @click="handleActivation($event)">Event Info</button>
17 </div>
18</template>Kompilator rozpoznaje, co podałeś: sama nazwa handleActivation oznacza, że Vue wywoła funkcję i przekaże jej natywne zdarzenie jako jedyny argument. Zapis z nawiasami to instrukcja, więc zdarzenie trzeba podać jawnie przez $event. W powerLevel++ nie ma .value, bo w szablonie refy są rozpakowane.
Funkcje obsługi mieszkają w skrypcie, gdzie obowiązuje już .value:
1<script setup>
2import { ref } from 'vue'
3
4const powerLevel = ref(0)
5
6function handleActivation(event) {
7 console.log('Reaktor aktywowany!', event)
8}
9
10function sendSignal(baseCode) {
11 alert(`Wysyłanie sygnału do: ${baseCode}`)
12}
13</script>Przycisk pozostał zwykłym <button> - v-on tylko dodaje do niego nasłuchiwanie zdarzenia, tak jak zrobiłby to addEventListener.
Modyfikatory Zdarzeń
Wywołania event.preventDefault() czy event.stopPropagation() zaśmiecają funkcje szczegółami DOM. Modyfikator dopisany po kropce załatwia to w szablonie. Poniższa ściąga pokazuje same znaczniki otwierające:
1<template>
2 <div class="mission-control">
3 <!-- Bez domyślnej akcji -->
4 <form @submit.prevent="onSubmit" class="mission-form">
5
6 <!-- Bez propagacji -->
7 <div @click.stop="onClick" class="stop-zone">
8
9 <!-- Tylko raz -->
10 <button @click.once="launchSequence" class="btn-launch">Uruchom Sekwencję</button>
11
12 <!-- Faza przechwytywania -->
13 <div @click.capture="onCapture" class="capture-zone">
14
15 <!-- Tylko sam element -->
16 <div @click.self="onSelf" class="self-zone">
17
18 <!-- Kombinacja -->
19 <a @click.stop.prevent="onClick" class="safe-link">
20 </div>
21</template>.prevent wywołuje event.preventDefault(), więc formularz nie przeładuje strony. .stop wywołuje event.stopPropagation() i zdarzenie nie dotrze do rodziców. .once pozwala zadziałać najwyżej raz, .capture obsługuje zdarzenie w fazie przechwytywania, zanim trafi do dziecka, a .self reaguje tylko wtedy, gdy kliknięto sam element. Gdy zdarzenie nadejdzie, Vue najpierw stosuje modyfikatory, a dopiero potem woła Twoją funkcję. W łańcuchu liczy się kolejność: @click.prevent.self blokuje domyślną akcję także dla dzieci, a @click.self.prevent tylko dla samego elementu.
Modyfikatory Klawiszy
Zdarzenia keyup i keydown też mają modyfikatory. Vue zna aliasy .enter, .tab, .delete (Delete i Backspace), .esc, .space i strzałki, a klawisze systemowe to .ctrl, .alt, .shift i .meta. Funkcje z przykładu są zdefiniowane w skrypcie:
1<template>
2 <input @keyup.enter="sendCommand" placeholder="Komenda dla stacji">
3 <input @keyup.esc="clearCommand" placeholder="Esc czyści pole">
4 <button @click.ctrl="emergencyStop">Ctrl + klik: stop awaryjny</button>
5</template>sendCommand ruszy tylko po Enterze, clearCommand tylko po Escape, a stop awaryjny wymaga wciśniętego Ctrl. Inne klawisze nie wywołają niczego - bez ani jednego if sprawdzającego event.key. W następnej lekcji zobaczysz, że :class i :style to specjalne odmiany v-bind, które przyjmują obiekty i tablice.
Zapamiętaj: dwukropek łączy atrybut z danymi, małpa łączy zdarzenie z funkcją, a kropka dopisuje do rozkazu protokół bezpieczeństwa.
Kod do tej lekcji: App.vue
1<script setup>
2import { ref } from 'vue'
3
4const stationStatus = ref('operational')
5const powerLevel = ref(85)
6const alertLevel = ref('low')
7const showDetails = ref(false)
8const logoUrl = ref('https://placehold.co/100/00ff88/0a0e27?text=NOVA')
9
10const systemMessages = ref([
11 { id: 1, text: 'Systemy podtrzymywania życia: ONLINE', type: 'success' },
12 { id: 2, text: 'Poziom energii: STABILNY', type: 'info' },
13 { id: 3, text: 'Komunikacja z Ziemią: AKTYWNA', type: 'success' }
14])
15
16function toggleDetails() {
17 showDetails.value = !showDetails.value
18}
19
20function increasePower() {
21 if (powerLevel.value < 100) {
22 powerLevel.value += 5
23 }
24}
25
26function decreasePower() {
27 if (powerLevel.value > 0) {
28 powerLevel.value -= 5
29 }
30}
31</script>
32
33<template>
34 <div class="control-panel">
35 <div class="station-header">
36 <!-- v-bind dla atrybutów src i alt -->
37 <img :src="logoUrl" :alt="'NOVA LAB Logo'" class="logo">
38 <h1>Stacja Badawcza NOVA</h1>
39 </div>
40
41 <div class="power-control">
42 <h2>Kontrola Energii</h2>
43 <!-- v-bind dla class i style -->
44 <div
45 class="power-bar"
46 :class="{ 'critical': powerLevel < 30, 'optimal': powerLevel >= 70 }"
47 :style="{ width: powerLevel + '%' }"
48 >
49 {{ powerLevel }}%
50 </div>
51
52 <!-- v-on (@) dla obsługi zdarzeń -->
53 <div class="controls">
54 <button @click="decreasePower" class="btn btn-danger">-5%</button>
55 <button @click="increasePower" class="btn btn-success">+5%</button>
56 </div>
57 </div>
58
59 <div class="status-section">
60 <h2>Status Systemów</h2>
61 <button @click="toggleDetails" class="btn btn-info">
62 {{ showDetails ? 'Ukryj szczegóły' : 'Pokaż szczegóły' }}
63 </button>
64
65 <div v-show="showDetails" class="messages">
66 <div
67 v-for="msg in systemMessages"
68 :key="msg.id"
69 :class="['message', 'message-' + msg.type]"
70 >
71 {{ msg.text }}
72 </div>
73 </div>
74 </div>
75
76 <div class="info-box">
77 <h3>Dyrektywy Vue.js</h3>
78 <ul>
79 <li><strong>v-bind</strong> (skrót :) - wiązanie atrybutów</li>
80 <li><strong>v-on</strong> (skrót @) - obsługa zdarzeń</li>
81 <li><strong>:class</strong> - dynamiczne klasy CSS</li>
82 <li><strong>:style</strong> - dynamiczne style inline</li>
83 </ul>
84 </div>
85 </div>
86</template>
87
88<style scoped>
89.control-panel {
90 background: linear-gradient(135deg, #0a0e27 0%, #1a1e3f 100%);
91 min-height: 100vh;
92 padding: 2rem;
93 color: #ffffff;
94 font-family: 'Courier New', monospace;
95}
96
97.station-header {
98 text-align: center;
99 margin-bottom: 2rem;
100}
101
102.logo {
103 width: 100px;
104 height: 100px;
105 border-radius: 50%;
106 border: 3px solid #00ff88;
107 box-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
108}
109
110h1, h2, h3 {
111 color: #00ff88;
112 text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
113}
114
115.power-control {
116 background: rgba(0, 255, 136, 0.1);
117 border: 2px solid #00ff88;
118 border-radius: 10px;
119 padding: 1.5rem;
120 margin-bottom: 2rem;
121}
122
123.power-bar {
124 height: 40px;
125 background: linear-gradient(90deg, #00ff88, #00b4d8);
126 border-radius: 20px;
127 display: flex;
128 align-items: center;
129 justify-content: center;
130 font-weight: bold;
131 transition: all 0.3s ease;
132 margin: 1rem 0;
133}
134
135.power-bar.critical {
136 background: linear-gradient(90deg, #ff0055, #ff6600);
137 animation: pulse 1s infinite;
138}
139
140.power-bar.optimal {
141 box-shadow: 0 0 20px rgba(0, 255, 136, 0.8);
142}
143
144@keyframes pulse {
145 0%, 100% { opacity: 1; }
146 50% { opacity: 0.7; }
147}
148
149.controls {
150 display: flex;
151 gap: 1rem;
152 justify-content: center;
153 margin-top: 1rem;
154}
155
156.btn {
157 padding: 0.75rem 1.5rem;
158 border: none;
159 border-radius: 5px;
160 font-size: 1rem;
161 font-weight: bold;
162 cursor: pointer;
163 transition: all 0.3s ease;
164 font-family: 'Courier New', monospace;
165}
166
167.btn-success {
168 background: #00ff88;
169 color: #0a0e27;
170}
171
172.btn-danger {
173 background: #ff0055;
174 color: #ffffff;
175}
176
177.btn-info {
178 background: #00b4d8;
179 color: #ffffff;
180}
181
182.btn:hover {
183 transform: scale(1.05);
184 box-shadow: 0 5px 15px rgba(0, 255, 136, 0.4);
185}
186
187.status-section {
188 background: rgba(0, 180, 216, 0.1);
189 border: 2px solid #00b4d8;
190 border-radius: 10px;
191 padding: 1.5rem;
192 margin-bottom: 2rem;
193}
194
195.messages {
196 margin-top: 1rem;
197}
198
199.message {
200 padding: 1rem;
201 margin: 0.5rem 0;
202 border-radius: 5px;
203 border-left: 4px solid;
204}
205
206.message-success {
207 background: rgba(0, 255, 136, 0.2);
208 border-color: #00ff88;
209}
210
211.message-info {
212 background: rgba(0, 180, 216, 0.2);
213 border-color: #00b4d8;
214}
215
216.info-box {
217 background: rgba(255, 255, 255, 0.05);
218 border: 2px solid #00ff88;
219 border-radius: 10px;
220 padding: 1.5rem;
221}
222
223.info-box ul {
224 list-style: none;
225 padding: 0;
226}
227
228.info-box li {
229 padding: 0.5rem 0;
230 color: #ffffff;
231}
232
233.info-box strong {
234 color: #00b4d8;
235}
236</style>Drugi edytor to laboratorium modyfikatorów: wyślij w nim formularz z .prevent, kliknij przyciski z .stop i .once, a w polach tekstowych wypróbuj Enter, Escape i skrót Ctrl+S.
Kod do tej lekcji: App.vue
1<script setup>
2import { ref } from 'vue'
3
4const commandInput = ref('')
5const searchQuery = ref('')
6const formData = ref({
7 mission: '',
8 priority: 'medium',
9 description: ''
10})
11
12const logs = ref([])
13const systemMessages = ref([
14 { id: 1, text: 'System uruchomiony' },
15 { id: 2, text: 'Połączono z bazą na Marsie' },
16 { id: 3, text: 'Wszystkie systemy sprawne' }
17])
18let nextMessageId = 4
19
20function handleCommand(event) {
21 event.preventDefault()
22 if (commandInput.value.trim()) {
23 logs.value.unshift({
24 id: Date.now(),
25 command: commandInput.value,
26 timestamp: new Date().toLocaleTimeString()
27 })
28 commandInput.value = ''
29 }
30}
31
32function preventRefresh(event) {
33 event.preventDefault()
34 alert('Formularz został zatrzymany przez event.preventDefault()')
35}
36
37function stopPropagation(event) {
38 event.stopPropagation()
39 systemMessages.value.unshift({ id: nextMessageId++, text: 'Kliknięto przycisk - propagacja zatrzymana' })
40}
41
42function handleContainerClick() {
43 systemMessages.value.unshift({ id: nextMessageId++, text: 'Kliknięto kontener' })
44}
45
46function runOnce() {
47 systemMessages.value.unshift({ id: nextMessageId++, text: 'Ta funkcja zadziała tylko raz!' })
48}
49
50function handleEnter() {
51 systemMessages.value.unshift({ id: nextMessageId++, text: `Wyszukano: ${searchQuery.value}` })
52 searchQuery.value = ''
53}
54
55function handleEscape() {
56 searchQuery.value = ''
57 systemMessages.value.unshift({ id: nextMessageId++, text: 'Wyszukiwanie wyczyszczone klawiszem ESC' })
58}
59
60function handleCtrlS(event) {
61 event.preventDefault()
62 systemMessages.value.unshift({ id: nextMessageId++, text: 'Misja zapisana (Ctrl+S)' })
63}
64
65function submitMission(event) {
66 event.preventDefault()
67 logs.value.unshift({
68 id: Date.now(),
69 command: `Misja: ${formData.value.mission} | Priorytet: ${formData.value.priority}`,
70 timestamp: new Date().toLocaleTimeString()
71 })
72 formData.value.mission = ''
73 formData.value.description = ''
74}
75</script>
76
77<template>
78 <div class="modifiers-lab">
79 <h1>Laboratorium Modyfikatorów - NOVA LAB</h1>
80
81 <!-- Sekcja modyfikatorów zdarzeń -->
82 <section class="lab-section">
83 <h2>Modyfikatory zdarzeń</h2>
84
85 <!-- Modyfikator .prevent -->
86 <div class="demo-card">
87 <h3>.prevent - Zatrzymanie domyślnej akcji</h3>
88 <form @submit.prevent="preventRefresh" class="demo-form">
89 <input
90 type="text"
91 placeholder="Ten formularz nie odświeży strony"
92 class="input-field"
93 >
94 <button type="submit" class="btn btn-primary">
95 Wyślij (preventDefault)
96 </button>
97 </form>
98 <code class="code-hint">@submit.prevent="preventRefresh"</code>
99 </div>
100
101 <!-- Modyfikator .stop -->
102 <div class="demo-card">
103 <h3>.stop - Zatrzymanie propagacji zdarzenia</h3>
104 <div @click="handleContainerClick" class="click-container">
105 Kliknij w kontener (zdarzenie się propaguje)
106 <button @click.stop="stopPropagation" class="btn btn-warning">
107 Kliknij w przycisk (propagacja zatrzymana)
108 </button>
109 </div>
110 <code class="code-hint">@click.stop="stopPropagation"</code>
111 </div>
112
113 <!-- Modyfikator .once -->
114 <div class="demo-card">
115 <h3>.once - Funkcja obsługi działa tylko raz</h3>
116 <button @click.once="runOnce" class="btn btn-success">
117 Kliknij mnie (działa tylko raz)
118 </button>
119 <code class="code-hint">@click.once="runOnce"</code>
120 </div>
121
122 <!-- Kilka modyfikatorów naraz -->
123 <div class="demo-card">
124 <h3>Łączenie modyfikatorów</h3>
125 <form @submit.prevent.stop="handleCommand" class="demo-form">
126 <input
127 v-model="commandInput"
128 type="text"
129 placeholder="Wpisz komendę..."
130 class="input-field"
131 >
132 <button type="submit" class="btn btn-primary">
133 Wykonaj komendę
134 </button>
135 </form>
136 <code class="code-hint">@submit.prevent.stop="handleCommand"</code>
137 </div>
138 </section>
139
140 <!-- Sekcja modyfikatorów klawiszy -->
141 <section class="lab-section">
142 <h2>Modyfikatory klawiszy</h2>
143
144 <!-- Modyfikator .enter -->
145 <div class="demo-card">
146 <h3>.enter - Wykrywa klawisz Enter</h3>
147 <input
148 v-model="searchQuery"
149 @keyup.enter="handleEnter"
150 type="text"
151 placeholder="Wpisz i naciśnij Enter..."
152 class="input-field"
153 >
154 <code class="code-hint">@keyup.enter="handleEnter"</code>
155 </div>
156
157 <!-- Modyfikator .esc -->
158 <div class="demo-card">
159 <h3>.esc - Wykrywa klawisz Escape</h3>
160 <input
161 v-model="searchQuery"
162 @keyup.esc="handleEscape"
163 type="text"
164 placeholder="Wpisz i naciśnij ESC aby wyczyścić..."
165 class="input-field"
166 >
167 <code class="code-hint">@keyup.esc="handleEscape"</code>
168 </div>
169
170 <!-- Klawisze systemowe -->
171 <div class="demo-card">
172 <h3>.ctrl + klawisz - Kombinacje klawiszy</h3>
173 <textarea
174 v-model="formData.description"
175 @keydown.ctrl.s="handleCtrlS"
176 placeholder="Naciśnij Ctrl+S aby zapisać..."
177 class="textarea-field"
178 rows="4"
179 ></textarea>
180 <code class="code-hint">@keydown.ctrl.s="handleCtrlS"</code>
181 </div>
182
183 <!-- Kilka modyfikatorów klawiszy -->
184 <div class="demo-card">
185 <h3>Kompleksowy formularz z modyfikatorami</h3>
186 <form @submit.prevent="submitMission" class="mission-form">
187 <input
188 v-model="formData.mission"
189 type="text"
190 placeholder="Nazwa misji (Enter wysyła)..."
191 class="input-field"
192 >
193 <select v-model="formData.priority" class="select-field">
194 <option value="low">Niski priorytet</option>
195 <option value="medium">Średni priorytet</option>
196 <option value="high">Wysoki priorytet</option>
197 </select>
198 <button type="submit" class="btn btn-primary">
199 Wyślij misję
200 </button>
201 </form>
202 </div>
203 </section>
204
205 <!-- Komunikaty systemu -->
206 <section class="lab-section">
207 <h2>Komunikaty systemu</h2>
208 <div class="messages-container">
209 <div
210 v-for="msg in systemMessages.slice(0, 10)"
211 :key="msg.id"
212 class="message-item"
213 >
214 <span class="message-time">{{ new Date().toLocaleTimeString() }}</span>
215 <span class="message-text">{{ msg.text }}</span>
216 </div>
217 </div>
218 </section>
219
220 <!-- Dziennik komend -->
221 <section class="lab-section" v-if="logs.length > 0">
222 <h2>Dziennik komend</h2>
223 <div class="logs-container">
224 <div
225 v-for="log in logs"
226 :key="log.id"
227 class="log-item"
228 >
229 <span class="log-time">{{ log.timestamp }}</span>
230 <span class="log-command">{{ log.command }}</span>
231 </div>
232 </div>
233 </section>
234
235 <!-- Ramka z podsumowaniem -->
236 <div class="info-box">
237 <h3>Modyfikatory zdarzeń i klawiszy w Vue</h3>
238 <div class="info-grid">
239 <div>
240 <h4>Modyfikatory zdarzeń:</h4>
241 <ul>
242 <li><strong>.prevent</strong> - event.preventDefault()</li>
243 <li><strong>.stop</strong> - event.stopPropagation()</li>
244 <li><strong>.once</strong> - funkcja obsługi tylko raz</li>
245 <li><strong>.capture</strong> - faza przechwytywania</li>
246 <li><strong>.self</strong> - tylko jeśli target === currentTarget</li>
247 <li><strong>.passive</strong> - { passive: true }</li>
248 </ul>
249 </div>
250 <div>
251 <h4>Modyfikatory klawiszy:</h4>
252 <ul>
253 <li><strong>.enter</strong> - klawisz Enter</li>
254 <li><strong>.tab</strong> - klawisz Tab</li>
255 <li><strong>.delete</strong> - Delete/Backspace</li>
256 <li><strong>.esc</strong> - klawisz Escape</li>
257 <li><strong>.space</strong> - spacja</li>
258 <li><strong>.up/.down/.left/.right</strong> - strzałki</li>
259 <li><strong>.ctrl/.alt/.shift/.meta</strong> - klawisze systemowe</li>
260 </ul>
261 </div>
262 </div>
263 </div>
264 </div>
265</template>
266
267<style scoped>
268.modifiers-lab {
269 background: linear-gradient(135deg, #0a0e27 0%, #1a1e3f 100%);
270 min-height: 100vh;
271 padding: 2rem;
272 color: #ffffff;
273 font-family: 'Courier New', monospace;
274}
275
276h1 {
277 text-align: center;
278 color: #00ff88;
279 text-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
280 margin-bottom: 3rem;
281}
282
283h2 {
284 color: #00b4d8;
285 margin-bottom: 1.5rem;
286 border-bottom: 2px solid #00b4d8;
287 padding-bottom: 0.5rem;
288}
289
290h3 {
291 color: #00ff88;
292 font-size: 1.1rem;
293 margin-bottom: 1rem;
294}
295
296.lab-section {
297 background: rgba(0, 255, 136, 0.05);
298 border: 2px solid #00ff88;
299 border-radius: 10px;
300 padding: 2rem;
301 margin-bottom: 2rem;
302}
303
304.demo-card {
305 background: rgba(0, 180, 216, 0.1);
306 border: 2px solid #00b4d8;
307 border-radius: 8px;
308 padding: 1.5rem;
309 margin-bottom: 1.5rem;
310}
311
312.demo-form {
313 display: flex;
314 gap: 1rem;
315 margin-bottom: 1rem;
316 flex-wrap: wrap;
317}
318
319.mission-form {
320 display: flex;
321 flex-direction: column;
322 gap: 1rem;
323}
324
325.input-field, .select-field, .textarea-field {
326 background: rgba(0, 0, 0, 0.3);
327 border: 2px solid #00b4d8;
328 border-radius: 5px;
329 padding: 0.75rem;
330 color: #ffffff;
331 font-family: 'Courier New', monospace;
332 font-size: 1rem;
333 flex: 1;
334 min-width: 200px;
335}
336
337.input-field:focus, .select-field:focus, .textarea-field:focus {
338 outline: none;
339 border-color: #00ff88;
340 box-shadow: 0 0 10px rgba(0, 255, 136, 0.3);
341}
342
343.input-field::placeholder, .textarea-field::placeholder {
344 color: rgba(255, 255, 255, 0.5);
345}
346
347.btn {
348 padding: 0.75rem 1.5rem;
349 border: none;
350 border-radius: 5px;
351 font-size: 1rem;
352 font-weight: bold;
353 cursor: pointer;
354 transition: all 0.3s ease;
355 font-family: 'Courier New', monospace;
356}
357
358.btn-primary {
359 background: #00ff88;
360 color: #0a0e27;
361}
362
363.btn-warning {
364 background: #ffb400;
365 color: #0a0e27;
366}
367
368.btn-success {
369 background: #00b4d8;
370 color: #ffffff;
371}
372
373.btn:hover {
374 transform: scale(1.05);
375 box-shadow: 0 5px 15px rgba(0, 255, 136, 0.4);
376}
377
378.click-container {
379 background: rgba(255, 180, 0, 0.1);
380 border: 2px dashed #ffb400;
381 border-radius: 5px;
382 padding: 2rem;
383 text-align: center;
384 cursor: pointer;
385 transition: all 0.3s ease;
386 margin-bottom: 1rem;
387}
388
389.click-container:hover {
390 background: rgba(255, 180, 0, 0.2);
391}
392
393.code-hint {
394 display: block;
395 background: rgba(0, 0, 0, 0.5);
396 border-left: 4px solid #00ff88;
397 padding: 0.75rem;
398 border-radius: 5px;
399 color: #00ff88;
400 font-family: 'Courier New', monospace;
401 font-size: 0.9rem;
402}
403
404.messages-container, .logs-container {
405 background: rgba(0, 0, 0, 0.3);
406 border-radius: 8px;
407 padding: 1rem;
408 max-height: 300px;
409 overflow-y: auto;
410}
411
412.message-item, .log-item {
413 padding: 0.75rem;
414 background: rgba(0, 180, 216, 0.1);
415 border-left: 4px solid #00b4d8;
416 border-radius: 5px;
417 margin-bottom: 0.5rem;
418 display: flex;
419 gap: 1rem;
420 align-items: center;
421}
422
423.message-time, .log-time {
424 color: #ffb400;
425 font-size: 0.9rem;
426 min-width: 100px;
427}
428
429.message-text, .log-command {
430 color: #ffffff;
431 flex: 1;
432}
433
434.info-box {
435 background: rgba(0, 255, 136, 0.05);
436 border: 2px solid #00ff88;
437 border-radius: 10px;
438 padding: 2rem;
439}
440
441.info-box h3 {
442 margin-top: 0;
443 margin-bottom: 1.5rem;
444}
445
446.info-grid {
447 display: grid;
448 grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
449 gap: 2rem;
450}
451
452.info-box h4 {
453 color: #00b4d8;
454 margin-bottom: 1rem;
455}
456
457.info-box ul {
458 list-style: none;
459 padding: 0;
460}
461
462.info-box li {
463 padding: 0.5rem 0;
464 color: #ffffff;
465 border-left: 3px solid #00b4d8;
466 padding-left: 1rem;
467 margin: 0.5rem 0;
468}
469
470.info-box strong {
471 color: #00ff88;
472}
473</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. Do czego służy dyrektywa v-bind?
2. Jaki jest skrót dla v-bind:href?
3. Do czego służy v-on?
4. Co oznacza <img :src> bez wartości, gdy w <script setup> istnieje zmienna src?
5. Co robi .prevent w @submit.prevent?
6. Co robi modyfikator .stop w v-on?
Zadania praktyczne w grze
- Edytor kodu
Użyj v-bind (skrót :) do przypisania dynamicznego src, alt, href i disabled.
- Klikanie w kolejności
Ułóż składnię obsługi kliknięcia z argumentem:
- Edytor kodu
Użyj v-on (lub skrótu @), aby obsłużyć zdarzenia click, submit i keyup.
- Układanie w poziomie
Ułóż poprawną składnię v-bind dla dynamicznego atrybutu src:
- Układanie w poziomie
Ułóż poprawną składnię v-on dla obsługi kliknięcia:
- Układanie w pionie
Uporządkuj kroki obsługi zdarzenia click w Vue:
- Edytor kodu
Użyj modyfikatorów .prevent, .stop i .once w obsłudze zdarzeń.