Projet : la Librairie en application Vue
Cours · Vue : construire une application web (école d'ingénieur), chapitre 12 · école d'ingénieur, informatique · le début du chapitre, la suite sur Adloun
Au livre HTML, la Librairie du Campus était une suite de pages structurées ; au livre CSS, elle a pris sa charte et sa mise en page ; au livre JavaScript, elle est devenue interactive, à la main. Dans ce livre, elle a été reconstruite en composants, puis dotée d'un routeur, d'un état partagé, de tests. Il lui manque une dernière page, la plus importante pour une librairie : celle où l'on commande.
Ce chapitre termine l'application. On écrit la page de commande (validation, envoi au serveur, refus, confirmation), on fait le tour de ses états (chargement, erreur, vide), on la vérifie de trois façons (tests de composants, tests de bout en bout, revue d'accessibilité), puis on la construit pour la production. Le code complet est dans code/ch12/librairie/ : c'est le point de départ du livre suivant, où le serveur d'essai sera remplacé par un vrai serveur Java.
Objectifs du chapitre
- Assembler une application complète à partir des outils du livre : composants, composables, routeur, stores.
- Écrire un formulaire qui guide la saisie, envoie ses données au serveur et traite chacune de ses réponses.
- Gérer et vérifier les états de chaque page : chargement, erreur, vide, succès.
- Vérifier l'application à trois niveaux : composants, parcours complets, accessibilité.
- Relire l'application avec un œil critique et savoir ce qui viendrait ensuite.
12.1 Le cahier des charges
L'application finale doit permettre à un étudiant :
- de parcourir le catalogue, de le filtrer et de le trier, la recherche et le tri restant dans l'adresse (chapitre 9) ;
- d'ouvrir la fiche d'un livre par son adresse
/livres/:isbn, avec le stock du moment ; - de remplir un panier qui survit à un rechargement et se partage entre les onglets (chapitres 8 et 10) ;
- de commander : choisir le mode de retrait, donner son nom et son courriel, envoyer la commande au serveur, et recevoir un numéro de commande, ou une explication claire du refus ;
- de tout faire au clavier et avec un lecteur d'écran, sur un téléphone comme sur un grand écran ;
- de comprendre ce qui se passe quand le serveur est lent ou en panne.
Le serveur est celui du livre JavaScript, outils/serveur.mjs. Pour une commande, il attend POST /api/commandes avec un corps JSON :
{
"lignes": [{ "isbn": "9782956300069", "quantite": 2 }],
"retrait": "campus",
"nom": "Ada Lovelace",
"courriel": "ada@example.org"
}
Il répond 201 et { "numero": 1001, "total": 17.8 } si tout va bien ; 400 et la liste des erreurs ({ "erreurs": [ … ] }) s'il refuse (stock insuffisant, courriel invalide…) ; 500 quand on le lance avec --panne. Il recalcule le total lui-même : le prix affiché par la page n'est qu'une information, le serveur seul fait foi.
12.2 L'architecture
Chaque route mène à une page (src/pages/), chargée à la demande sauf le catalogue et la page introuvable. Les pages se construisent avec les composants de src/components/ et s'appuient sur deux stores : le catalogue, chargé une fois, et le panier, conservé dans localStorage. La fiche et la commande s'adressent directement au serveur, par deux composables. Le projet compte :
src/
main.js, App.vue, router.js démarrage, gabarit commun, routes et gardes
pages/ 5 pages une par route
components/ 7 composants en-tête, pied, carte, liste, recherche, tri, panier
composables/ 4 composables useFetch, useEnvoi, useStockage, useRaccourci
stores/ 2 stores catalogue, panier
utils/ format.js prix en euros, texte sans accents
assets/ style.css la feuille du livre CSS
tests/ 7 fichiers, 20 tests (Vitest)
e2e/ 2 fichiers, 4 tests (Playwright)
deploiement/ nginx.conf
12.3 La page de commande
12.3.1 Y arriver
La page du panier propose désormais de passer la commande, et la route /commande est gardée : sans livre, inutile d'y entrer.
{
path: "/commande",
name: "commande",
component: () => import("./pages/PageCommande.vue"),
meta: { titre: "Passer la commande" },
// Pas de commande sans livre : retour au panier, qui explique pourquoi.
beforeEnter: () => {
if (usePanierStore().estVide) return { name: "panier", query: { raison: "vide" } };
},
},
La page du panier explique le renvoi quand la requête contient raison=vide, et n'affiche le lien vers la commande que si le panier contient un livre :
<template>
<section aria-labelledby="titre-panier">
<h1
id="titre-panier"
tabindex="-1"
>
Votre panier
</h1>
<p
v-if="route.query.raison === 'vide' && panier.estVide"
class="alerte"
>
Pour commander, ajoutez d’abord un livre à votre panier.
</p>
<PanierContenu />
<p v-if="!panier.estVide">
<RouterLink
class="bouton"
:to="{ name: 'commande' }"
>
Passer la commande
</RouterLink>
</p>
<p>
<RouterLink :to="{ name: 'catalogue' }">
Continuer mes achats
</RouterLink>
</p>
</section>
</template>
12.3.2 Envoyer : le composable useEnvoi
L'envoi est le pendant de useFetch : il expose l'état de la requête (en cours, erreurs, réponse) et une fonction qui la lance.
import { ref } from "vue";
/**
* Envoie du JSON en POST et expose l'état de l'envoi.
* Le serveur de la Librairie répond 201 + { numero, total },
* ou 400 + { erreurs: [ … ] }, ou 500 + { erreur }.
*/
export function useEnvoi(url) {
const envoi = ref(false);
const erreurs = ref([]);
const reponse = ref(null);
async function envoyer(corps) {
envoi.value = true;
erreurs.value = [];
reponse.value = null;
try {
const r = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(corps),
});
const donnees = await r.json().catch(() => ({})); // corps vide ou illisible
if (r.ok) reponse.value = donnees;
else erreurs.value = donnees.erreurs ?? [donnees.erreur ?? `Le serveur a répondu ${r.status}.`];
} catch {
erreurs.value = ["Le serveur ne répond pas. Vérifiez votre connexion, puis réessayez."];
} finally {
envoi.value = false;
}
return reponse.value !== null;
}
return { envoi, erreurs, reponse, envoyer };
}
Toutes les issues sont ramenées à une seule forme, une liste de messages : les erreurs détaillées d'un refus (400), le message d'une panne (500), un message par défaut pour un code inattendu, et un message pour un serveur injoignable (fetch rejette sa promesse). La page n'a plus qu'à afficher la liste.
La suite du cours est sur Adloun
Le chapitre en entier, formules et figures comprises, avec le tuteur, les fiches, les QCM et les exercices.
Lire la suite du coursEn accès libre, sans inscription.