Imprimer
Catégorie : R3.01 - dev. Web côté client (vuejs)
Affichages : 2004

Préambule

L'objectif principal de ce TP est d'implémenter une pseudo boutique en ligne, afin de mettre en oeuvre de façon plus poussée les principes de vuex et de vue-router.  Le scénario à implémenter est le suivant :

NB : pour simplifier l'exercice et notamment la gestion du panier, on part du principe que des boutons ne vont apparaître que si l'utilisateur est authentifié via la page de login.

 

 

 

 

 

0°/ Mise en place

La structuration générale reste la même que pour les TPs précédents. De plus, certains composants vont être réutilisés. La mise en place initiale la plus simple consiste donc à :

 

1°/ Modularisation du store

Comme l'application DrMad aura à terme des fonctionnalités bien séparées, il est important de modulariser le store. Pour cela :

Pour les exercices suivants, il va falloir modifier le module shop afin d'implémenter les fonctionnalités demandées.

 

2°/ Le composant racine de la boutique

<template>
  <div>
    <h1>Boutique</h1>
    <router-view name="shopmain"></router-view>
  </div>
</template>

 

3°/ L'accueil

 

4°/ Authentification

Cette partie a déjà été implémentée dans les TPs précédents. Il suffit donc de reprendre le composant déjà écrit., et en faisant quelques modifications

La première concerne le fichier datasource/controller.js. En effet, dans la solution proposée, la fonction qui recherche un utilisateur dans le fichier source de données data.js se contente de vérifier le login mais pas le mot de passe : il faut également faire cette vérification. Pour ce faire, vous devez :

Remarques :

La deuxième consiste à afficher un dialogue d'erreur si le login/mot de passe n'est pas valide.

Enfin, si l’authentification est correcte, on doit suivre la route /shop/buy.

 

5°/ Acheter des items

L'objectif est de permettre de sélectionner des virus et de les ajouter à un "panier d'achat", en plus ou moins grand nombre. (NB : cela impose de modifier les composant CheckedList.vue créé dans le TP précédent.)

5.1°/ Le composant racine des achats

5.2°/ Les items à acheter

 

 

