Skip to content

19. Verbesserungen am Vue.js-Client

19.1. Einführung

Wir werden das [vuejs-21]-Projekt mit dem Entwicklungsserver testen. Daher benötigen wir erneut den Server, um CORS-Header zu senden. Die [config.json]-Datei für Version 14 des Steuerberechnungsservers muss daher diese Header zulassen:

Image

Das [vuejs-21]-Projekt wird zunächst durch Duplizieren des [vuejs-20]-Projekts erstellt. Anschließend wird es angepasst [3].

Es erscheinen neue Dateien:

  • [session.js]: exportiert ein [session]-Objekt, das Informationen über die aktuelle Sitzung kapselt;
  • [pluginSession]: stellt das vorherige [session]-Objekt in der Eigenschaft [$session] der Ansichten zur Verfügung;
  • [NotFound.vue]: eine neue Ansicht, die angezeigt wird, wenn der Benutzer manuell eine URL aufruft, die nicht existiert;

Die folgenden Dateien werden geändert:

  • [main.js]: initialisiert die aktuelle Sitzung und stellt sie wieder her, wenn der Benutzer manuell eine URL eingibt;
  • [router.js]: Es werden Steuerelemente hinzugefügt, um vom Benutzer eingegebene URLs zu verarbeiten;
  • [store.js]: Es wird eine neue Mutation hinzugefügt;
  • [config.js]: Es wird eine neue Konfiguration hinzugefügt;
  • verschiedene Ansichten, hauptsächlich um die aktuelle Sitzung an wichtigen Punkten im Lebenszyklus der Anwendung zu speichern. Die Sitzung wird dann wiederhergestellt, sobald der Benutzer manuell URLs eingibt;

19.2. Der [Vuex]-Store

Das Skript [./store] entwickelt sich wie folgt:


// plugin Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
 
// store Vuex
const store = new Vuex.Store({
  state: {
    // le tableau des simulations
    simulations: [],
    // le n° de la dernière simulation
    idSimulation: 0
  },
  mutations: {
    // suppression ligne n° index
    deleteSimulation(state, index) {
      ...
    },
    // ajout d'une simulation
    addSimulation(state, simulation) {
      ...
    },
    // nettoyage state
    clear(state) {
      // plus de simulations
      state.simulations = [];
      // la numérotation des simulations repart de 0
      state.idSimulation = 0;
    }
  }
});
// export de l'objet [store]
export default store;
  • Zeilen 24–29: Die Zuweisung [clear] löscht die Liste der gespeicherten Simulationen und setzt die Nummer der letzten Simulation auf 0 zurück.

19.3. Die Sitzung

Eine Sitzung ist erforderlich, da das Skript [main.js] erneut ausgeführt wird, wenn der Benutzer eine URL in die Adressleiste des Browsers eingibt. Dieses Skript enthält jedoch die folgende Anweisung:


// store Vuex
import store from './store'

Diese Anweisung importiert die folgende Datei [./store]:


// plugin Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
 
// store Vuex
const store = new Vuex.Store({
  state: {
    // le tableau des simulations
    simulations: [],
    // le n° de la dernière simulation
    idSimulation: 0
  },
  mutations: {
    ...
  }
});
// export de l'objet [store]
export default store;

Wie wir in den Zeilen 7–13 sehen können, importieren wir ein leeres Array von Simulationen. Wenn wir also Simulationen hatten, bevor der Benutzer eine URL in die Adressleiste des Browsers eingegeben hat, haben wir danach keine mehr. Die Idee ist:

  • eine Sitzung zu verwenden, in der die Informationen gespeichert werden, die Sie behalten möchten, falls der Benutzer URLs manuell eingibt;
  • diese an wichtigen Punkten in der Anwendung zu speichern;
  • sie in [main.js] wiederherzustellen, das immer ausgeführt wird, wenn eine URL manuell eingegeben wird;

Das Skript [./session] sieht wie folgt aus:


// on importe le store Vuex
import store from './store'
// on importe la configuration
import config from './config';
 
// l'objet [session]
const session = {
  // session démarrée
  started: false,
  // authentification
  authenticated: false,
  // heure de sauvegarde
  saveTime: "",
  // couche [métier]
  métier: null,
  // état Vuex
  state: null,
 
  // sauvegarde de la session dans une chaîne jSON
  save() {
    // on ajoute à la session quelques proprités
    this.saveTime = Date.now();
    this.state = store.state;
    // on la transforme en jSON
    const json = JSON.stringify(this);
    // on la stocke sur le navigateur
    localStorage.setItem("session", json);
    // eslint-disable-next-line no-console
    console.log("session save", json);
  },
 
  // restauration de la session
  restore() {
    // on récupère la session jSON à partir du navigateur
    const json = localStorage.getItem("session")
    // si on a récupéré qq chose
    if (json) {
      // on restaure toutes les clés de la session
      const restore = JSON.parse(json);
      for (var key in restore) {
        if (restore.hasOwnProperty(key)) {
          this[key] = restore[key];
        }
      }
      // si on a dépassé une certaine durée d'inactivité depuis le début de la session, on repart de zéro
      let durée = Date.now() - this.saveTime;
      if (durée > config.duréeSession) {
        // on vide la session - elle sera également sauvegardée
        session.clear();
      } else {
        // on régénère le store Vuex
        store.replaceState(JSON.parse(JSON.stringify(this.state)));
      }
    }
    // eslint-disable-next-line no-console
    console.log("session restore", this);
  },
 
    // on nettoie la session
  clear() {
    // eslint-disable-next-line no-console
    console.log("session clear");
    // raz de certains champs de la session
    this.authenticated = false;
    this.saveTime = "";
    this.started = false;
    if (this.métier) {
      // on réinitialise le champ [taxAdminData]
      this.métier.taxAdminData = null;
    }
    // le store Vuex est nettoyé également
    store.commit("clear");
    // on sauvegarde la nouvelle session
    this.save();
  },
}
 
