Skip to content

19. Verbesserungen am Client Vue.js

19.1. Introduction

Wir werden das Projekt [vuejs-21] mit dem Entwicklungsserver testen. Daher muss der Server erneut die Header CORS senden. Daher muss die Datei [config.json] der Version 14 des Steuerberechnungsservers diese Header zulassen:

Image

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

Es entstehen neue Dateien:

  • [session.js]: exportiert ein Objekt [session], das Informationen zur aktuellen Sitzung enthält;
  • [pluginSession]: stellt das vorherige Objekt [session] in der Eigenschaft [$session] der Ansichten zur Verfügung;
  • [NotFound.vue]: Eine neue Ansicht, die angezeigt wird, wenn der Benutzer manuell ein nicht vorhandenes URL anfordert;

Folgende Dateien werden geändert:

  • [main.js]: initialisiert die aktuelle Sitzung und stellt sie wieder her, sobald der Benutzer manuell URL eingibt;
  • [router.js]: Es werden Prüfungen hinzugefügt, um den Fall zu behandeln, dass der Benutzer „URL“ eingibt;
  • [store.js]: Eine neue Mutation wird hinzugefügt;
  • [config.js]: Eine neue Konfiguration wird hinzugefügt;
  • verschiedene Ansichten, die im Wesentlichen dazu dienen, die aktuelle Sitzung an wichtigen Punkten im Ablauf der Anwendung zu speichern. Diese wird anschließend jedes Mal wiederhergestellt, wenn der Benutzer manuell URL eingibt;

19.2. Der Store [Vuex]

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


// Vuex-Plugin
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);

// Vuex-Store
const store = new Vuex.Store({
  state: {
    // die Simulationstabelle
    simulations: [],
    // Nummer der letzten Simulation
    idSimulation: 0
  },
  mutations: {
    // Löschen der Zeile mit der Indexnummer
    deleteSimulation(state, index) {
      ...
    },
    // Hinzufügen einer Simulation
    addSimulation(state, simulation) {
      ...
    },
    // Zustand bereinigen
    clear(state) {
      // Keine Simulationen mehr
      state.simulations = [];
      // Die Nummerierung der Simulationen beginnt wieder bei 0
      state.idSimulation = 0;
    }
  }
});
// Export des Objekts [store]
export default store;
  • Zeilen 24–29: Die Änderung [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 URL in das Adressfeld des Browsers eingibt. Dieses Skript enthält jedoch die Anweisung:


// Vuex-Store
import store from './store'

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


// Vuex-Plugin
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);

// Vuex-Store
const store = new Vuex.Store({
  state: {
    // die Simulationstabelle
    simulations: [],
    // Nummer der letzten Simulation
    idSimulation: 0
  },
  mutations: {
    ...
  }
});
// Export des Objekts [store]
export default store;

In den Zeilen 7–13 ist zu sehen, dass ein leeres Simulationsarray importiert wird. Wenn also vor der Eingabe von „URL“ in das Adressfeld des Browsers durch den Benutzer Simulationen vorhanden waren, sind diese danach nicht mehr vorhanden. Die Idee ist:

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

Das Skript „[./session]“ lautet wie folgt:


// Import der Vuex-Store
import store from './store'
// Die Konfiguration wird importiert
import config from './config';

// das Objekt [session]
const session = {
  // Sitzung gestartet
  started: false,
  // Authentifizierung
  authenticated: false,
  // Speicherzeitpunkt
  saveTime: "",
  // Ebene [métier]
  métier: null,
  // Vuex-Status
  state: null,

  // Speicherung der Sitzung in einer Zeichenkette jSON
  save() {
    // der Sitzung werden einige Eigenschaften hinzugefügt
    this.saveTime = Date.now();
    this.state = store.state;
    // sie wird in jSON umgewandelt
    const json = JSON.stringify(this);
    // im Browser gespeichert
    localStorage.setItem("session", json);
    // eslint-disable-next-line no-console
    console.log("session save", json);
  },

  // Wiederherstellung der Sitzung
  restore() {
    // Die Sitzung jSON wird aus dem Browser abgerufen
    const json = localStorage.getItem("session")
    // Wenn etwas abgerufen wurde
    if (json) {
      // werden alle Schlüssel der Sitzung wiederhergestellt
      const restore = JSON.parse(json);
      for (var key in restore) {
        if (restore.hasOwnProperty(key)) {
          this[key] = restore[key];
        }
      }
      // Wenn seit Beginn der Sitzung eine bestimmte Inaktivitätsdauer überschritten wurde, wird von vorne begonnen
      let durée = Date.now() - this.saveTime;
      if (durée > config.duréeSession) {
        // Die Sitzung wird geleert – sie wird ebenfalls gespeichert
        session.clear();
      } else {
        // Der Vuex-Store wird neu generiert
        store.replaceState(JSON.parse(JSON.stringify(this.state)));
      }
    }
    // eslint-disable-next-line no-console
    console.log("session restore", this);
  },

    // Die Sitzung wird bereinigt
  clear() {
    // eslint-disable-next-line no-console
    console.log("session clear");
    // Bestimmte Felder der Sitzung werden zurückgesetzt
    this.authenticated = false;
    this.saveTime = "";
    this.started = false;
    if (this.métier) {
      // Das Feld [taxAdminData] wird zurückgesetzt
      this.métier.taxAdminData = null;
    }
    // Der Vuex-Store wird ebenfalls bereinigt
    store.commit("clear");
    // Die neue Sitzung wird gespeichert
    this.save();
  },
}