5.3°/ Le stockage du panier en mémoire

 
{
  items: [{  
    item: {type: Schema.Types.ObjectId, required: true, ref: 'Item'},
    amount: {type: Number, required: true, min:0}, 
  }]
}
  • D'après cette structuration, un panier est un objet contenant un champ items, du type tableau. Chaque élément de celui-ci est un objet avec un champ item, qui est l'id d'un item existant, et un champ amount qui est le nombre de ce type d'item mis dans le panier.

 

  • Le problème de cette solution est que le store ne gère qu'un seul utilisateur courant et un seul panier.
  • Si on change d'utilisateur courant, le panier devient invalide et on doit le remettre à vide. Par conséquent, si on revient à l'utilisateur précédent, son panier est perdu.
  • La solution consiste à reporter toute modification du panier de l'utilisateur courant en BdD et dès qu'on change d'utilisateur, de récupérer son panier en BdD, le tout via des requêtes à l'API.
  • Mais comme ce TP fonctionne avec une source locale de données, il faut pouvoir simuler ces requêtes et "enregistrer" le panier dans celle-ci.
  • Pour ce faire, il suffit de constater que le contenu de data.js est chargé en mémoire. On peut donc tout à fait modifier les objets représentant les utilisateurs, stockés dans le tableau shopuser. Si on connait l'id d'un utilisateur, on peut retrouver son objet dans le tableau, puis récupérer ou lui ajouter/modifier un champ basket représentant son panier. 
  • Si on recharge l'application, ces ajouts seront perdus, mais il restent en mémoire même si on change d'utilisateur courant dans le store.

 

  • La solution à suivre est donc d'ajouter
    • dans controller.js :
      • une méthode pour ajouter/modifier le champ basket d'un utilisateur stocké dans shopuser, en fournissant un objet avec la structuration donnée ci-dessus.
      • une méthode pour récupérer la valeur du champ basket d'un utilisateur stocké dans shopuser.
    • dans shop.service.js : des méthodes qui appellent celles de controller.js
    • dans le store:
      • un champ dans le state (par ex. nommé basket) pour représenter le panier,
      • des mutations+actions permettant mettre à jour ce champ, via les fonctions de service.
      • compte tenu des opérations possibles sur le panier, il faut au moins des mutation+action pour :
        • récupérer le panier existant de l'utilisateur courant via shop.service afin d'initialiser le contenu de basket,
        • ajouter à basket un item avec une certaine quantité (ou augmenter la quantité si l'item est déjà dedans) et mettre à jour en Bdd/source locale via shop.service
        • supprimer totalement un item et mettre à jour en Bdd/source locale via shop.service
        • vider totalement le panier et mettre à jour en Bdd/source locale via shop.service

 

5.4°/ Le composant panier

  • Dans le répertoire components, créer un composant BasketList.vue.
  • Ce composant doit utiliser la version modifié de CheckedList.vue pour :
    • afficher chaque item du panier avec un bouton supprimer après,
    • afficher un bouton "vider le panier" après cette liste,
    • quand l'événement clic sur le bouton d'item est reçu, supprimer cet item du panier de l'utilisateur.
    • quand l'événement clic sur le bouton après la liste est reçu, supprimer le contenu du panier.
  • Lors de son chargement, BasketList doit demander au store de récupérer le panier de l'utilisateur courant.
  • Cela permet ensuite d'afficher les items du panier de l'utilisateur courant, stocké dans le store (cf. 4.3)
  • De plus, le composant doit afficher un bouton "Acheter" permettant de valider la commande. Quand on clique sur ce bouton, il faut normalement envoyer une requête à l'API (avec l'id utilisateur comme donnée) et recevoir en retour un objet représentant la commande. Dans la BdD, cette commande est représentée par le schéma OrderSchema suivant  :
{
  items: [{    
    item: {
      name: {type: String, required: true},
      description: {type: String},
      price: {type: Number, required: true, min: 0}, 
      promotion: [{
        discount: {type: Number, required: true, min: 0}, 
        amount: {type: Number, required: true, min: 0}, 
      }],
      object: {type: String, required: true}
    },
    amount: {type: Number, required: true, min:0}, 
  }],
  date: {type: Date, default: Date.now},
  total: {type: Number, required: true, min:0}, 
  status: {type: String, require: true, enum: ['waiting_payment', 'finalized', 'cancelled']},
  uuid: {type: String, required: true }, // value MUST BE an unique uuid v4
}
  • Dans ce TP, la solution la plus simple consiste à créer un objet basé sur cette structure à partir de ce qu'il y a stocké dans le panier.
  • ATTENTION :
    • il ne faut pas initialiser les champs date, total, status et uuid. C'est l'API/source locale qui va leur donner une valeur.
    • les objets stockés dans items ne sont pas des copies conformes des objets items que l'on a sélectionné. Il y a certains champs qui n'existent pas, tels que link, stock, ...

 

  • Comme pour le panier, il faut simuler les requêtes à l'API pour stocker cette nouvelle commande en modifiant les données de la source locale. Il faut donc ajouter des méthodes dans 2 fichiers :
    • dans controller.js :
      • une méthode pour ajouter/modifier le champ orders d'un utilisateur stocké dans shopuser,en fournissant un objet avec la structuration donnée ci-dessus.
      • cette méthode doit  d'abord ajouter à cet objet les champs manquants de la commande : le total, promotions comprises., l'uuid, (tiré aléatoirement), le status égal à "waiting_payment" et la date avec la date courante. Ensuite, elle ajoute cet objet au tableau orders. Enfin, elle renvoie une réponse dont le champ data est un objet {uuid: ...}.
    • dans shop.service.js :
      • une méthode qui appelle la fonction de controller.js et renvoie la valeur retournée (= un uuid si la commande est correcte).

 

  • Contrairement à la gestion du panier, il n'y a pas besoin de stocker la commande dans le store. C'est pourquoi, il n'y a pas besoin d'ajouter des mutations+actions dans shop.js.
  • Le composant BasketList doit donc directement importer shop.service afin d'accéder à la méthode pour créer une nouvelle commande. 
  • Quand on clique sur le bouton "Acheter", il faut donc :
    • créer l'objet représentant la commande à partir du contenu du panier,
    • appeler la fonction de service et récupérer la valeur retournée
    • si la valeur retournée est un uuid, vider le panier puis suivre la route /shop/pay/ avec en paramètre l'uuid.

 

  • A noter que le tableau shopuser fourni dans data.js contient déjà un utilisateur drmad avec un champ orders contenant une commande.

 