// export de l'objet [session]
export default session;

Kommentare

  • Zeile 2: Die Session kapselt auch den [Vuex]-Store (Liste der Simulationen, ID der zuletzt durchgeführten Simulation);
  • Zeilen 7–17: Von der Sitzung gespeicherte Informationen:
    • [started]: Gibt an, ob die JSON-Sitzung mit dem Server gestartet wurde oder nicht;
    • [authenticated]: ob sich der Benutzer authentifiziert hat oder nicht;
    • [saveTime]: das Datum der letzten Speicherung in Millisekunden;
    • [business]: ein Verweis auf die [business]-Ebene. Diese enthält die [taxAdminData]-Daten, die zur Berechnung der Steuer verwendet werden;
    • [state]: der Status des [Vuex]-Speichers (Liste der Simulationen, Nummer der zuletzt durchgeführten Simulation);
  • Zeilen 20–30: Die Methode [save] speichert die Sitzung lokal im Browser, auf dem die Anwendung ausgeführt wird;
    • Zeile 22: Der Zeitpunkt der Speicherung wird aufgezeichnet;
    • Zeile 23: Der [state] des [Vuex]-Speichers wird abgerufen;
    • Zeile 25: Die JSON-Zeichenkette der Sitzung wird erstellt;
    • Zeile 27: Sie wird lokal im Browser unter dem Schlüssel [session] gespeichert;
  • Zeilen 33–57: Die Methode [restore] stellt eine Sitzung aus ihrer lokalen Speicherung im Browser wieder her;
    • Zeile 35: Abrufen der lokalen JSON-Sicherung;
    • Zeile 37: falls etwas abgerufen wurde;
    • Zeilen 39–44: Das [session]-Objekt wird rekonstruiert;
    • Zeile 46: Die seit der letzten Sicherung verstrichene Zeit wird berechnet;
    • Zeilen 47–50: Wenn diese Dauer einen in der Konfiguration festgelegten Wert [config.sessionDuration] überschreitet, wird die Sitzung zurückgesetzt (Zeile 49) und zu diesem Zeitpunkt gespeichert;
    • Zeile 52: andernfalls wird das [state]-Attribut des [Vuex]-Speichers neu generiert;
  • Zeilen 60–75: Die Methode [clear] setzt die Sitzung zurück;
    • Zeilen 64–70: Die Sitzungseigenschaften werden auf ihre Ausgangswerte zurückgesetzt;
    • Zeile 72: ebenso wie der [Vuex]-Store;
    • Zeile 74: Die neue Sitzung wird gespeichert;

19.4. Die Konfigurationsdatei [config]

Die Datei [./config] entwickelt sich wie folgt:


// utilisation de la bibliothèque [axios]
const axios = require('axios');
// timeout des requêtes HTTP
axios.defaults.timeout = 2000;
...
 
// export de la configuration
export default {
  // objet [axios]
  axios: axios,
  // délai maximal d'inactivité de la session : 5 mn = 300 s = 300000 ms
  duréeSession: 300000
}
  • Zeile 12: Wir verwalten die Anwendungssitzung ähnlich wie eine Websitzung. Hier legen wir eine maximale Inaktivitätsdauer von 5 Minuten fest;

19.5. Das [pluginSession]-Plugin

Wie schon oft zuvor ermöglicht das [pluginSession]-Plugin den Views den Zugriff auf die Sitzung über die Eigenschaft [this.$session]:


export default {
  install(Vue, session) {
    // ajoute une propriété [$session] à la classe vue
    Object.defineProperty(Vue.prototype, '$session', {
      // lorsque Vue.$session est référencé, on rend le 2ième paramètre [session]
      get: () => session,
    })
  }
}

19.6. Das Hauptskript [main]

Das Hauptskript [./main.js] entwickelt sich wie folgt:


// log de démarrage
// eslint-disable-next-line no-console
console.log("main started");
 
// imports
import Vue from 'vue'
 
...

// instanciation couche [métier]
import Métier from './couches/Métier';
const métier = new Métier();
 