// Export des Objekts [session]
export default session;

Kommentare

  • Zeile 2: Die Sitzung kapselt auch den Speicher [Vuex] (Liste der Simulationen, Nummer der zuletzt durchgeführten Simulation);
  • Zeilen 7–17: Die von der Sitzung gespeicherten Informationen:
    • [started]: Ob die Sitzung jSON mit dem Server gestartet wurde oder nicht;
    • [authenticated]: Hat sich der Benutzer authentifiziert oder nicht?
    • [saveTime]: das Datum der letzten Speicherung in Millisekunden;
    • [métier]: Ein Verweis auf die Ebene [métier]. Diese enthält die Daten [taxAdminData], die die Berechnung der Steuer ermöglichen;
    • [state]: der Status des Speichers [Vuex] (Liste der Simulationen, Nummer der zuletzt durchgeführten Simulation);
  • Zeilen 20–30: Die Methode [save] speichert die Sitzung lokal im Browser, in dem die Anwendung ausgeführt wird;
    • Zeile 22: Die Uhrzeit der Speicherung wird notiert;
    • Zeile 23: Der [state] wird aus dem Speicher [Vuex] abgerufen;
    • Zeile 25: Die Zeichenkette jSON der Sitzung wird erstellt;
    • Zeile 27: Sie wird lokal im Browser unter dem Schlüssel [session] gespeichert;
  • Zeilen 33–57: Mit der Methode [restore] kann eine Sitzung aus ihrer lokalen Sicherung im Browser wiederhergestellt werden;
    • Zeile 35: Die lokale Sicherung jSON wird abgerufen;
    • Zeile 37: sofern etwas abgerufen wurde;
    • Zeilen 39–44: Das Objekt [session] wird rekonstruiert;
    • Zeile 46: Es wird die Zeit seit der letzten Sicherung berechnet;
    • Zeilen 47–50: Wenn diese Zeitspanne einen in der Konfiguration festgelegten Wert [config.duréeSession] überschreitet, wird die Sitzung zurückgesetzt (Zeile 49) und dabei gesichert;
    • Zeile 52: Andernfalls wird das Attribut [state] des Speichers [Vuex] neu generiert;
  • Zeilen 60–75: Die Methode [clear] setzt die Sitzung zurück;
    • Zeilen 64–70: Die Eigenschaften der Sitzung werden auf ihre Ausgangswerte zurückgesetzt;
    • Zeile 72: ebenso wie der Speicher [Vuex];
    • Zeile 74: Die neue Sitzung wird gespeichert;

19.4. Die Konfigurationsdatei [config]

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


// Verwendung der Bibliothek [axios]
const axios = require('axios');
// Zeitüberschreitung bei den Anfragen HTTP
axios.defaults.timeout = 2000;
...

// Export der Konfiguration
export default {
  // Objekt [axios]
  axios: axios,
  // Maximale Inaktivitätsdauer der Sitzung: 5 Min. = 300 s = 300000 ms
  duréeSession: 300000
}
  • Zeile 12: Die Anwendungssitzung wird ähnlich wie eine Websitzung verwaltet. Hier wird eine maximale Inaktivitätsdauer von 5 Minuten festgelegt;

19.5. Das Plugin [pluginSession]

Wie bereits mehrfach beschrieben, ermöglicht das Plugin [pluginSession] den Ansichten den Zugriff auf die Sitzung über die Eigenschaft [this.$session]:


export default {
  install(Vue, session) {
    // Fügt der Ansichtsklasse eine Eigenschaft [$session] hinzu
    Object.defineProperty(Vue.prototype, '$session', {
      // Wenn auf Vue.$session verwiesen wird, wird der zweite Parameter auf [session] gesetzt
      get: () => session,
    })
  }
}

19.6. Das Hauptskript [main]

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