6°/ Payer une commande

  • Dans le répertoire views, créer un composant ShopPay.vue.
  • Ce composant prend en props l'id d'une commande et affiche :
    • un champ texte pour saisir l'id d'une commande. Si la props mentionnée ci-dessus est définie, alors ce champ de saisie est initialisé avec la valeur de la props.
    • un bouton "Payer". Quand on clique sur celui-ci, et si l'id fourni correspond bien à une commande existante de l'utilisateur courant, cette commande passe à l'état "finalized". Dans ce cas, on suit la route permettant d'afficher les commandes de l'utilisateur courant.

NB : dans le TP suivant, la gestion d'un compte bancaire sera ajoutée, ce qui permettra de vérifier si le montant de la commande correspond effectivement à une transaction bancaire de paiement.

 

  • Comme avec les autres opérations, il faut mimer les appels à l'API en modifiant la source locale. Il faut donc créer les fonctions de service appelées par le store et modifier controller.js afin de vérifier qu'une commande avec un id donné existe bien pour l'utilisateur courant et si c'est le cas, modifier son status à "finalized"
  • Comme avec le panier et la création d'une commande, on remarque qu'il n'y a pas besoin de stocker la commande dans le store, donc aucune raison de créer une mutation+action.

  

7°/ Commandes passées

  • Dans le répertoire views, créer un composant ShopOrders.vue.
  • Ce composant affiche la liste des commandes de l'utilisateur courant, que l'on récupère auprès de l'API/source locale (cf. remarques ci-dessous)
  • Chaque élément de la liste affiche son montant, son état (c.a.d. champ status), et est suivi par des boutons "Payer" et "Annuler" si la commande est dans l'état "waiting_payment".
  • Quand on clique sur "Payer", on suit la route /shop/pay/:idcmd, en mettant comme valeur d'idcmd l'id de la commande que l'on veut payer.
  • Quand on clique sur "Annuler", la commande doit passer à l'état "cancelled".

 

  • Là encore, il faut mimer les appels l'API en écrivant les fonctions de service et en modifiant controller.js. Il faut donc ajouter des méthodes qui permettent de récupérer les commandes d'un utilisateur et changer le statut d'une commande avec la valeur "cancelled"
  • Comme pour le panier et le paiement, il n'est normalement pas nécessaire de stocker les commandes dans le store puisqu'il n'y a que ce composant qui les utilise.
  • Cependant, comme elles sont liées à l'utilisateur courant et que ce dernier EST dans le store, il est pertinent de stocker les commandes dans cet utilisateurs courant, comme c'est fait en BdD/source locale.
  • Cela implique qu'il faut ajouter dans le store :
    • une action qui utilise une fonction de service pour récupérer les commandes de l'utilisateur courant,
    • une action qui utilise une fonction de service pour mettre à jour le statut d'une commande donnée à "cancelled"
    • une mutation qui met à jour le champ shopUser.orders 
    • une mutation qui met à jour le statut d'une commande donnée

 

  • ATTENTION sur la récupération des commandes: quand on récupère les données d'un utilisateur lors du login, il n'y a pas de champ orders. Dans ce cas, si on écrit quelque chose du genre state.shopUser.orders = ... alors l'ajout du champ ne sera pas détecté par vuejs. Cela veut dire que le composant ShopOrders ne sera pas actualisé. Pour régler ce problème, il y a essentiellement 2 solutions :
    1. quand on met à jour shopUser à partir d'un objet, on ajoute au préalable à cet objet un champ orders.
    2. on utilise Vue.set(...) pour indiquer à vuejs d'observer immédiatement le nouveau champ.