// plugin [métier]
import pluginMétier from './plugins/pluginMétier'
Vue.use(pluginMétier, métier)
 
// store Vuex
import store from './store'
 
// session
import session from './session';
import pluginSession from './plugins/pluginSession'
Vue.use(pluginSession, session)
 
// on restore la session avant de redémarrer
session.restore();
 
// on restaure la couche [métier]
if (session.métier && session.métier.taxAdminData) {
  métier.setTaxAdminData(session.métier.taxAdminData);
}
 
// démarrage de l'UI
new Vue({
  el: '#app',
  // le routeur
  router: router,
  // le store Vuex
  store: store,
  // la vue principale
  render: h => h(Main),
})
 
// log de fin
// eslint-disable-next-line no-console
console.log("main terminated, session=", session);
  • Zeile 19: Importiere die Sitzung;
  • Zeile 20: Importiere das Plugin;
  • Zeile 21: Das Plugin [pluginSession] wird in [Vue] integriert. Nach dieser Anweisung steht die Sitzung in allen Ansichten über das Attribut [$session] zur Verfügung;
  • Zeile 27: Die Sitzung wird wiederhergestellt. Die in Zeile 11 importierte Sitzung wird dann mit dem Inhalt ihrer letzten Speicherung initialisiert;
  • Nach Zeile 16 verfügen die Ansichten über eine in Zeile 12 initialisierte [$métier]-Eigenschaft. Diese Eigenschaft enthält nicht die [taxAdminData]-Informationen, die zur Berechnung der Steuer verwendet werden;
  • Zeilen 30–32: Wenn die soeben durchgeführte Wiederherstellung die Eigenschaft [session.métier.taxAdminData] wiederhergestellt hat, wird die Eigenschaft [$métier] der Ansichten mit diesem Wert initialisiert;

19.7. Die Routing-Datei [router]

Die Routing-Datei [./router] entwickelt sich wie folgt:


// imports
import Vue from 'vue'
import VueRouter from 'vue-router'
// les vues
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'
import NotFound from './views/NotFound'
// la session
import session from './session'
 
// plugin de routage
Vue.use(VueRouter)
 
// les routes de l'application
const routes = [
  // authentification
  { path: '/', name: 'authentification', component: Authentification },
  { path: '/authentification', name: 'authentification', component: Authentification },
  // calcul de l'impôt
  {
    path: '/calcul-impot', name: 'calculImpot', component: CalculImpot,
    meta: { authenticated: true }
  },
  // liste des simulations
  {
    path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations,
    meta: { authenticated: true }
  },
  // fin de session
  {
    path: '/fin-session', name: 'finSession'
  },
  // page inconnue
  {
    path: '*', name: 'notFound', component: NotFound,
  },
]
 
// le routeur
const router = new VueRouter({
  // les routes
  routes,
  // le mode d'affichage des URL
  mode: 'history',
  // l'URL de base de l'application
  base: '/client-vuejs-impot/'
})
 
// vérification des routes
router.beforeEach((to, from, next) => {
  // eslint-disable-next-line no-console
  console.log("router to=", to, "from=", from);
  // route réservée aux utilisateurs authentifiés ?
  if (to.meta.authenticated && !session.authenticated) {
    next({
      // on passe à l'authentification
      name: 'authentification',
    })
    // retour à la boucle événementielle
    return;
  }
  // cas particulier de la fin de session
  if (to.name === "finSession") {
    // on nettoie la session
    session.clear();
    // on va sur la vue [authentification]
    next({
      name: 'authentification',
    })
    // retour à la boucle événementielle
    return;
  }
  // autres cas - vue suivante normale du routage
  next();
})
 
// export du router
export default router