// Startprotokoll
// eslint-disable-next-line no-console
console.log("main started");

// Importe
import Vue from 'vue'

...

// Instanziierung der Schicht [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)

// Vuex-Store
import store from './store'

// Sitzung
import session from './session';
import pluginSession from './plugins/pluginSession'
Vue.use(pluginSession, session)

// Die Sitzung wird vor dem Neustart wiederhergestellt
session.restore();

// Die Schicht wird wiederhergestellt [métier]
if (session.métier && session.métier.taxAdminData) {
  métier.setTaxAdminData(session.métier.taxAdminData);
}

// Start der App „UI“
new Vue({
  el: '#App',
  // der Router
  router: router,
  // die Vuex-Jalousie
  store: store,
  // Hauptansicht
  render: h => h(Main),
})

// Abschlussprotokoll
// eslint-disable-next-line no-console
console.log("main terminated, session=", session);
  • Zeile 19: Die Sitzung wird importiert;
  • Zeile 20: Das Plugin wird importiert;
  • Zeile 21: Das Plugin [pluginSession] wird in [Vue] integriert. Nach dieser Anweisung verfügen alle Ansichten über die Sitzung in ihrem Attribut [$session];
  • Zeile 27: Die Sitzung wird wiederhergestellt. Die in Zeile 11 importierte Sitzung wird dann mit dem Inhalt ihrer letzten Sicherung initialisiert;
  • Nach Zeile 16 verfügen die Ansichten über eine Eigenschaft [$métier], die in Zeile 12 initialisiert wurde. Diese Eigenschaft enthält nicht die Information [taxAdminData], mit der die Steuer berechnet werden kann;
  • Zeilen 30–32: Wenn bei der soeben durchgeführten Wiederherstellung die Eigenschaft [session.métier.taxAdminData] wiederhergestellt wurde, 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:


// Importe
import Vue from 'vue'
import VueRouter from 'vue-router'
// die Ansichten
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'
import NotFound from './views/NotFound'
// die Sitzung
import session from './session'

// Routing-Plugin
Vue.use(VueRouter)

// die Routen der Anwendung
const routes = [
  // Authentifizierung
  { path: '/', name: 'authentification', component: Authentification },
  { path: '/authentification', name: 'authentification', component: Authentification },
  // Steuerberechnung
  {
    path: '/calcul-impot', name: 'calculImpot', component: CalculImpot,
    meta: { authenticated: true }
  },
  // Liste der Simulationen
  {
    path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations,
    meta: { authenticated: true }
  },
  // Sitzung beenden
  {
    path: '/fin-session', name: 'finSession'
  },
  // Unbekannte Seite
  {
    path: '*', name: 'notFound', component: NotFound,
  },
]

// der Router
const router = new VueRouter({
  // die Routen
  routes,
  // Anzeigemodus der URL
  mode: 'history',
  // die Basis der Anwendung URL
  base: '/client-vuejs-impot/'
})

// Überprüfung der Routen
router.beforeEach((to, from, next) => {
  // eslint-disable-next-line no-console
  console.log("router to=", to, "from=", from);
  // Route nur für authentifizierte Benutzer?
  if (to.meta.authenticated && !session.authenticated) {
    next({
      // Weiter zur Authentifizierung
      name: 'authentification',
    })
    // Zurück zur Ereignisschleife
    return;
  }
  // Sonderfall: Ende der Sitzung
  if (to.name === "finSession") {
    // Die Sitzung wird bereinigt
    session.clear();
    // Wechsel zur Ansicht [authentification]
    next({
      name: 'authentification',
    })
    // Zurück zur Ereignisschleife
    return;
  }
  // andere Fälle – nächste normale Ansicht des Routings
  next();
})

// Export des Routers
export default router

