17. Beispiel [nuxt-20]: Portierung des Beispiels [vuejs-22]
17.1. Présentation
Wir beabsichtigen hier, das Beispiel [vuejs-22], bei dem es sich um eine Anwendung vom Typ [vue.js] handelte, in einen Kontext vom Typ [nuxt] SSR zu portieren. [vuejs-22] war eine Client-Anwendung des Steuerberechnungsservers, die folgende Ansichten enthielt:
Die erste Ansicht ist die Authentifizierungsansicht:

Die zweite Ansicht ist die Steuerberechnungsansicht:

Die dritte Ansicht zeigt die Liste der vom Benutzer durchgeführten Simulationen an:

Der obige Bildschirm zeigt, dass die Simulation Nr. 1 gelöscht werden kann. Daraufhin erhält man folgende Ansicht:

Löscht man nun die letzte Simulation, erhält man die folgende neue Ansicht:

Wir werden die Anwendung [vuejs-22] schrittweise auf die Anwendung [nuxt-20] umstellen. Wir werden den Code von [vuejs-22] nicht erneut erläutern. Der Leser wird gebeten, das Dokument |Einführung in das Framework VUE.JS anhand eines Beispiels| noch einmal durchzulesen. Die verschiedenen Schritte sollten die Unterschiede zwischen einer Anwendung [vuejs] und einer Anwendung [nuxt] verdeutlichen.
17.2. Schritt 1
Das Projekt [nuxt-20] wird zunächst durch Kopieren des Projekts [nuxt-12] erstellt. Dieses ist in der Tat ein guter Ausgangspunkt:
- Es kann mit dem Steuerberechnungsserver kommunizieren;
- es verarbeitet die vom Server gesendeten Fehler korrekt;
- der Client und der Server [nuxt] können über eine Sitzung [nuxt] miteinander kommunizieren;
Wir verfügen also über eine gute Ausgangsbasis. Unsere Hauptaufgabe sollte darin bestehen, Folgendes anzupassen:
- die Seiten. Wir werden die Seiten aus dem Projekt [vuejs-22] übernehmen und an die neue Umgebung anpassen;
- die Verwaltung des Stores. Es sollten zusätzliche Informationen angezeigt werden (Liste der Simulationen), während andere möglicherweise überflüssig werden;
- die Verwaltung des Client- und Server-Routings [nuxt];
Zunächst erstellen wir also das Projekt [nuxt-20], indem wir das Projekt [nuxt-12] kopieren:

Anschließend werden die nicht mehr benötigten Seiten und Komponenten [2] gelöscht:
- Die Komponente [components/navigation] verschwindet;
- das Layout [layout/default] verschwindet;
- die Seiten [index, authentification, get-admindata, fin-session] werden entfernt;
Anschließend werden Elemente aus [vuejs-22] und [3] in [nuxt-20] integriert:
- Die drei Seiten [Authentification, CalculImpot, ListeSimulations] der Anwendung [vuejs-22] werden in den Ordner [pages] verschoben;
- Die Komponenten [FormCalculImpot, Menu, Layout] der Anwendung [vuejs-22] werden in den Ordner [components] verschoben;
- Die Seite [Main] aus [vuejs-22], die in der Anwendung [vuejs-22] als [layout] diente, wird in den Ordner [layouts] verschoben;
Die eingebetteten Elemente [4] werden umbenannt:

- In [layouts] wurde [Main] zu [default], da dies der Standardname für das Layout einer Anwendung [nuxt] ist;
- in [pages] wurde die Seite [Authentification] zu [index], da [Authentification] diese Rolle in der Anwendung [vuejs-22] innehatte;
An dieser Stelle kann man das Projekt kompilieren, um erste Fehler zu erkennen. Man ändert die Datei [nuxt.config] aus dem Beispiel [nuxt-12], damit nun [nuxt-20] ausgeführt wird:
export default {
mode: 'universal',
/*
** Headers of the page
*/
head: {
title: 'Introduction à [nuxt.js]',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{
hid: 'description',
name: 'description',
content: 'ssr routing loading asyncdata middleware plugins store'
}
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
},
/*
** Customize the progress-bar color
*/
loading: false,
/*
** Global CSS
*/
css: [],
/*
** Plugins to load before mounting the App
*/
plugins: [
{ src: '@/plugins/client/plgSession', mode: 'client' },
{ src: '@/plugins/server/plgSession', mode: 'server' },
{ src: '@/plugins/client/plgDao', mode: 'client' },
{ src: '@/plugins/server/plgDao', mode: 'server' },
{ src: '@/plugins/client/plgEventBus', mode: 'client' }
],
/*
** Nuxt.js dev-modules
*/
buildModules: [
// Dok.: https://github.com/nuxt-community/eslint-module
'@nuxtjs/eslint-module'
],
/*
** Nuxt.js modules
*/
modules: [
// Dok.: https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
// Dok.: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
// https://www.npmjs.com/package/cookie-universal-nuxt
'cookie-universal-nuxt'
],
/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios: {},
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {}
},
// Quellcode-Verzeichnis
srcDir: 'nuxt-20',
// Router
router: {
// Stammverzeichnis der Anwendung URL
base: '/nuxt-20/',
// Routing-Middleware
middleware: ['routing']
},
// Server
server: {
// Dienstport, standardmäßig 3000
port: 81,
// Abgehörte Netzwerkadressen, standardmäßig localhost: 127.0.0.1
// 0.0.0.0 = alle Netzwerkadressen des Rechners
host: 'localhost'
},
// Umgebung
env: {
// Axios-Konfiguration
timeout: 2000,
withCredentials: true,
baseURL: 'http://„localhost/php7/scripts-web/impots/version-14“,
// Konfiguration des Sitzungs-Cookies [nuxt]
maxAge: 60 * 5
}
}
Anschließend wird eine [build]-Datei des Projekts erstellt:

Es werden folgende Fehler gemeldet:
- Der Fehler in Zeile 1 weist darauf hin, dass auf ein nicht vorhandenes Bild verwiesen wird. Dieses wird in [vuejs-22] abgerufen;
- Der Fehler in Zeile 2 zeigt an, dass die Komponente [./FormCalculImpot] nicht existiert. Tatsächlich befindet sich diese Komponente nun in [@/components/form-calcul-impot];
- Die Fehler in den Zeilen zu [3-5] zeigen, dass die Komponente [./Layout] nicht existiert. Tatsächlich befindet sich diese Komponente nun in [@/components/layout];
- Die Fehler in den Zeilen [6-7] zeigen, dass die Komponente [./Menu] nicht existiert. Tatsächlich heißt sie nun [@/components/menu];
Das Bild [assets/logo.jpg] wird dem Projekt [nuxt-20] hinzugefügt:

Außerdem werden wir auf allen Seiten den Pfad der Komponenten korrigieren. Nehmen wir als Beispiel die Seite [calcul-impot]:
<!-- Definition der Ansicht HTML -->
<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>
<script>
// Importe
import FormCalculImpot from './FormCalculImpot'
import Menu from './Menu'
import Layout from './Layout'
export default {
// verwendete Komponenten
components: {
Layout,
FormCalculImpot,
Menu
},
Die drei [import] in den Zeilen 26–28 werden zu:
// Importe
import FormCalculImpot from '@/components/form-calcul-impot'
import Menu from '@/components/menu'
import Layout from '@/components/layout'
Auf diese Weise überprüft und korrigiert man gegebenenfalls die [import] aller Komponenten, Layouts und Seiten. Sobald diese Korrekturen vorgenommen wurden, kann man einen neuen [build] versuchen. Normalerweise treten dann keine Fehler mehr auf.
Man kann dann einen Durchlauf versuchen:

Es treten Fehler auf:
Man sieht, dass der Befehl [dev] in Verbindung mit dem Modul [eslint] syntaktisch strenger ist als der Befehl [build]. Hier wird verlangt, dass der Vergleichsoperator [!=] als [!==] geschrieben wird, da dieser Operator strenger ist (er überprüft zusätzlich den Typ der Operanden). Diese Fehler treten auf der Seite [index.vue] auf.
Wir beheben die oben genannten Fehler und starten die Ausführung des Projekts erneut. Daraufhin erhalten wir eine Warnung vom Modul [eslint]:

Dieser Fehler wird mit dem Modul [Quick fix] aus den Modulen [eslint] und [2] behoben.
Das Projekt wird erneut ausgeführt. Es treten keine Kompilierungsfehler mehr auf. Anschließend wird die Datei URL [http://localhost:81/nuxt-20/] mit einem Browser aufgerufen. Dabei tritt ein Ausführungsfehler auf:

Der Fehler befindet sich in [index.vue] [2]. Der Fehler [1] rührt daher, dass in [vuejs-22] die Ebene [dao] in [this.$dao] verfügbar war, während sie in [nuxt-12], dessen Infrastruktur wir übernommen haben, in der Funktion [this.$dao()] verfügbar ist.
Der Fehler befindet sich in der Funktion [created] des Seitenlebenszyklus [index]:

Vorerst begnügen wir uns damit, [created] in [created2] umzubenennen, damit die Lebenszyklusfunktion [created] nicht ausgeführt wird: [3].
Wir speichern die Änderung und laden die Seite [index] erneut im Browser. Diesmal klappt es:

17.3. Schritt 2
Die Seiten des Projekts [vuejs-22] verwendeten die folgenden eingefügten Elemente:
- $dao: für die Ebene [dao] des Clients [vue.js];
- $session: für eine im Browser unter [localStorage] gespeicherte Sitzung;
Diese Elemente existieren in der Infrastruktur des Projekts [nuxt-12], das wir kopiert haben, nicht mehr:
- Es gibt nun zwei Ebenen [dao], eine für den Client [nuxt] und eine für den Server [nuxt]. Beide sind über eine eingebundene Funktion namens [$dao] verfügbar. Das bedeutet, dass in den Seiten der Anwendung [this.$dao] durch [this.$dao()] ersetzt werden muss;
- die von der Anwendung [nuxt-20] verwaltete Sitzung [nuxt] hat nichts mehr mit dem Objekt [$session] derAnwendung [vuejs-22], in der es noch kein Konzept für Sitzungs-Cookies gab. Dennoch haben sie eine ähnliche Funktionalität: das Speichern persistenter Informationen im Verlauf der Benutzeraktionen. Die Sitzung [nuxt] speichert die Informationen im Speicher statt direkt in der Sitzung. Auf den Seiten der Anwendung muss [this.$session] durch [this.$store] ersetzt werden, wenn es darum geht, Informationen in der Sitzung zu speichern, und durch [this.$session()], wenn es darum geht, die Sitzung selbst zu bearbeiten;
- um den Status einer Eigenschaft P des Stores abzufragen, muss [this.$store.state.P] geschrieben werden;
- Um die Eigenschaft P des Stores zu ändern, muss [this.$store.commit(‘replace’, {P:value}] eingegeben werden;
Diese Änderungen nehmen wir auf der Seite [index] vor:
<!-- Definition der Ansicht HTML -->
<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" label-cols="3">
<!-- Benutzereingabefeld -->
<b-col cols="6">
<b-form-input id="user" v-model="user" type="text" placeholder="Nom d'utilisateur" />
</b-col>
</b-form-group>
<!-- 2. Zeile -->
<b-form-group label="Mot de passe" label-for="password" label-cols="3">
<!-- Eingabefeld „Passwort“ -->
<b-col cols="6">
<b-input id="password" v-model="password" type="password" placeholder="Mot de passe" />
</b-col>
</b-form-group>
<!-- 3. Zeile -->
<b-alert v-if="showError" show variant="danger" 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 cols="2">
<b-button :disabled="!valid" variant="primary" type="submit">Valider</b-button>
</b-col>
</b-row>
</b-form>
</template>
</Layout>
</template>
<!-- Dynamik der Ansicht -->
<script>
/* eslint-disable no-console */
import Layout from '@/components/layout'
export default {
// verwendete Komponenten
components: {
Layout
},
// Komponentenstatus
data() {
return {
// Benutzer
user: '',
// sein Passwort
password: '',
// Steuert die Anzeige einer Fehlermeldung
showError: false,
// die Fehlermeldung
message: ''
}
},
// berechnete Eigenschaften
computed: {
// Gültige Eingaben
valid() {
return this.user && this.password && this.$store.state.started
}
},
// Lebenszyklus: Die Komponente wurde gerade erstellt
mounted() {
// eslint-disable-next-line
console.log("Authentification mounted");
// Kann der Benutzer Simulationen durchführen?
if (this.$store.state.started && this.$store.state.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.$store.state.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.$store.commit('replace', { started: true })
console.log('[authentification], session=', this.$session())
})
// 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()
})
}
},
// Ereignisbehandler
methods: {
// ----------- Authentifizierung
async login() {
try {
// Wartephase beginnt
this.$emit('loading', true)
// noch nicht authentifiziert
this.$store.commit('replace', { 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.$store.commit('replace', { 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 {
// Speicher aktualisieren
this.$store.commit('replace', { métier: this.$métier })
// Die Sitzung wird gespeichert
this.$session().save()
}
}
}
}
</script>
Beachten Sie folgende Punkte:
- Zeile 69: Die Funktion [created2] wurde in [mounted] umbenannt, damit der Server [nuxt] sie nicht ausführt (er führt weder [beforeMount] noch [mounted] aus). Nur der Client [nuxt] wird sie ausführen, wie es bereits beim Beispiel [vuejs-22] der Fall war;
- Zeile 73: Es wird auf [this.$métier] verwiesen, das derzeit noch nicht existiert;
- Zeile 75: Wir haben diese Methode in einer Anwendung [nuxt] noch nie verwendet. Es muss geprüft werden, ob sie in einem [nuxt]-Kontext funktioniert;
- Zeile 112, 172: In [vuejs-22] wurde die Projektsitzung auf diese Weise gespeichert. Beim Projekt [nuxt-20] muss die Methode [save] den aktuellen Kontext erhalten. Es ist bekannt, dass auf einer Seite [nuxt] das Objekt [context] in [this.$nuxt.context] verfügbar ist;
Die Zeilen 112 und 172 werden daher wie folgt umgeschrieben:
this.$session().save(this.$nuxt.context)
Es ist zu beachten, dass dieser Code nicht optimiert ist. Anstatt die Funktion [this.$session()] mehrfach zu verwenden, wäre es besser, Folgendes zu schreiben:
const session=this.$session()
und anschließend die Variable [session] zu verwenden. Die gleiche Argumentation gilt für die Funktion [this.$dao()].
Nachdem diese Korrekturen vorgenommen wurden, können wir die Seiten URL und [http://localhost:81/nuxt-20/] erneut mit einem Browser aufrufen. Wir erhalten weiterhin dieselbe Seite wie zuvor:

Sehen wir uns die Browser-Protokolle an:

Das Protokoll [1] ist das letzte Protokoll, das vom Client [nuxt] erstellt wurde. In [2] sehen wir, dass die Eigenschaft [started] auf [vrai] gesetzt ist, was bedeutet, dass die Funktion [mounted] erfolgreich eine Sitzung jSON mit dem Steuerberechnungsserver gestartet hat. Außerdem ist zu erkennen, dass der Store Eigenschaften enthält, die entweder verworfen oder umbenannt werden müssen. Zur Erinnerung: Wir verwenden den Store aus dem Beispiel [nuxt-12].
Nun rufen wir die URL- und die [http://localhost:81/nuxt-20/]-Sitzung erneut auf, während der Steuerberechnungsserver nicht gestartet ist. Zunächst löschen wir das Cookie der Sitzung [nuxt]:

Der obige Screenshot stammt aus Chrome. Anschließend liefert die Eingabe von URL und [http://localhost:81/nuxt-20/] das folgende Ergebnis:

Der Fehler wurde vom Projekt [vuejs-22] korrekt behandelt. Er wird auch vom Projekt [nuxt-20] weiterhin korrekt behandelt.
17.4. Schritt 3
Nachdem wir nun die Anmeldeseite haben, müssen wir uns den Code ansehen, der ausgeführt wird, wenn der Benutzer auf die Schaltfläche [Valider] klickt:
// Ereignisbehandler
methods: {
// ----------- Authentifizierung
async login() {
try {
// Wartephase beginnt
this.$emit('loading', true)
// noch nicht authentifiziert
this.$store.commit('replace', { 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.$store.commit('replace', { 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)
// Analyse der Antwort
if (response2.état !== 1000) {
// Es wird folgende Fehlermeldung angezeigt
this.message = response2.réponse
this.showError = true
// Zurück 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 {
// Speicher aktualisieren
this.$store.commit('replace', { métier: this.$métier })
// Die Sitzung wird gespeichert
this.$session().save(this.$nuxt.context)
}
}
}
Das Hauptproblem scheint hier das Fehlen der Daten [this.$métier] zu sein. Um dies zu beheben, werden wir:
- die Klasse [Métier] aus dem Beispiel [vuejs-22] einbinden. Wir werden sie im Ordner [api] ablegen;
- eine Funktion [$métier] in den Kontext des Clients [nuxt] einfügen, die den Zugriff auf diese Klasse ermöglicht;
Zunächst das Kopieren der Klasse [Métier] in den Ordner [api]:

Sobald die Klasse [Métier] im Projekt vorhanden ist, erstellen wir ein neues Plugin für den Client [nuxt]. Dieses Plugin mit dem Namen [pluginMétier] fügt eine Funktion [$métier] ein, die Zugriff auf die Klasse [Métier] gewährt:
/* eslint-disable no-console */
// Es wird ein Zugriffspunkt auf die Schicht [métier] erstellt
import Métier from '@/api/client/Métier'
export default (context, inject) => {
// Instanziierung der Schicht [métier]
const métier = new Métier()
// Einfügen einer Funktion [$métier] in den Kontext
inject('métier', () => métier)
// Protokoll
console.log('[fonction client $métier créée]')
}
Nachdem dies geschehen ist, können wir die Seite [index] korrigieren:
// Lebenszyklus: Die Komponente wurde gerade erstellt
mounted() {
// eslint-disable-next-line
console.log("Authentification mounted");
// Kann der Benutzer Simulationen durchführen?
if (this.$store.state.started && this.$store.state.authenticated && this.$métier().taxAdminData) {
// Dann kann der Benutzer Simulationen durchführen
this.$router.push({ name: 'calcul-impot' })
// Zurück zur Ereignisschleife
return
}
// Wenn die Sitzung jSON bereits gestartet wurde, wird sie nicht erneut gestartet
...
},
// Ereignisbehandler
methods: {
// ----------- Authentifizierung
async login() {
try {
// Wartephase beginnt
this.$emit('loading', true)
// Es wurde noch keine Authentifizierung durchgeführt
this.$store.commit('replace', { 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.$store.commit('replace', { 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: 'calcul-impot' })
} catch (error) {
// Der Fehler wird an die übergeordnete Komponente weitergeleitet
this.$emit('error', error)
} finally {
// Speicher aktualisieren
this.$store.commit('replace', { métier: this.$métier() })
// Die Sitzung wird gespeichert
this.$session().save(this.$nuxt.context)
}
}
}
- Zeilen 43, 61, 69: [this.$métier] wurde durch [this.$métier()] ersetzt;
- Zeilen 8, 63: Der Name der Seite [CalculImpot] aus dem Projekt [vuejs-22] wurde in die Seite [calcul-impot] im Projekt [nuxt-20] geändert;
Nachdem diese Korrekturen vorgenommen wurden, kann versucht werden, die Anmeldeseite zu validieren:

Die resultierende Seite sieht wie folgt aus:

Wir haben die Seite zur Steuerberechnung erfolgreich aufgerufen. Sehen wir uns nun die Protokolle an:

In [2] ist zu sehen, dass die Authentifizierung korrekt gespeichert wurde. In [3-4] ist zu sehen, dass die Daten aus [taxAdminData] abgerufen wurden, die die Steuerberechnung durch die Klasse [Métier] ermöglichen.
17.5. Schritt 4
Sehen wir uns die Seite [calcul-impot] an, die wir erhalten haben:
<!-- 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>
<script>
// Importe
import FormCalculImpot from '@/components/form-calcul-impot'
import Menu from '@/components/menu'
import Layout from '@/components/layout'
export default {
// verwendete Komponenten
components: {
Layout,
FormCalculImpot,
Menu
},
// 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
}
},
// Lebenszyklus
created() {
// eslint-disable-next-line
console.log("CalculImpot created");
},
// Methoden zur Ereignisverwaltung
methods: {
// Ergebnis der Steuerberechnung
handleResultatObtenu(résultat) {
// Das Ergebnis wird als Zeichenkette aufgebaut HTML
const impôt = "Montant de l'impôt : " + résultat.impôt + ' euro(s)'
const décôte = 'Décôte : ' + résultat.décôte + ' euro(s)'
const réduction = 'Réduction : ' + résultat.réduction + ' euro(s)'
const surcôte = 'Surcôte : ' + résultat.surcôte + ' euro(s)'
const taux = "Taux d'imposition : " + résultat.taux
this.résultat = impôt + '<br/>' + décôte + '<br/>' + réduction + '<br/>' + surcôte + '<br/>' + taux
// Anzeige des Ergebnisses
this.résultatObtenu = true
// ---- Aktualisierung des Stores [Vuex]
// eine Simulation von +
this.$store.commit('addSimulation', résultat)
// Die Sitzung wird gespeichert
this.$session.save()
}
}
}
</script>
- Zeilen 44 und 48: Die Links im Navigationsmenü sind korrekt. Die Seite [/fin-session] existiert nicht. Das Projekt [vuejs-22] hat dieses Problem mithilfe von Weiterleitungen gelöst. Wir werden dasselbe mit dem Projekt [nuxt-20] tun;
- Zeile 76: Es wird auf eine Mutation [addSimulation] verwiesen, die derzeit noch nicht existiert. Wir werden sie anlegen;
- Zeile 78: Wie auf der Seite [index] muss hier [this.$session().save(this.$nuxt.context)] stehen;
Ändern wir den Store [store/index]. Er wurde vom Projekt [nuxt-12] übernommen und sieht derzeit wie folgt aus:
/* eslint-disable no-console */
// Status des Stores
export const state = () => ({
// Sitzung jSON gestartet
jsonSessionStarted: false,
// Benutzer authentifiziert
userAuthenticated: false,
// Sitzungs-Cookie PHP
phpSessionCookie: '',
// adminData
adminData: ''
})
// Änderungen im Store
export const mutations = {
// Ersetzen des Status
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
},
// Speicher zurücksetzen
reset() {
this.commit('replace', { jsonSessionStarted: false, userAuthenticated: false, phpSessionCookie: '', adminData: '' })
}
}
// Aktionen des Speichers
export const actions = {
nuxtServerInit(store, context) {
// Wer führt diesen Code aus?
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// Sitzung initialisieren
initStore(store, context)
}
}
function initStore(store, context) {
// Der Speicher ist der zu initialisierende Speicher
// Die Sitzung wird abgerufen
const session = context.app.$session()
// Wurde die Sitzung bereits initialisiert?
if (!session.value.initStoreDone) {
// Ein neuer Store wird gestartet
console.log("nuxtServerInit, initialisation d'un nouveau store")
// Der Store wird in die Sitzung aufgenommen
session.value.store = store.state
// Der Speicher ist nun initialisiert
session.value.initStoreDone = true
} else {
console.log("nuxtServerInit, reprise d'un store existant")
// Der Speicher wird mit dem Speicher der Sitzung aktualisiert
store.commit('replace', session.value.store)
}
// Die Sitzung wird gespeichert
session.save(context)
// Protokoll
console.log('initStore terminé, store=', store.state)
}
- Zeilen 3–27: Wir übernehmen den Status und die Änderungen aus der Anwendung [vuejs-22] (siehe Dokument [3]):
// Jalousiestatus
export const state = () => ({
// Sitzung jSON gestartet
started: false,
// Benutzer authentifiziert
authenticated: false,
// Sitzungs-Cookie PHP
phpSessionCookie: '',
// Liste der Simulationen
simulations: [],
// Nummer der letzten Simulation
idSimulation: 0,
// Ebene [métier]
métier: null
})
// Änderungen an der Jalousie
export const mutations = {
// Ersetzen des Zustands
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
},
// Zurücksetzen des Speichers
reset() {
this.commit('replace', { started: false, authenticated: false, phpSessionCookie: '', idSimulation: 0, simulations: [], métier: null })
},
// Löschen der Zeile mit Index-Nr.
deleteSimulation(state, index) {
// eslint-disable-next-line no-console
console.log('mutation deleteSimulation')
// Zeile Nr. [index] wird gelöscht
state.simulations.splice(index, 1)
console.log('store simulations', state.simulations)
},
// Hinzufügen einer Simulation
addSimulation(state, simulation) {
// eslint-disable-next-line no-console
console.log('mutation addSimulation')
// Nummer der Simulation
state.idSimulation++
simulation.id = state.idSimulation
// Die Simulation wird zur Simulationsliste hinzugefügt
state.simulations.push(simulation)
}
}
- Zeilen 4 und 6: Wir fügen die bereits verwendeten Eigenschaften ein;
- Zeile 8: Wir behalten das Sitzungs-Cookie PHP bei. Es ist unerlässlich, damit der Client und der Server [nuxt] dieselbe Sitzung PHP mit dem Steuerberechnungsserver haben;
- Zeile 10: Die Liste der vom Benutzer durchgeführten Simulationen;
- Zeile 12: die Nummer der letzten vom Benutzer durchgeführten Simulation;
- Zeile 14: die Ebene [métier];
- Zeilen 30–47: die im Speicher des Projekts [vuejs-22] vorhandenen Mutationen, auf die von den Seiten der Anwendung verwiesen wird. Das Projekt [vuejs-22] hatte eine Mutation namens [clear], die die Liste der Simulationen leerte. Diese wird nicht aufgenommen, da die bereits vorhandene Mutation [reset] ausreichen sollte;
- Zeilen 26–28: Die Mutation [reset] wird angepasst, um den neuen Inhalt des Zustands zu berücksichtigen;
Die Seite [calcul-impot] verwendet die folgende Komponente [form-calcul-impot]:
<!-- Definition HTML der Ansicht -->
<template>
<!-- Formular HTML -->
<b-form @submit.prevent="calculerImpot" class="mb-3">
<!-- Meldung in 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 id="enfants" v-model="enfants" :state="enfantsValide" type="text" placeholder="Indiquez votre nombre d'enfants"></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 id="salaire" v-model="salaire" :state="salaireValide" type="text" placeholder="Salaire annuel"></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 :disabled="formInvalide" type="submit" variant="primary">Valider</b-button>
</b-col>
</b-form>
</template>
<!-- Skript -->
<script>
export default {
// interner Status
data() {
return {
// verheiratet oder nicht
marié: 'non',
// Anzahl der Kinder
enfants: '',
// Jahresgehalt
salaire: ''
}
},
// berechneter interner Status
computed: {
// Formularfreigabe
formInvalide() {
return (
// Gehalt ungültig
!this.salaire.match(/^\s*\d+\s*$/) ||
// oder ungültige Kinderangaben
!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*$/))
}
},
// Lebenszyklus
created() {
// Protokoll
// eslint-disable-next-line
console.log("FormCalculImpot created");
},
// Ereignis-Handler
methods: {
calculerImpot() {
// Die Steuer wird mithilfe der Schicht [métier] berechnet
const résultat = this.$métier.calculerImpot(this.marié, Number(this.enfants), Number(this.salaire))
// eslint-disable-next-line
console.log("résultat=", résultat);
// Das Ergebnis wird vervollständigt
résultat.marié = this.marié
résultat.enfants = this.enfants
résultat.salaire = this.salaire
// das Ereignis [resultatObtenu] wird ausgelöst
this.$emit('resultatObtenu', résultat)
}
}
}
</script>
- Zeilen 65, 89: Die Referenz [this.$métier] muss in [this.$métier()] geändert werden;
Nachdem diese Korrekturen vorgenommen wurden, kann eine Simulation gestartet werden:

Man erhält folgende Antwort:

Wenn man sich die Protokolle ansieht:

- in [9-10] sieht man, dass sich die erste Simulation tatsächlich in [store] befindet;
- Bei [5] wurde die Nummer der letzten Simulation tatsächlich erhöht;
17.6. Schritt 5
Nachdem wir nun eine Simulation durchgeführt haben, klicken wir auf den Link [Liste des simulations]. Wir gelangen auf die folgende Seite:

Die Weiterleitung des Clients [nuxt] ist korrekt erfolgt. Sehen wir uns den Code der Seite [liste-des-simulations] an:
<!-- Definition HTML der Ansicht -->
<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 :items="simulations" :fields="fields" striped hover responsive>
<template v-slot:cell(action)="data">
<b-button @click="supprimerSimulation(data.index)" variant="link">Supprimer</b-button>
</template>
</b-table>
</template>
</template>
<!-- Navigationsmenü in der linken Spalte -->
<Menu slot="left" :options="options" />
</Layout>
</div>
</template>
<script>
// Importe
import Layout from '@/components/layout'
import Menu from '@/components/menu'
export default {
// Komponenten
components: {
Layout,
Menu
},
// interner Status
data() {
return {
// Optionen des Navigationsmenüs
options: [
{
text: "Calcul de l'impôt",
path: '/calcul-impot'
},
{
text: 'Fin de session',
path: '/fin-session'
}
],
// Tabellenparameter HTML
fields: [
{ label: '#', Schlüssel: 'id' },
{ label: 'Marié', key: 'marié' },
{ label: "Nombre d'enfants", key: 'enfants' },
{ label: 'Salaire', key: 'salaire' },
{ label: 'Impôt', key: 'impôt' },
{ label: 'Décôte', key: 'décôte' },
{ label: 'Réduction', key: 'réduction' },
{ label: 'Surcôte', key: 'surcôte' },
{ label: '', key: 'action' }
]
}
},
// berechneter interner Status
computed: {
// Liste der aus dem Vuex-Store abgerufenen Simulationen
simulations() {
return this.$store.state.simulations
}
},
// Lebenszyklus
created() {
// eslint-disable-next-line
console.log("ListeSimulations created");
},
// Methoden
methods: {
supprimerSimulation(index) {
// eslint-disable-next-line
console.log("supprimerSimulation", index);
// Entfernung der Simulation Nr. [index]
this.$store.commit('deleteSimulation', index)
// Die Sitzung wird gespeichert
this.$session.save()
}
}
}
</script>
- Zeilen 47–56: Die Ziele des Navigationsmenüs sind korrekt;
- Zeile 75: Der Store ist korrekt referenziert;
- Zeile 89: Es wird eine Mutation [deleteSimulation] verwendet, die wir im vorherigen Schritt integriert haben;
- Zeile 91: Diese Zeile muss in [this.$session().save(this.$nuxt.context)] umgeschrieben werden;
Wir nehmen die erforderlichen Änderungen vor und versuchen anschließend, die angezeigte Simulation zu löschen:

Daraufhin wird folgende Seite angezeigt:

Sehen wir uns die Protokolle an:

- In [6] sieht man, dass die Simulationstabelle leer ist;
Kehren wir nun zum Formular zur Steuerberechnung zurück:

Man gelangt auf die folgende Seite:

Die Weiterleitung hat also funktioniert.
17.7. Schritt 6
Nun müssen wir noch die Navigationsoption [Fin de session] im Navigationsmenü einrichten:
// Menüoptionen
options: [
{
text: 'Liste des simulations',
path: '/liste-des-simulations'
},
{
text: 'Fin de session',
path: '/fin-session'
}
]
- Zeile 9: Die Seite [/fin-session] existiert nicht. Das Projekt [vuejs-22] hat diesen Fall mit Routing-Regeln in einer Datei [router.js] behandelt:
// 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)
// Anwendungsrouten
const routes = [
// Authentifizierung
{ path: '/', name: 'authentification', component: Authentification },
{ path: '/authentification', name: 'authentification2', 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 von URL
mode: 'history',
// die Basis der Anwendung URL
base: '/client-vuejs-impot/'
})
// Routenprüfung
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
- Die Zeilen 64–76 behandelten den Sonderfall der Weiterleitung zum Pfad [/fin-session];
- Zeile 66: Die aktuelle Sitzung wird geleert;
- Zeilen 68–70: Die Ansicht [authentification] wird angezeigt;
Wir werden versuchen, etwas Ähnliches in der Routing-Datei des Clients [nuxt] zu implementieren:

Das Skript [client/routing.js] sieht nun wie folgt aus:
/* eslint-disable no-console */
export default function(context) {
// Wer führt diesen Code aus?
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// Verwaltung des Sitzungs-Cookies PHP im Browser
// Das Sitzungs-Cookie PHP des Browsers muss mit dem in der Nuxt-Sitzung gefundenen identisch sein
// Die Aktion [fin-session] erhält ein neues Cookie PHP (sowohl vom Server als auch vom Nuxt-Client)
// Wenn der Server es empfängt, muss der Client es an den Browser weiterleiten
// für den eigenen Datenaustausch mit dem Server PHP
// Hier handelt es sich um ein Client-Routing
// Das Sitzungs-Cookie wird abgerufen: PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// Falls vorhanden, wird das Sitzungs-Cookie PHP dem Browser zugewiesen
document.cookie = phpSessionCookie
}
// Wohin geht es weiter?
const to = context.route.path
if (to === '/fin-session') {
// Die Sitzung wird bereinigt
const session = context.app.$session()
session.reset(context)
// Weiterleitung zur Startseite
context.redirect({ name: 'index' })
}
}
- Wir haben die Zeilen [19-27] zum bestehenden Code hinzugefügt;
- Zeile 20: Man ruft [path] aus dem Ziel der aktuellen Route ab;
- Zeile 21: Es wird geprüft, ob es sich um [/fin-session] handelt. Wenn ja:
- Zeilen 23–24: Die Sitzung wird zurückgesetzt;
- Zeile 26: Der Client [nuxt] wird auf die Startseite umgeleitet;
Die Methode [session.reset(context)] (Zeile 24) der Sitzung lautet wie folgt:
// Die Sitzung wird zurückgesetzt
reset(context) {
console.log('nuxt-session reset')
// Speicher zurücksetzen
context.store.commit('reset')
// den neuen Speicher in der Sitzung speichern und die Sitzung speichern
this.save(context)
}
Die Methode [context.store.commit('reset')] (Zeile 5) lautet wie folgt:
// Speicher zurücksetzen
reset() {
this.commit('replace', { started: false, authenticated: false, phpSessionCookie: '', idSimulation: 0, simulations: [], métier: null })
}
Wenn man nun den Link [Fin de session] verwendet, wird die Startseite mit den folgenden Protokollen angezeigt:

- In [3] sieht man, dass man nicht mehr authentifiziert ist;
- in [4] sieht man, dass die Sitzung jSON gestartet wurde;
- In [6] ist die Ebene [métier] nicht mehr im Speicher vorhanden (sie ist weiterhin auf den Seiten mit [this.$métier()] vorhanden);
- in [5, 7] gibt es keine Simulationen mehr;
Man muss genau verstehen, was am Ende einer Sitzung geschieht:
- Die Sitzung [nuxt] wird zurückgesetzt: Die Eigenschaft [started] des Stores wechselt zu [false];
- es erfolgt eine Weiterleitung zur Seite [index];
- die Methode [mounted] der Seite [index] wird ausgeführt. Diese startet eine neue Sitzung jSON mit dem Steuerberechnungsserver. Wenn der Vorgang erfolgreich ist, wechselt die Eigenschaft [started] des Stores zu [true];
17.8. Schritt 7
Zu diesem Zeitpunkt verfügt die Anwendung [nuxt-20] über alle Funktionen der Anwendung [vuejs-22]. Die Portierung scheint abgeschlossen zu sein.
Wir werden im Sinne von [nuxt] noch einen Schritt weitergehen. Die Methode [mounted] der Seite [index] stellt ein Problem dar. Sie startet einen asynchronen Vorgang, auf dessen Ende eine Suchmaschine nicht warten wird. Es ist bekannt, dass in diesem Fall der asynchrone Vorgang in eine Funktion [asyncData] ausgelagert werden muss, da der Server [nuxt], der ihn ausführt, dann wartet, bis er abgeschlossen ist, bevor er die Seite an die Suchmaschine übergibt.
Wir nutzen hier die Funktion [asyncData], die in der Anwendung [nuxt-12] für die Seite [index] geschrieben wurde:
export default {
name: 'InitSession',
// verwendete Komponenten
components: {
Layout,
Navigation
},
// Asynchrone Daten
async asyncData(context) {
// Protokoll
console.log('[index asyncData started]')
// Man macht nichts zweimal, wenn die Seite bereits angefordert wurde
if (process.server && context.store.state.jsonSessionStarted) {
console.log('[index asyncData canceled]')
return { result: '[succès]' }
}
try {
// eine Sitzung wird gestartet jSON
const dao = context.app.$dao()
const response = await dao.initSession()
// Protokoll
console.log('[index asyncData response=]', response)
// Das Sitzungs-Cookie PHP wird für die nächsten Anfragen abgerufen
const phpSessionCookie = dao.getPhpSessionCookie()
// Das Sitzungs-Cookie PHP wird in der Sitzung [nuxt] gespeichert
context.store.commit('replace', { phpSessionCookie })
// Ist ein Fehler aufgetreten?
if (response.état !== 700) {
// Der Fehler befindet sich in response.réponse
throw new Error(response.réponse)
}
// Es ist zu vermerken, dass die Sitzung jSON gestartet wurde
context.store.commit('replace', { jsonSessionStarted: true })
// Das Ergebnis wird ausgegeben
return { result: '[succès]' }
} catch (e) {
// Protokoll
console.log('[index asyncData error=]', e)
// Es wird vermerkt, dass die Sitzung jSON nicht gestartet wurde
context.store.commit('replace', { jsonSessionStarted: false })
// Der Fehler wird gemeldet
return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
} finally {
// Der Speicher wird gesichert
const session = context.app.$session()
session.save(context)
// Protokoll
console.log('[index asyncData finished]')
}
},
// Lebenszyklus
beforeCreate() {
console.log('[index beforeCreate]')
},
created() {
console.log('[index created]')
},
beforeMount() {
console.log('[index beforeMount]')
},
mounted() {
console.log('[index mounted]')
// nur Client
if (this.showErrorLoading) {
console.log('[index mounted, showErrorLoading=true]')
this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
}
}
- Zeilen 13, 33, 40: Die Eigenschaft [jsonSessionStarted] muss in [started] geändert werden;
- Zeile 13: In der Anwendung [nuxt-12] führte nur der Server [nuxt] die Seite [index] und deren Funktion [asyncData] aus. Der Client [nuxt] führte die Seite [index] erst aus, nachdem er sie vom Server [nuxt] erhalten hatte, und führte dabei die Funktion [asyncData] nicht aus. In [nuxt-20] ist dies anders: Der Link [Fin de session] zeigt die Seite [index] in der Umgebung des Clients [nuxt] an. Die Funktion [asyncData] wird dann ausgeführt. Wenn man jedoch auf diese Weise zur Seite [index] gelangt, wurde die Sitzung [nuxt] inzwischen zurückgesetzt, und die Eigenschaft [started] des Stores hat den Wert [false], sodass die Bedingung in Zeile 13 zwangsläufig falsch ist. Man kann also [process.server] beibehalten, sodass der Client [nuxt] diesen Test nicht durchführt;
- Zeilen 15, 35, 42: Eine Eigenschaft [result] wird in die Eigenschaften [data] der Seite [index] eingefügt. In [nuxt-20] wird diese Eigenschaft nicht verwendet, daher entfernen wir sie aus dem von der Funktion zurückgegebenen Ergebnis;
- Zeilen 61–67: Diese Methode [mounted] muss beibehalten werden, da sie es dem Client [nuxt] ermöglicht, die Fehlermeldung anzuzeigen. Die Art und Weise, wie der Fehler behandelt wird, wird jedoch geändert;
Auf der aktuellen Seite [index] integrieren wir die oben genannte Funktion [asyncData] anstelle der alten Funktion [mounted] und fügen eine neue Funktion [mounted] hinzu. Der Code der Seite [index] aus dem Beispiel [nuxt-20] sieht dann wie folgt aus:
...
<!-- Dynamik der Ansicht -->
<script>
/* eslint-disable no-console */
import Layout from '@/components/layout'
export default {
// verwendete Komponenten
components: {
Layout
},
// Komponentenstatus
data() {
return {
// Benutzer
user: '',
// sein Passwort
password: '',
// Fehleranzeige
showError: false
}
},
// berechnete Eigenschaften
computed: {
// Gültige Eingaben
valid() {
return this.user && this.password && this.$store.state.started
}
},
// asynchrone Daten
async asyncData(context) {
// Protokoll
console.log('[index asyncData started]')
// Wird nicht doppelt ausgeführt, wenn die Seite bereits angefordert wurde
if (process.server && context.store.state.started) {
console.log('[index asyncData canceled]')
return
}
try {
// eine Sitzung wird gestartet jSON
const dao = context.app.$dao()
const response = await dao.initSession()
// Protokoll
console.log('[index asyncData response=]', response)
// Das Sitzungs-Cookie PHP wird für die nächsten Anfragen abgerufen
const phpSessionCookie = dao.getPhpSessionCookie()
// Das Sitzungs-Cookie PHP wird in der Sitzung [nuxt] gespeichert
context.store.commit('replace', { phpSessionCookie })
// Ist ein Fehler aufgetreten?
if (response.état !== 700) {
// Der Fehler befindet sich in response.réponse
throw new Error(response.réponse)
}
// Es ist zu beachten, dass die Sitzung jSON gestartet wurde
context.store.commit('replace', { started: true })
// kein Ergebnis
return
} catch (e) {
// Protokoll
console.log('[index asyncData error=]', e.message)
// Es wird vermerkt, dass die Sitzung jSON nicht gestartet wurde
context.store.commit('replace', { started: false })
// Der Fehler wird gemeldet
return { showErrorLoading: true, errorLoadingMessage: e.message }
} finally {
// Der Speicher wird gesichert
const session = context.app.$session()
session.save(context)
// Protokoll
console.log('[index asyncData finished]')
}
},
// Lebenszyklus
beforeCreate() {
console.log('[index beforeCreate]')
},
created() {
console.log('[index created]')
},
beforeMount() {
// nur Client
console.log('[index beforeMount]')
// Behandlung eines möglichen Fehlers
if (this.showErrorLoading) {
// Protokoll
console.log('[index beforeMount, showErrorLoading=true]')
// Der Fehler wird an die Hauptkomponente weitergeleitet [default]
this.$emit('error', new Error(this.errorLoadingMessage))
}
},
mounted() {
console.log('[index mounted]')
},
// Ereignisbehandler
methods: {
// ----------- Authentifizierung
async login() {
...
}
</script>
- Zeilen 58, 65: Die Funktion [asyncData] macht die Eigenschaft [result] hier nicht mehr ungenutzt;
- Zeile 81: Die Methode [beforeMount] des Clients [nuxt]. Sie wurde der Methode [mounted] vorgezogen, um einen möglichen Fehler von [asyncData] zu behandeln;
- Zeile 85: Es wird geprüft, ob die Eigenschaft [errorLoading] gesetzt wurde. Sie kann nur durch die Funktion [asyncData] gesetzt werden;
- Zeilen 85–90: Wenn die Funktion [asyncData] einen Fehler gemeldet hat, wird sie über das Ereignis [error] an die Seite [default] weitergeleitet. So hat die alte Funktion [created], die wir gerade ersetzt haben, einen möglichen Fehler behandelt;
Führen wir einige Tests durch.
Zunächst löschen wir das Sitzungs-Cookie [nuxt] und das Sitzungs-Cookie PHP, sofern diese vorhanden sind. Anschließend rufen wir die Seite „[http://localhost:81/nuxt-20/]“ auf, während der Server zur Steuerberechnung nicht gestartet ist. Wir erhalten die folgende Seite:

Wir laden dieselbe Seite erneut, nachdem wir den Steuerberechnungsserver gestartet haben:

Sehen wir uns die Protokolle an:

- In [2-3] sehen wir, dass die Sitzung jSON gestartet wurde;
- in [4] sieht man das Sitzungs-Cookie PHP, das der Server [nuxt] beim Austausch mit dem Steuerberechnungsserver abgerufen hat. Der Client [nuxt] wird es nun verwenden;
Nun wollen wir uns identifizieren:

Wir erhalten die folgende Seite:

Bei [1] haben wir eine Fehlermeldung erhalten. Das bedeutet, dass der Browser nicht das richtige Cookie der Sitzung PHP gesendet hat, die im vorherigen Schritt vom Server [nuxt] gestartet wurde. In [nuxt-12] erfolgte die Weitergabe des Sitzungs-Cookies PHP vom Server [nuxt] an den Client [nuxt] im Routing des Clients [nuxt] des Skripts [middleware/client/routing] :
/* eslint-disable no-console */
export default function(context) { // Wer führt diesen Code aus?
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// Verwaltung des Sitzungs-Cookies PHP im Browser
// Das Sitzungs-Cookie PHP des Browsers muss mit dem in der Nuxt-Sitzung gefundenen identisch sein
// Die Aktion [fin-session] erhält ein neues Cookie PHP (sowohl vom Server als auch vom Nuxt-Client)
// Wenn der Server es empfängt, muss der Client es an den Browser weiterleiten
// für seine eigenen Datenaustausche mit dem Server PHP
// Hier handelt es sich um ein Client-Routing
// Das Sitzungs-Cookie wird abgerufen: PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// Falls vorhanden, wird das Sitzungs-Cookie PHP dem Browser zugewiesen
document.cookie = phpSessionCookie
}
// Wohin geht es weiter?
const to = context.route.path
if (to === '/fin-session') {
// Die Sitzung wird bereinigt
const session = context.app.$session()
session.reset(context)
// Weiterleitung zur Startseite
context.redirect({ name: 'index' })
}
}
Die Zeilen 13–17 ermöglichen es dem Client [nuxt], das Sitzungs-Cookie PHP vom Server [nuxt] abzurufen.
Das Problem hierbei ist, dass beim Klicken auf die Schaltfläche [Valider] keine Weiterleitung des Clients [nuxt] erfolgt. Seine Weiterleitungsfunktion wird daher nicht aufgerufen. Das Problem wird behoben, indem die Zeilen 12–17 am Anfang der Authentifizierungsmethode der Seite [index] dupliziert werden:
// Ereignisbehandler
methods: {
// ----------- Authentifizierung
async login() {
// Das Sitzungs-Cookie PHP wird aus dem Speicher abgerufen
const phpSessionCookie = this.$store.state.phpSessionCookie
if (phpSessionCookie) {
// Falls vorhanden, wird das Sitzungs-Cookie PHP dem Browser zugewiesen
document.cookie = phpSessionCookie
}
try {
// Wartephase beginnt
this.$emit('loading', true)
// Man ist noch nicht authentifiziert
In den Zeilen 5–10 wird das Cookie der Sitzung PHP, die vom Server [nuxt] initiiert wurde, aus dem Speicher abgerufen. Nach dieser Änderung wird die Seite zur Steuerberechnung korrekt angezeigt, was bedeutet, dass die Authentifizierung funktioniert hat.
17.9. Schritt 8
Wir haben nun eine funktionsfähige Anwendung, die im Sinne von [nuxt] arbeitet. Wie bereits bei der Anwendung [nuxt-13] werden wir uns nun mit der Navigation des Servers [nuxt] befassen. Wie bereits erwähnt, soll der Benutzer die URL der Anwendung nicht manuell eingeben. Er soll die ihm angezeigten Links verwenden, die vom Client [nuxt] ausgeführt werden, der dann im Modus SPA läuft. Dennoch werden wir sicherstellen, dass die Navigation auf dem Server [nuxt] die Anwendung stets in einem stabilen Zustand belässt.
Aus der für [nuxt-13] durchgeführten Untersuchung (siehe Abschnitt „Link“) wissen wir, dass Folgendes erforderlich ist:
- das Skript [midleware/routing] ändern;
- ein Skript [middleware/server/routing] hinzufügen;

Das Skript [middleware/routing] wird wie folgt geändert:
/* eslint-disable no-console */
// Die Server- und Client-Middleware wird importiert
import serverRouting from './server/routing'
import clientRouting from './client/routing'
export default function(context) {
// Wer führt diesen Code aus?
console.log('[middleware], process.server', process.server, ', process.client=', process.client)
if (process.server) {
// Server-Routing
serverRouting(context)
} else {
// Client-Routing
clientRouting(context)
}
}
- Zeile 4: Das Routing-Skript des Servers [nuxt] wird importiert;
- Zeilen 10–12: Wenn der Server [nuxt] den Code ausführt, wird dessen Routing-Funktion verwendet;
Das Skript [middleware/server/routing] lautet wie folgt:
/* eslint-disable no-console */
export default function(context) {
// Wer führt diesen Code aus?
console.log('[middleware server], process.server', process.server, ', process.client=', process.client)
// Wir sammeln hier und da ein paar Informationen
const store = context.store
// Woher kommen wir?
const from = store.state.from || 'nowhere'
// Wohin gehen wir?
let to = context.route.name
// Sonderfall von /fin-session, der kein Attribut [name] hat
if (context.route.path === '/fin-session') {
to = 'fin-session'
}
// Mögliche Weiterleitung
let redirection = ''
// Routing-Verarbeitung abgeschlossen
let done = false
// Befindet man sich bereits in einer Weiterleitung des Servers [nuxt]?
if (store.state.serverRedirection) {
// keine Aktion erforderlich
done = true
}
// Handelt es sich um ein Neuladen der Seite?
if (!done && from === to) {
// Nichts zu tun
done = true
}
// Überprüfung der Servernavigation [nuxt]
// Es wird das Navigationsmenü des Clients übernommen
// zuerst wird der Fall „Sitzungsende“ behandelt
if (!done && store.state.started && store.state.authenticated && to === 'fin-session') {
// Die Sitzung wird bereinigt
const session = context.app.$session()
session.reset(context)
// Weiterleitung zur Startseite
redirection = 'index'
// Auftrag abgeschlossen
done = true
}
// Fall, in dem die Sitzung PHP nicht gestartet wurde
if (!done && !store.state.started && to !== 'index') {
// Weiterleitung zu [index]
redirection = 'index'
// Auftrag abgeschlossen
done = true
}
// Fall, in dem der Benutzer nicht authentifiziert ist
if (!done && store.state.started && !store.state.authenticated && to !== 'index') {
redirection = 'index'
// Auftrag abgeschlossen
done = true
}
// Fall, in dem [adminData] nicht abgerufen wurde
if (!done && store.state.started && store.state.authenticated && !store.state.métier.taxAdminData && to !== 'index') {
// Weiterleitung zu [index]
redirection = 'index'
// Auftrag abgeschlossen
done = true
}
// Falls [adminData] abgerufen wurde
if (
!done &&
store.state.started &&
store.state.authenticated &&
store.state.métier.taxAdminData &&
to !== 'calcul-impot' &&
to !== 'liste-des-simulations'
) {
// man bleibt auf derselben Seite
redirection = from
// Arbeit abgeschlossen
done = true
}
// Normalerweise wurden alle Prüfungen durchgeführt ---------------------
// Weiterleitung?
if (redirection) {
// Die Weiterleitung wird im Store vermerkt
store.commit('replace', { serverRedirection: true })
} else {
// keine Weiterleitung
store.commit('replace', { serverRedirection: false, from: to })
}
// Der Speicher wird in der Sitzung gespeichert [nuxt]
const session = context.app.$session()
session.value.store = store.state
session.save(context)
// Es erfolgt eine eventuelle Weiterleitung vom Server [nuxt]
if (redirection) {
context.redirect({ name: redirection })
}
}
- In diesem Skript greifen wir die Ideen auf, die bereits bei der Weiterleitung des Servers [nuxt] der Anwendung [nuxt-13] entwickelt und verwendet wurden;
- Wir fügen dem Store der Anwendung zwei Eigenschaften hinzu:
- [from]: der Name der zuletzt angezeigten Seite. Wir wissen, dass der Client [nuxt] über diese Information verfügt, der Server [nuxt] jedoch nicht. Wir werden diese Information hinzufügen, indem wir bei jeder Weiterleitung des Servers [nuxt] den Namen der Seite, die angezeigt werden soll, im Speicher ablegen. Das Gleiche tun wir bei jeder Weiterleitung des Clients [nuxt]. So findet der Server [nuxt] bei der nächsten Weiterleitung im Speicher den Namen der zuletzt von der Anwendung angezeigten Seite;
- [serverRedirection]: Wenn ein Routing-Ziel vom Server [nuxt] abgelehnt wird, führt dieser eine Weiterleitung durch. Er vermerkt dann im Speicher, dass das nächste Ziel des Servers [nuxt] eine Weiterleitungsseite ist. Diese Weiterleitung löst eine erneute Ausführung des Routers des Servers [nuxt] aus. Stellt dieser fest, dass das aktuelle Ziel aus einer Weiterleitung stammt, lässt er den Vorgang zu;
- Zeilen 6–11: Es werden die für das Routing relevanten Informationen abgerufen;
- Zeilen 13–16: Das Ziel [/fin-session] ist keiner Seite zugeordnet, die [fin-session] heißen würde. Es hat also keinen Namen. Wir weisen ihm einen zu;
- Zeile 19: das Ziel einer möglichen Weiterleitung;
- Zeile 21: [done=true], sobald die Weiterleitungstests abgeschlossen sind;
- Zeilen 23–27: Wie bereits erwähnt, ist nichts zu tun, wenn das aktuelle Routing aus einer Weiterleitung resultiert. Denn beim vorherigen Routing hat der Router entschieden, dass der Client-Browser weitergeleitet werden muss. Es besteht kein Grund, diese Entscheidung zu überdenken;
- Zeilen 29–33: Handelt es sich um ein Neuladen der Seite, lässt man den Vorgang zu. Dies ist kein für alle Anwendungen [nuxt] geltendes Axiom: Die Auswirkungen eines Neuladens müssen für jede Seite einzeln geprüft werden. In diesem Fall hat das Neuladen der Seiten [index, calcul-impot, liste-des-simulations] keine unerwünschten Auswirkungen;
- Zeilen 35–85: Die Routing-Konfiguration des Servers [nuxt] übernimmt die des Clients [nuxt]. Befindet man sich auf einer Seite, muss das Routing des Servers [nuxt] das Navigationsmenü widerspiegeln, das der Client [nuxt] auf dieser Seite anbietet;
- Zeilen 38–47: Zunächst wird der Fall behandelt, dass das Ziel [fin-session] keiner vorhandenen Seite entspricht. Sind die Bedingungen erfüllt (Sitzung gestartet, Benutzer authentifiziert), wird die Sitzung zurückgesetzt und der Benutzer auf die Seite [index] weitergeleitet;
- Zeilen 49–55: Wenn die Sitzung jSON mit dem Steuerberechnungsserver noch nicht gestartet wurde, ist die Seite [index] das einzig mögliche Ziel;
- Zeilen 57–62: Wenn die Sitzung jSON gestartet wurde und der Benutzer nicht authentifiziert ist und die Authentifizierungsseite nicht angefordert hat, wird er auf die Authentifizierungsseite weitergeleitet, nämlich die Seite [index];
- Zeilen 64–70: Wenn der Benutzer authentifiziert ist, die Daten [adminData] jedoch nicht abgerufen wurden, erfolgt eine Weiterleitung zur Authentifizierungsseite. Die Authentifizierung erfüllt zwei Aufgaben: Sie führt die Authentifizierung durch und fordert bei erfolgreicher Authentifizierung zusätzlich die Daten „[adminData]“ an. Wurden diese nicht abgerufen, muss die Authentifizierung wiederholt werden;
- Zeilen 72–85: Wenn die Daten „[adminData]“ erhalten wurden, sind die einzigen möglichen Ziele „[calcul-impot]“ und „[liste-des-simulations]“. Ist dies nicht der Fall, wird die Weiterleitung abgelehnt;
- Zeilen 88–95: Der Speicher wird je nachdem aktualisiert, ob eine Umleitung erfolgt oder nicht;
- Zeile 94: Es findet keine Weiterleitung statt. Daher wird das aktuelle [to] zum [from] der nächsten Weiterleitung;
- Zeilen 96–99: Die Informationen aus dem Store werden im Sitzungs-Cookie [nuxt] gespeichert;
- Zeilen 100–103: Wenn eine Weiterleitung erforderlich ist, wird sie durchgeführt;
Um die Tests durchzuführen, muss darauf geachtet werden, dass man von einer leeren Situation ausgeht, indem man das Sitzungs-Cookie [nuxt] und das Sitzungs-Cookie PHP auf dem Steuerberechnungsserver löscht:

Um das Routing des Servers [nuxt] zu testen, probieren Sie auf jeder Seite alle möglichen URL- und [/, /calcul-impot, /liste-des-simulations]-Werte aus. Die Anwendung muss dabei jedes Mal in einem konsistenten Zustand bleiben.
17.10. Schritt 9
In Schritt 9 erfolgt die Bereitstellung der Anwendung [nuxt-20]. Hierfür ist ein Hosting-Anbieter erforderlich, der eine [node.js]-Umgebung zur Ausführung des Servers [nuxt] bereitstellt. Ich verfüge nicht darüber. Der Leser kann die im Abschnitt „Link“ beschriebenen Schritte befolgen, um die Anwendung [nuxt-20] auf seinem Entwicklungsrechner bereitzustellen und sie mit einem Protokoll HTTPS zu sichern.
17.11. Conclusion
Die Portierung der Anwendung [vuejs-22] auf die Anwendung [nuxt-20] ist nun abgeschlossen. Hier einige wichtige Punkte zu dieser Portierung:
- Die Seiten von [vuejs-22] wurden beibehalten;
- die asynchronen Vorgänge, die in den Seiten von [vuejs-22] vorhanden waren, wurden in eine Funktion [asyncData] migriert;
- in [nuxt-20] mussten zwei Entitäten verwaltet werden: der Client [nuxt] und der Server [nuxt]. Letztere Entität existierte in [vuejs-22] nicht. Um die Konsistenz zwischen den beiden Entitäten zu gewährleisten, benötigten wir eine Sitzung [nuxt];
- wir mussten das Routing des Servers [nuxt] verwalten;
In der Praxis ist es zweifellos vorteilhafter, direkt mit einer Architektur [nuxt] zu beginnen, als eine Architektur [vue.js] zu erstellen, die anschließend in eine Umgebung [nuxt] übertragen wird.