Kommentare

  • Zeilen 16–38: Einige Routen wurden um zusätzliche Informationen ergänzt;
  • Zeile 19: Es wurde eine neue Route zur Ansicht [Authentication] erstellt;
  • Zeilen 21–24: Die Route, die zur Ansicht [TaxCalculation] führt, verfügt nun über eine [meta]-Eigenschaft (dieser Name ist erforderlich). Der Inhalt dieses Objekts kann beliebig sein und wird vom Entwickler festgelegt;
  • Zeile 23: Wir fügen die Eigenschaft [authenticated] zu [meta] hinzu (dieser Name kann beliebig gewählt werden). Das bedeutet, dass der Benutzer für den Zugriff auf die Ansicht [TaxCalculation] authentifiziert sein muss;
  • Zeilen 26–29: Wir verfahren ebenso für die Route, die zur Ansicht [ListeSimulations] führt. Auch hier muss der Benutzer authentifiziert sein;
  • Die Eigenschaft [meta.authenticated] ermöglicht es uns zu überprüfen, ob ein Benutzer, der die URLs für die Ansichten [CalculImpot] und [ListeSimulations] manuell eingibt, nicht darauf zugreifen kann, wenn er nicht authentifiziert ist;
  • Zeilen 51–76: Die Methode [beforeEach] wird ausgeführt, bevor eine Ansicht weitergeleitet wird. Dies ist der richtige Zeitpunkt, um Überprüfungen durchzuführen;
    • [to]: die nächste Route, wenn nichts unternommen wird;
    • [from]: die zuletzt angezeigte Route;
    • [next]: Funktion zum Ändern der nächsten angezeigten Route;
  • Zeile 55: Wir prüfen, ob die nächste Route eine Authentifizierung des Benutzers erfordert;
  • Zeilen 56–59: Ist dies der Fall und der Benutzer nicht authentifiziert, ändern wir die nächste Route zur Ansicht [Authentication];
  • Zeilen 64–73: Behandlung des Sonderfalls der Route [endSession] aus den Zeilen 30–32. Diese Route hat keine zugehörige Ansicht;
    • Zeile 66: Die Sitzung wird auf ihren Ausgangswert zurückgesetzt;
    • Zeilen 68–70: Die Ansicht [Authentication] wird als nächste Ansicht festgelegt;
  • Zeile 75: Wenn keiner der beiden vorherigen Fälle zutrifft, fahren wir einfach mit der in der Routing-Datei angegebenen Route fort;
  • Zeilen 35–37: Eine [NotFound]-Ansicht wird bereitgestellt, wenn die vom Benutzer eingegebene Route mit keiner bekannten Route übereinstimmt. Diese Ansicht wird in Zeile 8 importiert. Routen werden in der Reihenfolge der Routing-Datei überprüft. Wenn wir also Zeile 36 erreichen, bedeutet dies, dass die angeforderte Route keine der Routen in den Zeilen 18–33 ist;

19.8. Die Ansicht [NotFound]

Die Ansicht [NotFound] wird angezeigt, wenn die vom Benutzer eingegebene Route mit keiner bekannten Route übereinstimmt:

Image

Der Code der Ansicht lautet wie folgt:


<!-- définition HTML de la vue -->
<template>
  <!-- mise en page -->
  <Layout :left="true" :right="true">
    <!-- alerte dans la colonne de droite -->
    <template slot="right">
      <!-- message sur fond jaune -->
      <b-alert show variant="danger" align="center">
        <h4>Cette page n'existe pas</h4>
      </b-alert>
    </template>
    <!-- menu de navigation dans la colonne de gauche -->
    <Menu slot="left" :options="options" />
  </Layout>
</template>
 
<script>
// imports
import Layout from "./Layout";
import Menu from "./Menu";
export default {
  // composants
  components: {
    Layout,
    Menu
  },
  // état interne du composant
  data() {
    return {
      // options du menu de navigation
      options: [
        {
          text: "Authentification",
          path: "/"
        }
      ]
    };
  },
  // cycle de vie
  created() {
    // eslint-disable-next-line
    console.log("NotFound created");
    // on regarde quelles options de menu offrir
    if (this.$session.authenticated && this.$métier.taxAdminData) {
      // l'utilisateur peut faire des simulations
      Array.prototype.push.apply(this.options, [
        {
          text: "Calcul de l'impôt",
          path: "/calcul-impot"
        },
        {
          text: "Liste des simulations",
          path: "/liste-des-simulations"
        }
      ]);
    }
  }
};
</script>

Kommentare

  • Zeile 4: Es werden beide Spalten der weitergeleiteten Ansichten verwendet;
  • Zeilen 6–11: eine Fehlermeldung;
  • Zeile 13: Das Navigationsmenü nimmt die linke Spalte ein;
  • Zeilen 31–36: Die Standardoptionen des Menüs;
  • Zeilen 40–57: Code, der beim Erstellen der Ansicht ausgeführt wird;
  • Zeile 44: prüft, ob der Benutzer Simulationen ausführen darf;
  • Zeilen 45–55: Ist dies der Fall, werden dem Navigationsmenü zwei Optionen hinzugefügt – solche, die eine Authentifizierung erfordern, und eine operative [Geschäfts-]Ebene (Zeilen 46–55);

19.9. Die Ansicht [Authentifizierung]

Die Ansicht [Authentifizierung] entwickelt sich wie folgt:


<!-- définition HTML de la vue -->
<template>
  <Layout :left="false" :right="true">
    ...
  </Layout>
</template>
 
