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:

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:

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

Ü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.

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:

- 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


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.