Anmerkungen

  • Zeilen 16–38: Einige Routen wurden um zusätzliche Informationen ergänzt;
  • Zeile 19: Es wurde eine neue Route zur Ansicht [Authentification] erstellt;
  • Zeilen 21–24: Die Route, die zur Ansicht [CalculImpot] führt, verfügt nun über eine Eigenschaft [meta] (dieser Name ist obligatorisch). Der Inhalt dieses Objekts kann beliebig sein und wird vom Entwickler festgelegt;
  • Zeile 23: In [meta] wird die Eigenschaft [authenticated] hinterlegt (dieser Name kann beliebig sein). Für uns bedeutet dies, dass der Benutzer authentifiziert sein muss, um zur Ansicht [CalculImpot] zu gelangen;
  • Zeilen 26–29: Das Gleiche wird für die Route durchgeführt, die zur Ansicht „[ListeSimulations]“ führt. Auch hier muss der Benutzer authentifiziert sein;
  • Die Eigenschaft [meta.authenticated] ermöglicht es uns zu überprüfen, dass ein Benutzer, der die URL der Ansichten [CalculImpot, ListeSimulations] manuell eingibt, diese nicht abrufen 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: Es wird geprüft, ob die nächste Route eine Authentifizierung des Benutzers erfordert;
  • Zeilen 56–59: Wenn ja und der Benutzer nicht authentifiziert ist, wird die nächste Route auf die Ansicht [Authentification] geändert;
  • Zeilen 64–73: Es wird der Sonderfall der Route [finSession] aus den Zeilen 30–32 behandelt. Dieser ist keine Ansicht zugeordnet;
    • Zeile 66: Die Sitzung wird auf ihren Ausgangswert zurückgesetzt;
    • Zeilen 68–70: Die Ansicht [Authentification] wird als nächste Ansicht festgelegt;
  • Zeile 75: Wenn keiner der beiden vorherigen Fälle zutrifft, wechselt man einfach zu der in der Routing-Datei vorgesehenen Route;
  • Zeilen 35–37: Es wird eine Ansicht [NotFound] vorgesehen, falls die vom Benutzer eingegebene Route keiner bekannten Route entspricht. Diese Ansicht wird in Zeile 8 importiert. Die Routen werden in der Reihenfolge der Routendatei überprüft. Wenn also Zeile 36 erreicht wird, bedeutet dies, dass die angeforderte Route keine der Routen aus den Zeilen 18–33 ist;

19.8. Die Ansicht [NotFound]

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

Image

Der Code der Ansicht lautet wie folgt:


<!-- Definition HTML der Ansicht -->
<template>
  <!-- Layout -->
  <Layout :left="true" :right="true">
    <!-- Warnung in der rechten Spalte -->
    <template slot="right">
      <!-- Meldung auf gelbem Hintergrund -->
      <b-alert show variant="danger" align="center">
        <h4>Cette page n'existe pas</h4>
      </b-alert>
    </template>
    <!-- Navigationsmenü in der linken Spalte -->
    <Menu slot="left" :options="options" />
  </Layout>
</template>

<script>
// Importe
import Layout from "./Layout";
import Menu from "./Menu";
export default {
  // Komponenten
  components: {
    Layout,
    Menu
  },
  // Interner Status der Komponente
  data() {
    return {
      // Optionen des Navigationsmenüs
      options: [
        {
          text: "Authentification",
          path: "/"
        }
      ]
    };
  },
  // Lebenszyklus
  created() {
    // eslint-disable-next-line
    console.log("NotFound created");
    // Es wird geprüft, welche Menüoptionen angeboten werden sollen
    if (this.$session.authenticated && this.$métier.taxAdminData) {
      // Der Benutzer kann Simulationen durchführen
      Array.prototype.push.apply(this.options, [
        {
          text: "Calcul de l'impôt",
          path: "/calcul-impot"
        },
        {
          text: "Liste des simulations",
          path: "/liste-des-simulations"
        }
      ]);
    }
  }
};
</script>

Anmerkungen

  • Zeile 4: Hier werden beide Spalten der gerouteten 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: Es wird geprüft, ob der Benutzer Simulationen durchführen darf;
  • Zeilen 45–55: Wenn ja, werden dem Navigationsmenü zwei Optionen hinzugefügt, für die eine Authentifizierung sowie eine betriebsbereite Ebene „[métier]“ erforderlich sind (Zeilen 46–55);

19.9. Die Ansicht [Authentification]

Die Ansicht [Authentification] entwickelt sich wie folgt:


<!-- Definition HTML der Ansicht -->
<template>
  <Layout :left="false" :right="true">
    ...
  </Layout>
</template>