<!-- dynamique de la vue -->
<script>
import Layout from "./Layout";
export default {
  // component status
  data() {
    return {
      // user
      user: "",
      // password
      password: "",
      // controls the display of an error msg
      showError: false,
      // the error message
      message: ""
    };
  },
 
  // components used
  components: {
    Layout
  },
 
  // calculated properties
  computed: {
    // valid entries
    valid() {
      return this.user && this.password && this.$session.started;
    }
  },
 
  // event managers
  methods: {
    // ----------- authentication
    async login() {
      try {
        // start waiting
        this.$emit("loading", true);
        // you are not yet authenticated
        this.$session.authenticated = false;
        // blocking server authentication
        const response = await this.$dao.authentifierUtilisateur(
          this.user,
          this.password
        );
        // end of loading
        this.$emit("loading", false);
        // server response analysis
        if (response.état != 200) {
          // error is displayed
          this.message = response.réponse;
          this.showError = true;
          // return to event loop
          return;
        }
        // no error
        this.showError = false;
        // you are authenticated
        this.$session.authenticated = true;
        // --------- we now request data from the tax authorities
        // initially, no data
        this.$métier.setTaxAdminData(null);
        // start waiting
        this.$emit("loading", true);
        // blocking request to the server
        const response2 = await this.$dao.getAdminData();
        // end of loading
        this.$emit("loading", false);
        // response analysis
        if (response2.état != 1000) {
          // error is displayed
          this.message = response2.réponse;
          this.showError = true;
          // return to event loop
          return;
        }
        // no error
        this.showError = false;
        // the received data is stored in the [business] layer
        this.$métier.setTaxAdminData(response2.réponse);
        // we can move on to tax calculation
        this.$router.push({ name: "calculImpot" });
      } catch (error) {
        // the error is traced back to the main component
        this.$emit("error", error);
      } finally {
        // maj session
        this.$session.métier = this.$métier;
        // save the session
        this.$session.save();
      }
    }
  },
  // life cycle: the component has just been created
  created() {
    // eslint-disable-next-line
    console.log("Authentification created");
    // can the user run simulations?
    if (
      this.$session.started &&
      this.$session.authenticated &&
      this.$métier.taxAdminData
    ) {
      // then the user can run simulations
      this.$router.push({ name: "calculImpot" });
      // return to event loop
      return;
    }
    // if the jSON session has already been started, it will not be restarted
    if (!this.$session.started) {
      // start waiting
      this.$emit("loading", true);
      // initialize the session with the server - asynchronous request
      // we use the promise rendered by the [dao] layer methods
      this.$dao
        // initialize a jSON session
        .initSession()
        // we got the answer
        .then(response => {
          // end waiting
          this.$emit("loading", false);
          // response analysis
          if (response.état != 700) {
            // error is displayed
            this.message = response.réponse;
            this.showError = true;
            // return to event loop
            return;
          }
          // the session has started
          this.$session.started = true;
        })
        // in case of error
        .catch(error => {
          // the error is traced back to the [Main] view
          this.$emit("error", error);
        })
        // in all cases
        .finally(() => {
          // save the session
          this.$session.save();
        });
    }
  }
};
</script>

Kommentare

  • Die Anweisungen, die die in dieser Version des [Vue.js]-Clients eingeführte Session verwenden, sind gelb hervorgehoben;
  • Zeilen 97, 148: Am Ende der Methoden [login, created] wird die Sitzung unabhängig vom Ergebnis der innerhalb dieser Methoden auftretenden HTTP-Anfragen gespeichert (in beiden Fällen die [finally]-Klausel);
  • Die Methode [created] in den Zeilen 102–150 wird jedes Mal ausgeführt, wenn die Ansicht [Authentication] erstellt wird. Wenn der Benutzer die URL der Ansicht eingegeben hat, gibt uns die Sitzung vor, was zu tun ist;
  • Zeilen 106–115: Wenn die JSON-Sitzung gestartet, der Benutzer authentifiziert und die Daten [this.$métier.taxAdminData] initialisiert sind, kann der Benutzer direkt zum Formular für die Steuerberechnung wechseln (Zeile 112);
  • Zeile 117: Die Methode [created] wurde in der vorherigen Version verwendet, um eine JSON-Sitzung mit dem Server zu initialisieren. Dieser Schritt ist überflüssig, wenn er bereits durchgeführt wurde;
  • Zeilen 42–66: die Authentifizierungsmethode;
  • Zeile 66: Wenn die Authentifizierung erfolgreich ist, wird dies in der Sitzung vermerkt;
  • Zeilen 67–92: die Anfrage an den Server nach Steuerverwaltungsdaten [taxAdminData];
  • Zeile 95: Am Ende dieser Phase wird die Eigenschaft [business] der Sitzung aktualisiert, unabhängig davon, ob der Vorgang erfolgreich war oder nicht;

19.10. Die Ansicht [CalculImpot]

Der Code für die Ansicht [CalculImpot] entwickelt sich wie folgt:


<!-- définition HTML de la vue -->
<template>
  ...
</template>

<script>
// imports
import FormCalculImpot from "./FormCalculImpot";
import Menu from "./Menu";
import Layout from "./Layout";
 
export default {
  // état interne
  data() {
    return {
      // options du menu
      options: [
        {
          text: "Liste des simulations",
          path: "/liste-des-simulations"
        },
        {
          text: "Fin de session",
          path: "/fin-session"
        }
      ],
      // résultat du calcul de l'impôt
      résultat: "",
      résultatObtenu: false
    };
  },
  // composants utilisés
  components: {
    Layout,
    FormCalculImpot,
    Menu
  },
  // méthodes de gestion des évts
  methods: {
    // résultat du calcul de l'impôt
    handleResultatObtenu(résultat) {
      // on construit le résultat en chaîne HTML
      ...
      // une simulation de +
      this.$store.commit("addSimulation", résultat);
      // on sauvegarde la session
      this.$session.save();
    }
  },
  // cycle de vie
  created() {
    // eslint-disable-next-line
    console.log("CalculImpot created");
  }
};
</script>

