Cours Vue.js · Module 2: Syntaxe des templates
Les directives Vue
Dans cette leçon4
Les directives sont des attributs spéciaux commençant par v-. Chaque directive est comme une instruction pour le système de contrôle de la base martienne.
Les moustaches de la leçon précédente ont deux lacunes. Elles n'atteignent pas les attributs, donc l'adresse de l'image d'un capteur ou le blocage d'un bouton ne peuvent pas dépendre des données. Et elles n'entendent pas l'opérateur : un clic sur l'hologramme ne déclenche rien. Les directives comblent ces deux lacunes. Leur structure est toujours la même : un nom après le préfixe v-, un argument après les deux-points, des modificateurs après des points et une expression entre guillemets. Dans v-on:submit.prevent="onSubmit", le nom est on, l'argument submit, le modificateur prevent et la valeur la fonction onSubmit.
v-bind - Liaison d'attributs
v-bind garde un attribut synchronisé avec les données : après les deux-points tu indiques le nom de l'attribut, et entre guillemets une expression. Comme cette directive revient sans arrêt, elle a un raccourci - les deux-points seuls. Voici le template d'un panneau avec quatre cas typiques :
1<template>
2 <div class="control-panel" style="background: #0a0e27; padding: 1rem;">
3 <!-- Syntaxe complète -->
4 <img v-bind:src="sensorImage" v-bind:alt="sensorName">
5
6 <!-- Syntaxe raccourcie (recommandée) -->
7 <img :src="sensorImage" :alt="sensorName">
8
9 <!-- Attributs dynamiques -->
10 <a :href="documentationUrl" :class="linkClass">Documentation</a>
11
12 <!-- Attributs booléens -->
13 <button :disabled="isProcessing" class="btn-neon">Launch</button>
14 </div>
15</template>Les deux images produisent un HTML identique - le raccourci ne change que la longueur de l'écriture. Entre les guillemets se trouve une expression, pas du texte : :alt="sensorName" insère la valeur de la variable, alors qu'un simple alt="sensorName" écrirait littéralement le mot sensorName. L'attribut booléen disabled apparaît quand la valeur est vraie et disparaît pour false. Quand une liaison donne null ou undefined, Vue retire complètement l'attribut.
Les données de ces liaisons sont de simples refs de la section script - rien de nouveau par rapport au module 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>Il suffit de régler isProcessing.value = true pour que le bouton reçoive l'attribut disabled - tu ne touches pas au template. L'apparence du bouton vient d'un CSS ordinaire, car v-bind ne change pas le fonctionnement des styles :
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>Il n'y a pas un seul élément propre à Vue dans la section des styles. Mon conseil : écris toujours le raccourci : - dans un template dense, tu vois tout de suite quels attributs sont vivants. Depuis Vue 3.4, une forme encore plus courte fonctionne : quand l'attribut et la variable portent le même nom, :id signifie la même chose que :id="id".
v-on - Gestion des événements
v-on attache une fonction à un événement du navigateur ; son raccourci est l'arobase @. La valeur peut être un nom de fonction ou une courte instruction, par exemple un appel avec un argument. La variable spéciale $event transmet l'objet événement quand tu appelles la fonction toi-même :
1<template>
2 <div class="reactor-controls" style="background: #0a0e27; color: #00b4d8;">
3 <!-- Syntaxe complète -->
4 <button v-on:click="handleActivation">Activate</button>
5
6 <!-- Syntaxe raccourcie (recommandée) -->
7 <button @click="handleActivation" class="btn-reactor">Activate Reactor</button>
8
9 <!-- Gestionnaire inline -->
10 <button @click="powerLevel++">Increase Power</button>
11
12 <!-- Avec argument -->
13 <button @click="sendSignal('ALPHA-BASE')">Send Signal</button>
14
15 <!-- Avec l'objet événement -->
16 <button @click="handleActivation($event)">Event Info</button>
17 </div>
18</template>Le compilateur reconnaît ce que tu lui donnes : le nom seul handleActivation signifie que Vue appelle la fonction et lui passe l'événement natif comme unique argument. Une écriture avec parenthèses est une instruction, donc l'événement doit être transmis explicitement par $event. Il n'y a pas de .value dans powerLevel++, car les refs sont déballés dans le template.
Les fonctions de gestion vivent dans le script, où .value s'applique de nouveau :
1<script setup>
2import { ref } from 'vue'
3
4const powerLevel = ref(0)
5
6function handleActivation(event) {
7 console.log('Reactor activated!', event)
8}
9
10function sendSignal(baseCode) {
11 alert(`Sending signal to: ${baseCode}`)
12}
13</script>Le bouton reste un <button> ordinaire - v-on ne fait qu'y ajouter une écoute d'événement, comme le ferait addEventListener.
Modificateurs d'événement
Les appels comme event.preventDefault() ou event.stopPropagation() encombrent les fonctions de détails du DOM. Un modificateur ajouté après un point règle cela dans le template. L'aide-mémoire ci-dessous ne montre que les balises ouvrantes :
1<template>
2 <div class="mission-control">
3 <!-- Empêcher l'action par défaut -->
4 <form @submit.prevent="onSubmit" class="mission-form">
5
6 <!-- Arrêter la propagation -->
7 <div @click.stop="onClick" class="stop-zone">
8
9 <!-- Une seule fois -->
10 <button @click.once="launchSequence" class="btn-launch">Start Sequence</button>
11
12 <!-- Mode capture -->
13 <div @click.capture="onCapture" class="capture-zone">
14
15 <!-- Uniquement soi-même -->
16 <div @click.self="onSelf" class="self-zone">
17
18 <!-- Combinaison -->
19 <a @click.stop.prevent="onClick" class="safe-link">
20 </div>
21</template>.prevent appelle event.preventDefault(), donc le formulaire ne recharge pas la page. .stop appelle event.stopPropagation(), et l'événement n'atteint pas les parents. .once ne laisse le gestionnaire agir qu'une seule fois, .capture traite l'événement en phase de capture, avant qu'il n'atteigne l'enfant, et .self ne réagit que si l'élément lui-même a été cliqué. Quand l'événement arrive, Vue applique d'abord les modificateurs, puis seulement appelle ta fonction. Dans une chaîne, l'ordre compte : @click.prevent.self bloque l'action par défaut aussi pour les enfants, tandis que @click.self.prevent ne le fait que pour l'élément lui-même.
Modificateurs de touches
Les événements keyup et keydown ont eux aussi des modificateurs. Vue connaît les alias .enter, .tab, .delete (Suppr et Retour arrière), .esc, .space et les flèches, et les touches système sont .ctrl, .alt, .shift et .meta. Les fonctions de l'exemple sont définies dans le script :
1<template>
2 <input @keyup.enter="sendCommand" placeholder="Commande pour la station">
3 <input @keyup.esc="clearCommand" placeholder="Échap vide le champ">
4 <button @click.ctrl="emergencyStop">Ctrl + clic : arrêt d'urgence</button>
5</template>sendCommand ne part qu'après Entrée, clearCommand qu'après Échap, et l'arrêt d'urgence exige la touche Ctrl enfoncée. Les autres touches ne déclenchent rien - sans un seul if qui vérifie event.key. Dans la prochaine leçon, tu verras que :class et :style sont des variantes spéciales de v-bind qui acceptent des objets et des tableaux.
Retiens : les deux-points relient un attribut aux données, l'arobase relie un événement à une fonction, et le point ajoute à l'ordre un protocole de sécurité.
Code de cette leçon : 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: 'Systèmes de survie : EN LIGNE', type: 'success' },
12 { id: 2, text: 'Niveau d’énergie : STABLE', type: 'info' },
13 { id: 3, text: 'Communication avec la Terre : ACTIVE', 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 pour les attributs src et alt -->
37 <img :src="logoUrl" :alt="'NOVA LAB Logo'" class="logo">
38 <h1>Station de recherche NOVA</h1>
39 </div>
40
41 <div class="power-control">
42 <h2>Contrôle de l'énergie</h2>
43 <!-- v-bind pour class et 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 (@) pour la gestion des événements -->
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>Statut des systèmes</h2>
61 <button @click="toggleDetails" class="btn btn-info">
62 {{ showDetails ? 'Masquer les détails' : 'Afficher les détails' }}
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>Les directives Vue.js</h3>
78 <ul>
79 <li><strong>v-bind</strong> (raccourci :) - liaison d'attributs</li>
80 <li><strong>v-on</strong> (raccourci @) - gestion des événements</li>
81 <li><strong>:class</strong> - classes CSS dynamiques</li>
82 <li><strong>:style</strong> - styles inline dynamiques</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>Le second éditeur est un laboratoire de modificateurs : envoie son formulaire avec .prevent, clique sur les boutons avec .stop et .once, et essaie Entrée, Échap et le raccourci Ctrl+S dans les champs de texte.
Code de cette leçon : 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: 'Système initialisé' },
15 { id: 2, text: 'Connexion établie avec la base martienne' },
16 { id: 3, text: 'Tous les systèmes opérationnels' }
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('Le formulaire a été arrêté par event.preventDefault()')
35}
36
37function stopPropagation(event) {
38 event.stopPropagation()
39 systemMessages.value.unshift({ id: nextMessageId++, text: 'Bouton cliqué - propagation arrêtée' })
40}
41
42function handleContainerClick() {
43 systemMessages.value.unshift({ id: nextMessageId++, text: 'Conteneur cliqué' })
44}
45
46function runOnce() {
47 systemMessages.value.unshift({ id: nextMessageId++, text: 'Ce gestionnaire ne s\'exécute qu\'une seule fois !' })
48}
49
50function handleEnter() {
51 systemMessages.value.unshift({ id: nextMessageId++, text: `Recherche exécutée : ${searchQuery.value}` })
52 searchQuery.value = ''
53}
54
55function handleEscape() {
56 searchQuery.value = ''
57 systemMessages.value.unshift({ id: nextMessageId++, text: 'Recherche effacée avec ÉCHAP' })
58}
59
60function handleCtrlS(event) {
61 event.preventDefault()
62 systemMessages.value.unshift({ id: nextMessageId++, text: 'Mission enregistrée (Ctrl+S)' })
63}
64
65function submitMission(event) {
66 event.preventDefault()
67 logs.value.unshift({
68 id: Date.now(),
69 command: `Mission : ${formData.value.mission} | Priorité : ${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>Laboratoire de modificateurs - NOVA LAB</h1>
80
81 <!-- Section des modificateurs d'événement -->
82 <section class="lab-section">
83 <h2>Modificateurs d'événement</h2>
84
85 <!-- Modificateur .prevent -->
86 <div class="demo-card">
87 <h3>.prevent - Empêcher l'action par défaut</h3>
88 <form @submit.prevent="preventRefresh" class="demo-form">
89 <input
90 type="text"
91 placeholder="Ce formulaire ne rechargera pas la page"
92 class="input-field"
93 >
94 <button type="submit" class="btn btn-primary">
95 Envoyer (preventDefault)
96 </button>
97 </form>
98 <code class="code-hint">@submit.prevent="preventRefresh"</code>
99 </div>
100
101 <!-- Modificateur .stop -->
102 <div class="demo-card">
103 <h3>.stop - Arrêter la propagation de l'événement</h3>
104 <div @click="handleContainerClick" class="click-container">
105 Clique sur le conteneur (l'événement se propage)
106 <button @click.stop="stopPropagation" class="btn btn-warning">
107 Clique sur le bouton (propagation arrêtée)
108 </button>
109 </div>
110 <code class="code-hint">@click.stop="stopPropagation"</code>
111 </div>
112
113 <!-- Modificateur .once -->
114 <div class="demo-card">
115 <h3>.once - Le gestionnaire ne s'exécute qu'une fois</h3>
116 <button @click.once="runOnce" class="btn btn-success">
117 Clique-moi (ne fonctionne qu'une fois)
118 </button>
119 <code class="code-hint">@click.once="runOnce"</code>
120 </div>
121
122 <!-- Modificateurs multiples -->
123 <div class="demo-card">
124 <h3>Combiner les modificateurs</h3>
125 <form @submit.prevent.stop="handleCommand" class="demo-form">
126 <input
127 v-model="commandInput"
128 type="text"
129 placeholder="Saisis une commande..."
130 class="input-field"
131 >
132 <button type="submit" class="btn btn-primary">
133 Exécuter la commande
134 </button>
135 </form>
136 <code class="code-hint">@submit.prevent.stop="handleCommand"</code>
137 </div>
138 </section>
139
140 <!-- Section des modificateurs de touche -->
141 <section class="lab-section">
142 <h2>Modificateurs de touche</h2>
143
144 <!-- Modificateur .enter -->
145 <div class="demo-card">
146 <h3>.enter - Détecte la touche Entrée</h3>
147 <input
148 v-model="searchQuery"
149 @keyup.enter="handleEnter"
150 type="text"
151 placeholder="Tape puis appuie sur Entrée..."
152 class="input-field"
153 >
154 <code class="code-hint">@keyup.enter="handleEnter"</code>
155 </div>
156
157 <!-- Modificateur .esc -->
158 <div class="demo-card">
159 <h3>.esc - Détecte la touche Échap</h3>
160 <input
161 v-model="searchQuery"
162 @keyup.esc="handleEscape"
163 type="text"
164 placeholder="Tape puis appuie sur ÉCHAP pour effacer..."
165 class="input-field"
166 >
167 <code class="code-hint">@keyup.esc="handleEscape"</code>
168 </div>
169
170 <!-- Touches modificatrices système -->
171 <div class="demo-card">
172 <h3>.ctrl + touche - Combinaisons de touches</h3>
173 <textarea
174 v-model="formData.description"
175 @keydown.ctrl.s="handleCtrlS"
176 placeholder="Appuie sur Ctrl+S pour enregistrer..."
177 class="textarea-field"
178 rows="4"
179 ></textarea>
180 <code class="code-hint">@keydown.ctrl.s="handleCtrlS"</code>
181 </div>
182
183 <!-- Modificateurs de touche multiples -->
184 <div class="demo-card">
185 <h3>Formulaire complet avec modificateurs</h3>
186 <form @submit.prevent="submitMission" class="mission-form">
187 <input
188 v-model="formData.mission"
189 type="text"
190 placeholder="Nom de la mission (Entrée pour envoyer)..."
191 class="input-field"
192 >
193 <select v-model="formData.priority" class="select-field">
194 <option value="low">Priorité basse</option>
195 <option value="medium">Priorité moyenne</option>
196 <option value="high">Priorité haute</option>
197 </select>
198 <button type="submit" class="btn btn-primary">
199 Envoyer la mission
200 </button>
201 </form>
202 </div>
203 </section>
204
205 <!-- Messages système -->
206 <section class="lab-section">
207 <h2>Messages système</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 <!-- Journaux de commandes -->
221 <section class="lab-section" v-if="logs.length > 0">
222 <h2>Journaux de commandes</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 <!-- Encadré d'information -->
236 <div class="info-box">
237 <h3>Modificateurs d'événement et de touche dans Vue</h3>
238 <div class="info-grid">
239 <div>
240 <h4>Modificateurs d'événement :</h4>
241 <ul>
242 <li><strong>.prevent</strong> - event.preventDefault()</li>
243 <li><strong>.stop</strong> - event.stopPropagation()</li>
244 <li><strong>.once</strong> - gestionnaire une seule fois</li>
245 <li><strong>.capture</strong> - mode capture</li>
246 <li><strong>.self</strong> - seulement si target === currentTarget</li>
247 <li><strong>.passive</strong> - { passive: true }</li>
248 </ul>
249 </div>
250 <div>
251 <h4>Modificateurs de touche :</h4>
252 <ul>
253 <li><strong>.enter</strong> - touche Entrée</li>
254 <li><strong>.tab</strong> - touche Tab</li>
255 <li><strong>.delete</strong> - Suppr/Retour arrière</li>
256 <li><strong>.esc</strong> - touche Échap</li>
257 <li><strong>.space</strong> - touche Espace</li>
258 <li><strong>.up/.down/.left/.right</strong> - touches fléchées</li>
259 <li><strong>.ctrl/.alt/.shift/.meta</strong> - touches système</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>Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. À quoi sert la directive v-bind ?
2. Quel est le raccourci de v-bind:href ?
3. À quoi sert v-on ?
4. Que signifie <img :src> sans valeur quand une variable src existe dans <script setup> ?
5. Que fait .prevent dans @submit.prevent ?
6. Que fait le modificateur .stop dans v-on ?
Tâches pratiques dans le jeu
- Éditeur de code
Utilise v-bind (raccourci :) pour affecter dynamiquement les attributs src, alt, href et disabled.
- Clique dans l'ordre
Ordonne la syntaxe de gestion du clic avec un argument :
- Éditeur de code
Utilise v-on (ou son raccourci @) pour gérer les événements click, submit et keyup.
- Tri horizontal
Ordonne la syntaxe correcte de v-bind pour un attribut src dynamique :
- Tri horizontal
Ordonne la syntaxe correcte de v-on pour gérer un clic :
- Tri vertical
Ordonne les étapes de gestion d'un événement clic dans Vue :
- Éditeur de code
Utilise les modificateurs .prevent, .stop et .once dans la gestion des événements.