<!-- Dynamik der Ansicht -->
<script>
import Layout from "./Layout";
export default {
  // Status der Komponente
  data() {
    return {
      // Benutzer
      user: "",
      // sein Passwort
      password: "",
      // steuert die Anzeige einer Fehlermeldung
      showError: false,
      // die Fehlermeldung
      message: ""
    };
  },

  // verwendete Komponenten
  components: {
    Layout
  },

  // berechnete Eigenschaften
  computed: {
    // Gültige Eingaben
    valid() {
      return this.user && this.password && this.$session.started;
    }
  },

  // Ereignisbehandler
  methods: {
    // ----------- Authentifizierung
    async login() {
      try {
        // Wartezeit beginnt
        this.$emit("loading", true);
        // noch nicht authentifiziert
        this.$session.authenticated = false;
        // blockierende Authentifizierung beim Server
        const response = await this.$dao.authentifierUtilisateur(
          this.user,
          this.password
        );
        // Ende des Ladevorgangs
        this.$emit("loading", false);
        // Auswertung der Serverantwort
        if (response.état != 200) {
          // Der Fehler wird angezeigt
          this.message = response.réponse;
          this.showError = true;
          // Rückkehr zur Ereignisschleife
          return;
        }
        // kein Fehler
        this.showError = false;
        // Authentifizierung erfolgreich
        this.$session.authenticated = true;
        // --------- Nun werden die Daten der Steuerbehörde angefordert
        // zunächst keine Daten
        this.$métier.setTaxAdminData(null);
        // Wartephase beginnt
        this.$emit("loading", true);
        // blockierende Anfrage an den Server
        const response2 = await this.$dao.getAdminData();
        // Ladevorgang abgeschlossen
        this.$emit("loading", false);
        // Antwort wird analysiert
        if (response2.état != 1000) {
          // Fehler wird angezeigt
          this.message = response2.réponse;
          this.showError = true;
          // Rückkehr zur Ereignisschleife
          return;
        }
        // kein Fehler
        this.showError = false;
        // Die empfangenen Daten werden in der Schicht [métier] gespeichert
        this.$métier.setTaxAdminData(response2.réponse);
        // Es kann mit der Steuerberechnung fortgefahren werden
        this.$router.push({ name: "calculImpot" });
      } catch (error) {
        // Der Fehler wird an die übergeordnete Komponente weitergeleitet
        this.$emit("error", error);
      } finally {
        // Sitzung aktualisieren
        this.$session.métier = this.$métier;
        // Die Sitzung wird gespeichert
        this.$session.save();
      }
    }
  },
  // Lebenszyklus: Die Komponente wurde gerade erstellt
  created() {
    // eslint-disable-next-line
    console.log("Authentification created");
    // Kann der Benutzer Simulationen durchführen?
    if (
      this.$session.started &&
      this.$session.authenticated &&
      this.$métier.taxAdminData
    ) {
      // Dann kann der Benutzer Simulationen durchführen
      this.$router.push({ name: "calculImpot" });
      // Zurück zur Ereignisschleife
      return;
    }
    // Wenn die Sitzung jSON bereits gestartet wurde, wird sie nicht erneut gestartet
    if (!this.$session.started) {
      // Wartephase beginnt
      this.$emit("loading", true);
      // Die Sitzung mit dem Server wird initialisiert – asynchrone Anfrage
      // Das von den Methoden der Schicht [dao] zurückgegebene Versprechen wird verwendet
      this.$dao
        // Eine Sitzung wird initialisiert jSON
        .initSession()
        // Die Antwort wurde empfangen
        .then(response => {
          // Wartezeit beendet
          this.$emit("loading", false);
          // Analyse der Antwort
          if (response.état != 700) {
            // Der Fehler wird angezeigt
            this.message = response.réponse;
            this.showError = true;
            // Rückkehr zur Ereignisschleife
            return;
          }
          // Die Sitzung wurde gestartet
          this.$session.started = true;
        })
        // im Fehlerfall
        .catch(error => {
          // Der Fehler wird an die Ansicht [Main] weitergeleitet
          this.$emit("error", error);
        })
        // in jedem Fall
        .finally(() => {
          // wird die Sitzung gespeichert
          this.$session.save();
        });
    }
  }
};
</script>

Anmerkungen

  • Die Anweisungen, die die in dieser Version des [Vue.js]-Clients eingegebene Sitzung verwenden, wurden gelb hervorgehoben;
  • Zeilen 97, 148: Am Ende der Methoden [login, created] wird die Sitzung unabhängig vom Ergebnis der Abfragen HTTP, die in diesen Methoden stattfinden, gespeichert (Klausel [finally] in beiden Fällen);
  • Die Methode [created] in den Zeilen 102–150 wird jedes Mal ausgeführt, wenn die Ansicht [Authentification] erstellt wird. Wenn der Benutzer die URL der Ansicht eingegeben hat, gibt uns die Sitzung Aufschluss darüber, was zu tun ist;
  • Zeilen 106–115: Wenn die Sitzung jSON gestartet, der Benutzer authentifiziert und die Daten [this.$métier.taxAdminData] initialisiert sind, kann der Benutzer direkt zum Formular zur Steuerberechnung (Zeile 112) wechseln;
  • Zeile 117: Die Methode [created] wurde in der vorherigen Version verwendet, um eine Sitzung jSON mit dem Server zu initialisieren. Dieser Schritt ist überflüssig, wenn er bereits stattgefunden hat;
  • Zeilen 42–66: die Authentifizierungsmethode;
  • Zeile 66: Bei erfolgreicher Authentifizierung wird dies in der Sitzung vermerkt;
  • Zeilen 67–92: die Anfrage an den Server bezüglich der Daten der Steuerbehörde [taxAdminData];
  • Zeile 95: Am Ende dieser Phase wird die Eigenschaft [métier] der Sitzung aktualisiert, unabhängig davon, ob der Vorgang erfolgreich war oder nicht;