Kommentare

  • Zeile 45: Die berechnete Simulation wird dem [Vuex]-Store hinzugefügt. Dies wirkt sich auf die Session aus, die die [state]-Eigenschaft des Stores umfasst. Daher speichern wir die Session (Zeile 47);
  • Zeile 51: Wir erstellen eine [created]-Methode, um die Erstellung von Views in den Logs zu verfolgen;

19.11. Die Ansicht [SimulationList]

Die Ansicht [ListeSimulations] entwickelt sich wie folgt:


<!-- définition HTML de la vue -->
<template>
  ...
  </div>
</template>
 
<script>
// imports
import Layout from "./Layout";
import Menu from "./Menu";
export default {
  // composants
  components: {
    Layout,
    Menu
  },
  // état interne
  data() {
    ...
  },
  // état interne calculé
  computed: {
    // liste des simulations prise dans le store Vuex
    simulations() {
      return this.$store.state.simulations;
    }
  },
  // méthodes
  methods: {
    supprimerSimulation(index) {
      // eslint-disable-next-line
      console.log("supprimerSimulation", index);
      // suppression de la simulation n° [index]
      this.$store.commit("deleteSimulation", index);
      // on sauvegarde la session
      this.$session.save();
    }
  },
  // cycle de vie
  created() {
    // eslint-disable-next-line
    console.log("ListeSimulations created");
  }
};
</script>

Kommentare

  • Zeile 36: Nachdem wir in Zeile 34 eine Simulation gelöscht haben, speichern wir die Sitzung, um diese Statusänderung zu berücksichtigen;
  • Zeilen 40–43: Wir verfolgen weiterhin die Erstellung von Ansichten;

19.12. Projektdurchführung

Image

Überprüfen Sie während des Testens die folgenden Punkte:

  • Wenn der Benutzer die Anwendung über die Links im Navigationsmenü und die Aktionsschaltflächen/-links „nutzt“, funktioniert sie;
  • Wenn der Benutzer URLs manuell eingibt, funktioniert die Anwendung weiterhin. Führen Sie insbesondere den folgenden Test durch:
    • Führen Sie Simulationen durch;
    • Sobald Sie sich in der Ansicht [SimulationList] befinden, laden Sie die Ansicht neu (F5). In der vorherigen Anwendung [vuejs-20] gingen die Simulationen an dieser Stelle verloren. Das ist hier nicht der Fall: Die bereits durchgeführten Simulationen sind weiterhin vorhanden;
  • Überprüfen Sie die Protokolle, um zu verstehen:
    • wann das [main]-Skript ausgeführt wird. Sie sollten sehen, dass es jedes Mal ausgeführt wird, wenn der Benutzer manuell eine URL eingibt;
    • wann die Ansichten erstellt werden. Sie sollten sehen, dass sie jedes Mal erstellt werden, wenn sie angezeigt werden sollen;
    • wie das Routing funktioniert. Vor jedem Routing wird ein Protokoll generiert, das Ihnen Folgendes mitteilt:
      • von welcher Route Sie gekommen sind;
      • die Route, zu der Sie gehen;

19.13. Bereitstellung der Anwendung auf einem lokalen Server

Folgen Sie als Übung dem Abschnitt |Bereitstellung auf einem lokalen Server|, um das [vuejs-21]-Projekt auf dem lokalen Laragon-Server bereitzustellen. Testen Sie es anschließend.

19.14. Entwicklung der mobilen Version

Theoretisch sollte die Verwendung von Bootstrap es uns ermöglichen, eine Anwendung zu erstellen, die auf verschiedenen Geräten funktioniert: Smartphones, Tablets, Laptops und Desktops. Was diese Geräte unterscheidet, ist ihre Bildschirmgröße.

Wenn wir die [vuejs-21]-Version auf einem mobilen Gerät testen, sehen wir, dass die Darstellung der Ansicht chaotisch ist. Die [vuejs-22]-Version behebt dieses Problem. Alle Änderungen wurden in den Ansichtsvorlagen vorgenommen. Sie betrafen hauptsächlich die Optimierung der Darstellung für einen Smartphone-Bildschirm. Sobald diese optimiert ist, funktioniert die Darstellung auf größeren Bildschirmen dank Bootstrap reibungslos.

Image

19.14.1. Die [Main]-Ansicht

Die [Main]-Ansicht entwickelt sich wie folgt:


<!-- definition HTML of the view -->
<template>
  <div class="container">
    <b-card>
      <!-- jumbotron -->
      <b-jumbotron>
        <b-row>
          <b-col sm="4">
            <img src="../assets/logo.jpg" alt="Cerisier en fleurs" />
          </b-col>
          <b-col sm="8">
            <h1>Calculez votre impôt</h1>
          </b-col>
        </b-row>
      </b-jumbotron>
      ....
    </b-card>
  </div>
