Cours Vue.js · Module 5: Événements et formulaires

Projet : Système de contrôle de mission

3 min de lecture
Dans cette leçon2

Projet final du Simulateur de Contrôle ! Construis un système complet de formulaires et d'événements pour le contrôle des missions spatiales à la station NOVA LAB.

Résumé du module

Dans ce module, tu as appris les mécanismes d'interaction clés de Vue.js, qui constituent le fondement de toute application web :

  • Gestion des événements - les directives @click, @input, @submit, @keydown et autres événements du DOM qui permettent de réagir aux actions de l'utilisateur en temps réel
  • Modificateurs d'événements - .prevent (bloquer l'action par défaut), .stop (arrêter la propagation), .once (appel unique), .self (uniquement sur l'élément cible) - un contrôle précis du comportement des événements
  • v-model - la liaison de données bidirectionnelle dans les formulaires qui synchronise automatiquement les données entre le template et l'état du composant
  • Modificateurs de v-model - .lazy (mise à jour au blur), .number (conversion automatique en nombre), .trim (suppression des espaces) - personnaliser le comportement de la liaison
  • Formulaires avancés - cases à cocher, boutons radio, menus déroulants, zones de texte et autres contrôles qui se connectent aux données réactives de Vue

Chacun de ces mécanismes correspond à un système de contrôle de la station NOVA LAB - les événements sont les boutons du panneau de contrôle, v-model est la communication bidirectionnelle avec les capteurs, et les modificateurs sont les filtres de signal qui garantissent la précision.

Exigences du projet

Construis un Panneau d'enregistrement de mission - une interface complète pour planifier des expéditions martiennes qui inclut :

1. Formulaire de données de mission

Crée un formulaire étendu avec les champs : nom de la mission (champ texte), destination (menu déroulant avec les options : Olympus Mons, Valles Marineris, Calotte polaire, Cratère Jezero), priorité (radio : faible/moyenne/élevée/critique), durée estimée (champ numérique en jours), description de la mission (zone de texte) et consentement aux règles de sécurité (case à cocher). Chaque champ doit être lié à un état réactif via v-model.

2. Modificateurs de v-model

Utilise .trim sur les champs texte (nom de la mission, description) pour supprimer automatiquement les espaces inutiles. Applique .number sur le champ de la durée pour que la valeur soit toujours un nombre. Utilise .lazy sur le champ de description de la mission pour que la mise à jour ne se fasse qu'après avoir quitté le champ - cela réduit le nombre de re-rendus.

3. Validation du formulaire

Implémente une validation en temps réel : nom de la mission d'au moins 3 caractères, description d'au moins 20 caractères, durée comprise entre 1 et 365 jours, consentement obligatoire aux règles de sécurité. Affiche les messages d'erreur sous chaque champ. Le bouton d'envoi doit être désactivé lorsque le formulaire est invalide.

4. Gestion des événements et récapitulatif

Ajoute @submit.prevent sur le formulaire, la gestion de la touche Échap (@keydown.escape) pour effacer le formulaire avec confirmation (confirm()), et affiche un récapitulatif de la mission enregistrée après l'envoi du formulaire. Ajoute aussi un historique des missions enregistrées affiché sous le formulaire.

Développe le starter ci-dessous !

Code de cette leçon : App.vue
1<script setup>
2import { ref, reactive } from 'vue'
3
4// Système de Contrôle de Mission - Projet final du module 5
5const missionForm = reactive({
6  name: '',
7  destination: 'mars',
8  priority: 'normal',
9  description: '',
10  crewSize: 4,
11  acceptTerms: false
12})
13
14const errors = ref({})
15const submitted = ref(false)
16const missions = ref([])
17
18function validate() {
19  const e = {}
20  if (!missionForm.name.trim()) e.name = 'Le nom de la mission est obligatoire'
21  if (!missionForm.description.trim()) e.description = 'La description est obligatoire'
22  if (missionForm.crewSize < 1 || missionForm.crewSize > 20) e.crewSize = 'Équipage : 1-20 personnes'
23  if (!missionForm.acceptTerms) e.acceptTerms = 'Tu dois accepter le règlement'
24  errors.value = e
25  return Object.keys(e).length === 0
26}
27
28function submitMission() {
29  if (!validate()) return
30  missions.value.push({ ...missionForm, id: Date.now() })
31  resetForm()
32  submitted.value = true
33  setTimeout(() => { submitted.value = false }, 3000)
34}
35
36function resetForm() {
37  missionForm.name = ''
38  missionForm.destination = 'mars'
39  missionForm.priority = 'normal'
40  missionForm.description = ''
41  missionForm.crewSize = 4
42  missionForm.acceptTerms = false
43  errors.value = {}
44}
45</script>
46
47<template>
48  <div class="mission-control">
49    <h1>Enregistrement de Mission NOVA LAB</h1>
50
51    <div v-if="submitted" class="success">Mission enregistrée avec succès !</div>
52
53    <form @submit.prevent="submitMission" @keyup.escape="resetForm">
54      <div class="field">
55        <label>Nom de la mission</label>
56        <input v-model.trim="missionForm.name" placeholder="Saisis le nom..." />
57        <span class="error" v-if="errors.name">{{ errors.name }}</span>
58      </div>
59
60      <div class="field">
61        <label>Destination</label>
62        <select v-model="missionForm.destination">
63          <option value="mars">Mars</option>
64          <option value="europa">Europe (Jupiter)</option>
65          <option value="titan">Titan (Saturne)</option>
66          <option value="asteroid">Ceinture d'astéroïdes</option>
67        </select>
68      </div>
69
70      <div class="field">
71        <label>Priorité</label>
72        <div class="radio-group">
73          <label><input type="radio" v-model="missionForm.priority" value="low" /> Faible</label>
74          <label><input type="radio" v-model="missionForm.priority" value="normal" /> Normale</label>
75          <label><input type="radio" v-model="missionForm.priority" value="high" /> Élevée</label>
76        </div>
77      </div>
78
79      <div class="field">
80        <label>Taille de l'équipage</label>
81        <input type="number" v-model.number="missionForm.crewSize" min="1" max="20" />
82        <span class="error" v-if="errors.crewSize">{{ errors.crewSize }}</span>
83      </div>
84
85      <div class="field">
86        <label>Description de la mission</label>
87        <textarea v-model.lazy="missionForm.description" rows="3" placeholder="Décris l'objectif de la mission..."></textarea>
88        <span class="error" v-if="errors.description">{{ errors.description }}</span>
89      </div>
90
91      <div class="field">
92        <label><input type="checkbox" v-model="missionForm.acceptTerms" /> J'accepte le règlement NOVA LAB</label>
93        <span class="error" v-if="errors.acceptTerms">{{ errors.acceptTerms }}</span>
94      </div>
95
96      <div class="actions">
97        <button type="submit">Enregistrer la mission</button>
98        <button type="button" @click="resetForm">Effacer (Échap)</button>
99      </div>
100    </form>
101  </div>
102</template>
103
104<style scoped>
105.mission-control { background: #0a0a2e; color: #e0e0ff; padding: 2rem; min-height: 100vh; font-family: monospace; }
106h1 { color: #00d4ff; text-align: center; }
107.success { background: #1a4a1a; color: #00ff88; padding: 1rem; border-radius: 4px; text-align: center; margin: 1rem 0; }
108.field { margin: 1rem 0; }
109label { display: block; margin-bottom: 0.3rem; color: #aaa; }
110input, select, textarea { width: 100%; background: #1a1a4e; border: 1px solid #333; color: white; padding: 0.5rem; border-radius: 4px; box-sizing: border-box; }
111.radio-group { display: flex; gap: 1rem; }
112.radio-group label { display: flex; align-items: center; gap: 0.3rem; }
113.error { color: #ff4444; font-size: 0.8rem; }
114.actions { display: flex; gap: 1rem; margin-top: 1rem; }
115button { background: #00d4ff; color: #0a0a2e; border: none; padding: 0.7rem 1.5rem; cursor: pointer; border-radius: 4px; font-weight: bold; }
116button[type="button"] { background: #333; color: white; }
117</style>

Tu as repéré une erreur dans cette leçon ?