19.10. Die Ansicht [CalculImpot]

Der Code der Ansicht [CalculImpot] entwickelt sich wie folgt:


<!-- Definition der Ansicht HTML -->
<template>
  ...
</template>

<script>
// Importe
import FormCalculImpot from "./FormCalculImpot";
import Menu from "./Menu";
import Layout from "./Layout";

export default {
  // interner Bericht
  data() {
    return {
      // Menüoptionen
      options: [
        {
          text: "Liste des simulations",
          path: "/liste-des-simulations"
        },
        {
          text: "Fin de session",
          path: "/fin-session"
        }
      ],
      // Ergebnis der Steuerberechnung
      résultat: "",
      résultatObtenu: false
    };
  },
  // verwendete Komponenten
  components: {
    Layout,
    FormCalculImpot,
    Menu
  },
  // Methoden zur Ereignisverwaltung
  methods: {
    // Ergebnis der Steuerberechnung
    handleResultatObtenu(résultat) {
      // Das Ergebnis wird in eine Zeichenkette umgewandelt HTML
      ...
      // eine Simulation von +
      this.$store.commit("addSimulation", résultat);
      // Die Sitzung wird gespeichert
      this.$session.save();
    }
  },
  // Lebenszyklus
  created() {
    // eslint-disable-next-line
    console.log("CalculImpot created");
  }
};
</script>

Kommentare

  • Zeile 45: Die berechnete Simulation wird dem Store [Vuex] hinzugefügt. Dies wirkt sich auf die Sitzung aus, die die Eigenschaft [state] des Stores umfasst. Daher wird die Sitzung gespeichert (Zeile 47);
  • Zeile 51: Es wird eine Methode [created] angelegt, um die Erstellung von Ansichten in den Protokollen zu verfolgen;

19.11. Die Ansicht [ListeSimulations]

Die Ansicht [ListeSimulations] entwickelt sich wie folgt:


<!-- Definition HTML der Ansicht -->
<template>
  ...
  </div>
</template>

<script>
// Importe
import Layout from "./Layout";
import Menu from "./Menu";
export default {
  // Komponenten
  components: {
    Layout,
    Menu
  },
  // interner Status
  data() {
    ...
  },
  // berechneter interner Status
  computed: {
    // Liste der aus dem Vuex-Store abgerufenen Simulationen
    simulations() {
      return this.$store.state.simulations;
    }
  },
  // Methoden
  methods: {
    supprimerSimulation(index) {
      // eslint-disable-next-line
      console.log("supprimerSimulation", index);
      // Löschen der Simulation Nr. [index]
      this.$store.commit("deleteSimulation", index);
      // Die Sitzung wird gespeichert
      this.$session.save();
    }
  },
  // Lebenszyklus
  created() {
    // eslint-disable-next-line
    console.log("ListeSimulations created");
  }
};
</script>

Kommentare

  • Zeile 36: Nach dem Löschen einer Simulation in Zeile 34 wird die Sitzung gespeichert, um diese Statusänderung zu berücksichtigen;
  • Zeilen 40–43: Die Erstellung der Ansichten wird weiterverfolgt;

19.12. Ausführung des Projekts

Image

Überprüfen Sie bei den Tests folgende Punkte:

  • Wenn der Benutzer die Anwendung über die Links im Navigationsmenü und die Aktionsschaltflächen/-links „nutzt“, funktioniert sie;
  • Wenn der Benutzer URL manuell eingibt, funktioniert die Anwendung weiterhin. Führen Sie insbesondere den folgenden Test durch:
    • Führen Sie Simulationen durch;
    • Sobald Sie sich auf der Ansicht [ListeSimulations] befinden, laden Sie die Ansicht neu (F5). In der vorherigen Anwendung [vuejs-20] gingen dabei die Simulationen verloren. Hier ist das nicht der Fall: Die bereits durchgeführten Simulationen sind weiterhin vorhanden;
  • Sehen Sie sich die Protokolle an, um zu verstehen:
    • zu welchem Zeitpunkt das Skript [main] ausgeführt wird. Sie sollten feststellen, dass dies jedes Mal geschieht, wenn der Benutzer eine URL manuell eingibt;
    • zu welchen Zeitpunkten die Ansichten erstellt werden. Sie sollten feststellen, dass dies jedes Mal geschieht, wenn sie angezeigt werden sollen;
    • wie das Routing funktioniert. Vor jedem Routing wird ein Protokoll erstellt, das Ihnen Folgendes anzeigt:
      • den Pfad, von dem Sie kommen;
      • den Pfad, zu dem Sie gehen;