</template>

Kommentare

  • Zeile 8: Wo früher [cols=’4’] stand, schreiben wir nun [sm=’4’]. [sm] steht für [small]. Smartphone-Bildschirme fallen in diese Kategorie. Die anderen Kategorien sind [xs=extra small, md=medium, lg=large, xl=extra large];
  • Zeile 11: wie oben;

19.14.2. Die Ansicht [Layout]

Die Ansicht [Layout] entwickelt sich wie folgt:


<!-- definition HTML of the routed view layout -->
<template>
  <!-- line -->
  <div>
    <b-row>
      <!-- three-column zone on the left -->
      <b-col sm="3" v-if="left">
        <slot name="left" />
      </b-col>
      <!-- nine-column zone on the right -->
      <b-col sm="9" v-if="right">
        <slot name="right" />
      </b-col>
    </b-row>
  </div>
</template>

19.14.3. Die Ansicht [Authentifizierung]

Die Ansicht [Authentifizierung] entwickelt sich wie folgt:


<!-- definition HTML of the view -->
<template>
  <Layout :left="false" :right="true">
    <template slot="right">
      <!-- form HTML - post its values with the [authenticate-user] action -->
      <b-form @submit.prevent="login">
        <!-- title -->
        <b-alert show variant="primary">
          <h4>Bienvenue. Veuillez vous authentifier pour vous connecter</h4>
        </b-alert>
        <!-- 1st line -->
        <b-form-group label="Nom d'utilisateur" label-for="user" description="Tapez admin">
          <!-- user input field -->
          <b-col sm="6">
            <b-form-input type="text" id="user" placeholder="Nom d'utilisateur" v-model="user" />
          </b-col>
        </b-form-group>
        <!-- 2nd line -->
        <b-form-group label="Mot de passe" label-for="password" description="Tapez admin">
          <!-- password input field -->
          <b-col sm="6">
            <b-input type="password" id="password" placeholder="Mot de passe" v-model="password" />
          </b-col>
        </b-form-group>
        <!-- 3rd line -->
        <b-alert
          show
          variant="danger"
          v-if="showError"
          class="mt-3"
        >L'erreur suivante s'est produite : {{message}}</b-alert>
        <!-- submit] button on a 3rd line -->
        <b-row>
          <b-col sm="2">
            <b-button variant="primary" type="submit" :disabled="!valid">Valider</b-button>
          </b-col>
        </b-row>
      </b-form>
    </template>
  </Layout>
</template>

Kommentare

  • Zeilen 11 und 19: Wir haben das Attribut [label-cols] entfernt, das die Anzahl der Spalten für die Eingabe-Beschriftung festlegte. Ohne dieses Attribut erscheint die Beschriftung über dem Eingabefeld. Dies ist besser für Smartphone-Bildschirme geeignet;

19.14.4. Die Ansicht [CalculImpot]

Die Ansicht [CalculImpot] wurde wie folgt aktualisiert:


<!-- definition HTML of the view -->
<template>
  <div>
    <Layout :left="true" :right="true">
      <!-- tax calculation form on the right -->
      <FormCalculImpot slot="right" @resultatObtenu="handleResultatObtenu" />
      <!-- left-hand navigation menu -->
      <Menu slot="left" :options="options" />
    </Layout>
    <!-- display area for tax calculation results under the form -->
    <b-row v-if="résultatObtenu" class="mt-3">
      <!-- empty three-column zone -->
      <b-col sm="3" />
      <!-- nine-column zone -->
      <b-col sm="9">
        <b-alert show variant="success">
          <span v-html="résultat"></span>
        </b-alert>
      </b-col>
    </b-row>
  </div>
</template>

19.14.5. Die Ansicht [FormCalculImpot]

Die Ansicht [FormCalculImpot] entwickelt sich wie folgt:


<!-- definition HTML of the view -->
  <template>
  <!-- form HTML -->
  <b-form @submit.prevent="calculerImpot" class="mb-3">
    <!-- 12-column message on blue background -->
    <b-row>
      <b-col sm="12">
        <b-alert show variant="primary">
          <h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
        </b-alert>
      </b-col>
    </b-row>
    <!-- form elements -->
    <!-- first line -->
    <b-form-group label="Etes-vous marié(e) ou pacsé(e) ?">
      <!-- 5-column radio buttons-->
      <b-col sm="5">
        <b-form-radio v-model="marié" value="oui">Oui</b-form-radio>
        <b-form-radio v-model="marié" value="non">Non</b-form-radio>
      </b-col>
    </b-form-group>
    <!-- second line -->
    <b-form-group label="Nombre d'enfants à charge" label-for="enfants">
      <b-form-input
        type="text"
        id="enfants"
        placeholder="Indiquez votre nombre d'enfants"
        v-model="enfants"
        :state="enfantsValide"
      ></b-form-input>
      <!-- possible error message -->
      <b-form-invalid-feedback :state="enfantsValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
    </b-form-group>
    <!-- third line -->
    <b-form-group
      label="Salaire annuel net imposable"
      label-for="salaire"
      description="Arrondissez à l'euro inférieur"
    >
      <b-form-input
        type="text"
        id="salaire"
        placeholder="Salaire annuel"
        v-model="salaire"
        :state="salaireValide"
      ></b-form-input>
      <!-- possible error message -->
      <b-form-invalid-feedback :state="salaireValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
    </b-form-group>
    <!-- fourth line, [submit] button -->
    <b-col sm="3">
      <b-button type="submit" variant="primary" :disabled="formInvalide">Valider</b-button>
    </b-col>
  </b-form>
</template>

Kommentare

  • Zeilen 15, 23, 35: Das Attribut [label-cols] wurde entfernt;

Außerdem aktualisieren wir die Validierungstests:


...
// état interne calculé
  computed: {
    // validation du formulaire
    formInvalide() {
      return (
        // salaire invalide
        !this.salaire.match(/^\s*\d+\s*$/) ||
        // ou enfants invalide
        !this.enfants.match(/^\s*\d+\s*$/) ||
        // ou données fiscales pas obtenues
        !this.$métier.taxAdminData
      );
    },
    // validation du salaire
    salaireValide() {
      // doit être numérique >=0
      return Boolean(
        this.salaire.match(/^\s*\d+\s*$/) || this.salaire.match(/^\s*$/)
      );
    },
    // validation des enfants
    enfantsValide() {
      // doit être numérique >=0
      return Boolean(
        this.enfants.match(/^\s*\d+\s*$/) || this.enfants.match(/^\s*$/)
      );
    }
},
...

Kommentare

  • Zeile 19: Wenn nichts eingegeben wurde, wird die Eingabe als gültig angesehen. Dadurch wird sichergestellt, dass die Eingabe bei der ersten Anzeige der Ansicht gültig ist. In der vorherigen Version wurde die Eingabe zunächst als falsch angezeigt;
  • Zeile 26: wie oben;
  • Zeilen 5–14: Die Schaltfläche „Absenden“ ist nur aktiv, wenn beide Felder Daten enthalten und gültig sind;

19.14.6. Die Ansicht [Menü]

Die Ansicht [Menü] entwickelt sich wie folgt:


<!-- definition HTML of the view -->
<template>
  <b-card class="mb-3">
    <!-- bootstrap vertical menu -->
    <b-nav vertical>
      <!-- menu options -->
      <b-nav-item
        v-for="(option,index) of options"
        :key="index"
        :to="option.path"
        exact
        exact-active-class="active"
      >{{option.text}}</b-nav-item>
    </b-nav>
  </b-card>
</template>

Kommentare

  • Zeile 3: Wir fügen das <b-card>-Tag hinzu, um das Menü mit einem schmalen Rahmen zu umgeben. Dadurch lässt sich das Menü auf einem Smartphone leichter finden;

19.14.7. Die Ansicht [SimulationList]

Die Ansicht [ListeSimulations] bleibt unverändert:


<!-- definition HTML of the view -->
<template>
  <div>
    <!-- layout -->
    <Layout :left="true" :right="true">
      <!-- simulations in right-hand column -->
      <template slot="right">
        <template v-if="simulations.length==0">
          <!-- no simulations -->
          <b-alert show variant="primary">
            <h4>Votre liste de simulations est vide</h4>
          </b-alert>
        </template>
        <template v-if="simulations.length!=0">
          <!-- there are simulations -->
          <b-alert show variant="primary">
            <h4>Liste de vos simulations</h4>
          </b-alert>
          <!-- simulation table -->
          <b-table striped hover responsive :items="simulations" :fields="fields">
            <template v-slot:cell(action)="data">
              <b-button variant="link" @click="supprimerSimulation(data.index)">Supprimer</b-button>
            </template>
          </b-table>
        </template>
      </template>
      <!-- navigation menu in left-hand column -->
      <Menu slot="left" :options="options" />
    </Layout>
  </div>
</template>

Kommentare

  • Zeile 20: Beachten Sie das Attribut [responsive], das dafür sorgt, dass sich die Darstellung der Tabelle an die Bildschirmgröße anpasst:

Image

  • in [2] ermöglicht auf kleinen Bildschirmen eine horizontale Bildlaufleiste die Anzeige der Tabelle;

19.14.8. Die Ansicht [NotFound]

Sie bleibt unverändert.

19.14.9. Mobile Ansichten

Image

Image

Hinweis: Es ist sicherlich möglich, Ansichten zu erstellen, die noch besser für Mobilgeräte geeignet sind. Ich denke dabei insbesondere an das Navigationsmenü, das verbessert werden könnte, aber es gibt auch andere Bereiche. Das Hauptziel dieses Dokuments war es nicht, eine mobile App zu erstellen. In diesem Fall hätten wir uns vielleicht einem Framework wie Ionic |https://ionicframework.com/| zugewandt.