19.13. Bereitstellung der Anwendung auf einem lokalen Server

Führen Sie als Übung den Abschnitt |Bereitstellung auf einem lokalen Server| durch, um das Projekt [vuejs-21] 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 genutzt werden kann: Smartphones, Tablets, Laptops und Desktop-Computern. Was diese Geräte unterscheidet, ist die Größe ihres Bildschirms.

Testet man die Version [vuejs-21] auf einem Smartphone, stellt man fest, dass die Darstellung der Ansichten chaotisch ist. Die Version [vuejs-22] behebt dieses Problem. Alle Änderungen wurden in den Vorlagen der Ansichten vorgenommen. Sie bestanden im Wesentlichen darin, eine Darstellung für einen Smartphone-Bildschirm zu optimieren. Sobald diese optimiert ist, läuft die Darstellung auf größeren Bildschirmen dank Bootstrap reibungslos ab.

Image

19.14.1. Die Ansicht [Main]

Die Ansicht [Main] entwickelt sich wie folgt:


<!-- Definition der Ansicht HTML -->
<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>

Anmerkungen

  • Zeile 8: Wo zuvor [cols=’4’] stand, wird nun [sm=’4’] geschrieben. [sm] bedeutet [small]. Smartphone-Bildschirme fallen in diese Kategorie. Die anderen Kategorien sind [xs=extra small, md=medium, lg=large, xl=extra large];
  • Zeile 11: ebenso;

19.14.2. Die Ansicht [Layout]

Die Ansicht [Layout] entwickelt sich wie folgt:


<!-- Definition HTML des Layouts der weitergeleiteten Ansicht -->
<template>
  <!-- Zeile -->
  <div>
    <b-row>
      <!-- dreispaltiger Bereich links -->
      <b-col sm="3" v-if="left">
        <slot name="left" />
      </b-col>
      <!-- neunsäuliger Bereich rechts -->
      <b-col sm="9" v-if="right">
        <slot name="right" />
      </b-col>
    </b-row>
  </div>
</template>

19.14.3. Die Ansicht [Authentification]

Die Ansicht [Authentification] entwickelt sich wie folgt:


<!-- Definition HTML der Ansicht -->
<template>
  <Layout :left="false" :right="true">
    <template slot="right">
      <!-- Formular HTML – die Werte werden mit der Aktion [authentifier-utilisateur] übermittelt -->
      <b-form @submit.prevent="login">
        <!-- Titel -->
        <b-alert show variant="primary">
          <h4>Bienvenue. Veuillez vous authentifier pour vous connecter</h4>
        </b-alert>
        <!-- 1. Zeile -->
        <b-form-group label="Nom d'utilisateur" label-for="user" description="Tapez admin">
          <!-- Benutzereingabefeld -->
          <b-col sm="6">
            <b-form-input type="text" id="user" placeholder="Nom d'utilisateur" v-model="user" />
          </b-col>
        </b-form-group>
        <!-- 2. Zeile -->
        <b-form-group label="Mot de passe" label-for="password" description="Tapez admin">
          <!-- Passwort-Eingabefeld -->
          <b-col sm="6">
            <b-input type="password" id="password" placeholder="Mot de passe" v-model="password" />
          </b-col>
        </b-form-group>
        <!-- 3. Zeile -->
        <b-alert
          show
          variant="danger"
          v-if="showError"
          class="mt-3"
        >L'erreur suivante s'est produite : {{message}}</b-alert>
        <!-- Schaltfläche vom Typ [submit] in der dritten Zeile -->
        <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>

Anmerkungen

  • Zeilen 11 und 19: Das Attribut [label-cols], das eine bestimmte Spaltenanzahl für die Beschriftung des Eingabefelds festlegte, wurde entfernt. Ohne dieses Attribut befindet sich die Beschriftung über dem Eingabefeld. Dies ist besser für Smartphone-Bildschirme geeignet;

19.14.4. Die Ansicht [CalculImpot]

Die Ansicht [CalculImpot] ändert sich wie folgt:


<!-- Definition HTML der Ansicht -->
<template>
  <div>
    <Layout :left="true" :right="true">
      <!-- Formular zur Steuerberechnung auf der rechten Seite -->
      <FormCalculImpot slot="right" @resultatObtenu="handleResultatObtenu" />
      <!-- Navigationsmenü links -->
      <Menu slot="left" :options="options" />
    </Layout>
    <!-- Anzeigebereich für die Ergebnisse der Steuerberechnung unterhalb des Formulars -->
    <b-row v-if="résultatObtenu" class="mt-3">
      <!-- leerer Bereich mit drei Spalten -->
      <b-col sm="3" />
      <!-- neunsäuliger Bereich -->
      <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 der Ansicht -->
  <template>
  <!-- Formular HTML -->
  <b-form @submit.prevent="calculerImpot" class="mb-3">
    <!-- Meldung über 12 Spalten auf blauem Hintergrund -->
    <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>
    <!-- Formularelemente -->
    <!-- erste Zeile -->
    <b-form-group label="Etes-vous marié(e) ou pacsé(e) ?">
      <!-- Optionsfelder in 5 Spalten-->
      <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>
    <!-- zweite Zeile -->
    <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>
      <!-- eventuelle Fehlermeldung -->
      <b-form-invalid-feedback :state="enfantsValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
    </b-form-group>
    <!-- dritte Zeile -->
    <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>
      <!-- mögliche Fehlermeldung -->
      <b-form-invalid-feedback :state="salaireValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
    </b-form-group>
    <!-- vierte Zeile, Schaltfläche [submit] -->
    <b-col sm="3">
      <b-button type="submit" variant="primary" :disabled="formInvalide">Valider</b-button>
    </b-col>
  </b-form>
</template>

Anmerkungen

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

Außerdem werden die Gültigkeitstests angepasst:


...
// berechneter interner Status
  computed: {
    // Formularüberprüfung
    formInvalide() {
      return (
        // Ungültiges Gehalt
        !this.salaire.match(/^\s*\d+\s*$/) ||
        // oder ungültige Kinder
        !this.enfants.match(/^\s*\d+\s*$/) ||
        // oder Steuerdaten nicht abgerufen
        !this.$métier.taxAdminData
      );
    },
    // Lohnvalidierung
    salaireValide() {
      // muss ein numerischer Wert >= 0 sein
      return Boolean(
        this.salaire.match(/^\s*\d+\s*$/) || this.salaire.match(/^\s*$/)
      );
    },
    // Validierung der Kinder
    enfantsValide() {
      // muss ein numerischer Wert >= 0 sein
      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 ist die Eingabe bereits bei der ersten Anzeige der Ansicht gültig. In der vorherigen Version wurde die Eingabe zunächst als fehlerhaft angezeigt;
  • Zeile 26: ebenso;
  • Zeilen 5–14: Die Schaltfläche „Bestätigen“ ist nur aktiv, wenn beide Eingaben etwas enthalten und gültig sind;

19.14.6. Die Ansicht [Menu]

Die Ansicht [Menu] ändert sich wie folgt:


<!-- Definition der Ansicht HTML -->
<template>
  <b-card class="mb-3">
    <!-- vertikales Bootstrap-Menü -->
    <b-nav vertical>
      <!-- Menüoptionen -->
      <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: Das Tag <b-card> wird hinzugefügt, um das Menü mit einem schmalen Rahmen zu umgeben. Dadurch lässt sich das Menü auf dem Smartphone besser lokalisieren;

19.14.7. Die Ansicht [ListeSimulations]

Die Ansicht [ListeSimulations] bleibt unverändert:


<!-- Definition der Ansicht HTML -->
<template>
  <div>
    <!-- Layout -->
    <Layout :left="true" :right="true">
      <!-- Simulationen in der rechten Spalte -->
      <template slot="right">
        <template v-if="simulations.length==0">
          <!-- keine Simulationen -->
          <b-alert show variant="primary">
            <h4>Votre liste de simulations est vide</h4>
          </b-alert>
        </template>
        <template v-if="simulations.length!=0">
          <!-- Es gibt Simulationen -->
          <b-alert show variant="primary">
            <h4>Liste de vos simulations</h4>
          </b-alert>
          <!-- Tabelle der Simulationen -->
          <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>
      <!-- Navigationsmenü in der linken Spalte -->
      <Menu slot="left" :options="options" />
    </Layout>
  </div>
</template>

Anmerkungen

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

Image

  • Bei [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. Ansichten auf Mobilgeräten

Image

Image

Hinweis: Es gibt sicherlich Möglichkeiten, die Ansichten noch besser an mobile Geräte anzupassen. Ich denke dabei insbesondere an das Navigationsmenü, das verbessert werden könnte, aber es gibt noch weitere Punkte. Das primäre Ziel dieses Dokuments war nicht die Erstellung einer mobilen App. In diesem Fall hätte man sich vielleicht für ein Framework wie Ionic |https://ionicframework.com/| entschieden.