17. Beispiel [nuxt-20]: Portierung des [vuejs-22]-Beispiels
17.1. Einleitung
Hier schlagen wir vor, das [vuejs-22]-Beispiel, bei dem es sich um eine [vue.js]-Anwendung vom Typ SPA handelte, in einen [nuxt]-SSR-Kontext zu portieren. [vuejs-22] war eine Client-Anwendung für den Steuerberechnungsserver, die die folgenden Ansichten darstellte:
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 Simulation Nr. 1 gelöscht werden kann. Dies führt zu folgender Ansicht:

Wenn wir nun die letzte Simulation löschen, erhalten wir die folgende neue Ansicht:

Wir werden die [vuejs-22]-Anwendung schrittweise auf die [nuxt-20]-Anwendung umstellen. Wir werden den Code aus [vuejs-22] nicht erneut erläutern. Den Lesern wird empfohlen, das Dokument |Einführung in das VUE.JS-Framework anhand von Beispielen| durchzulesen. Die verschiedenen Schritte sollten die Unterschiede zwischen einer [vuejs]-Anwendung und einer [nuxt]-Anwendung verdeutlichen.
17.2. Schritt 1
Das [nuxt-20]-Projekt wird zunächst durch Klonen des [nuxt-12]-Projekts erstellt. Dies ist in der Tat ein guter Ausgangspunkt:
- Es kann mit dem Steuerberechnungsserver kommunizieren;
- es verarbeitet die vom Server gesendeten Fehler korrekt;
- der [Nuxt]-Client und der Server können über eine [Nuxt]-Sitzung kommunizieren;
Wir verfügen somit über eine solide Ausgangsinfrastruktur. Unsere Hauptaufgabe sollte darin bestehen, Folgendes anzupassen:
- die Seiten. Wir werden die aus dem [vuejs-22]-Projekt verwenden, die an die neue Umgebung angepasst werden müssen;
- Speicherverwaltung. Es sollten zusätzliche Informationen angezeigt werden (Liste der Simulationen), während andere Informationen möglicherweise überflüssig werden;
- Client- und Server-Routing-Verwaltung [nuxt];
Zunächst erstellen wir also das [nuxt-20]-Projekt, indem wir das [nuxt-12]-Projekt klonen:

Anschließend entfernen wir die Seiten und Komponenten, die nicht mehr benötigt werden [2]:
- Die Komponente [components/navigation] verschwindet;
- das Layout [layout/default] verschwindet;
- die Seiten [index, authentication, get-admindata, end-session] werden entfernt;
Anschließend integrieren wir Elemente aus [vuejs-22] in [nuxt-20] [3]:
- Die drei Seiten [Authentication, TaxCalculation, SimulationList] aus der Anwendung [vuejs-22] werden in den Ordner [pages] verschoben;
- Die Komponenten [FormCalculImpot, Menu, Layout] aus der [vuejs-22]-Anwendung kommen in den Ordner [components];
- die Seite [Main] aus [vuejs-22], die als [Layout] für die [vuejs-22]-Anwendung diente, kommt in den Ordner [layouts];
Wir benennen die integrierten Elemente [4] um:

- in [layouts] wurde [Main] zu [default], da dies der Standardname für das Layout einer [nuxt]-Anwendung ist;
- in [pages] wurde die Seite [Authentication] zu [index], da [Authentication] diese Rolle in der [vuejs-22]-Anwendung innehatte;
An dieser Stelle können wir das Projekt kompilieren, um die ersten Fehler zu sehen. Wir ändern die Datei [nuxt.config] aus dem [nuxt-12]-Beispiel so, dass 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: [
// Doc: https://github.com/nuxt-community/eslint-module
'@nuxtjs/eslint-module'
],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://bootstrap-vue.js.org
'bootstrap-vue/nuxt',
// Doc: 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) {}
},
// source code directory
srcDir: 'nuxt-20',
// router
router: {
// application URL root
base: '/nuxt-20/',
// routing middleware
middleware: ['routing']
},
// server
server: {
// service port, default 3000
port: 81,
// network addresses listened to, default localhost: 127.0.0.1
// 0.0.0.0 = all the machine's network addresses
host: 'localhost'
},
// environment
env: {
// axios configuration
timeout: 2000,
withCredentials: true,
baseURL: 'http://localhost/php7/scripts-web/impots/version-14',
// session cookie configuration [nuxt]
maxAge: 60 * 5
}
}
Als Nächstes [bauen] wir das Projekt:

Die folgenden Fehler werden gemeldet:
- Der Fehler in Zeile 1 weist darauf hin, dass auf ein nicht vorhandenes Bild verwiesen wird. Wir werden es in [vuejs-22] abrufen;
- 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 [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] weisen darauf hin, dass die Komponente [./Menu] nicht existiert. Tatsächlich heißt sie nun [@/components/menu];
Wir fügen das Bild [assets/logo.jpg] zum Projekt [nuxt-20] hinzu:

Außerdem korrigieren wir die Komponentenpfade auf allen Seiten. Nehmen wir als Beispiel die Seite [calcul-import]:
<!-- 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>
<script>
// imports
import FormCalculImpot from './FormCalculImpot'
import Menu from './Menu'
import Layout from './Layout'
export default {
// composants utilisés
components: {
Layout,
FormCalculImpot,
Menu
},
Die drei [import]-Anweisungen in den Zeilen 26–28 lauten nun:
// imports
import FormCalculImpot from '@/components/form-calcul-impot'
import Menu from '@/components/menu'
import Layout from '@/components/layout'
Überprüfen Sie die [import]-Anweisungen für alle Komponenten, Layouts und Seiten und korrigieren Sie sie gegebenenfalls. Sobald diese Korrekturen vorgenommen wurden, können Sie einen neuen [Build] versuchen. Normalerweise sollten dann keine Fehler mehr auftreten.
Anschließend können Sie versuchen, die Anwendung auszuführen:

Es treten Fehler auf:
Wir sehen, dass der Befehl [dev] in Kombination mit dem Modul [eslint] syntaktisch gesehen strenger ist als der Befehl [build]. Hier muss der Vergleichsoperator [!=] als [!==] geschrieben werden, was ein strengerer Operator ist (er prüft auch den Typ der Operanden). Diese Fehler treten auf der Seite [index.vue] auf.
Wir korrigieren die oben genannten Fehler und führen das Projekt erneut aus. Daraufhin erhalten wir eine Warnung vom [eslint]-Modul:

Wir beheben diesen Fehler mithilfe der [Schnellkorrektur] aus dem [eslint]-Modul [2].
Wir starten das Projekt neu. Es gibt keine Kompilierungsfehler mehr. Anschließend rufen wir die URL [http://localhost:81/nuxt-20/] in einem Browser auf. Wir erhalten einen Laufzeitfehler:

Der Fehler befindet sich in [index.vue] [2]. Der Fehler [1] rührt daher, dass in [vuejs-22] die [dao]-Ebene 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 Lebenszyklus der Seite [index]:

Vorerst benennen wir [created] einfach in [created2] um, damit die Lebenszyklusfunktion [created] nicht ausgeführt wird [3].
Wir speichern die Änderung und laden die Seite [index] im Browser neu. Diesmal funktioniert es:

17.3. Schritt 2
Die Seiten des [vuejs-22]-Projekts verwendeten die folgenden eingefügten Elemente:
- $dao: für die [dao]-Ebene des [vue.js]-Clients;
- $session: für eine im [localStorage] des Browsers gespeicherte Sitzung;
Diese Elemente existieren in der von uns kopierten Infrastruktur des [nuxt-12]-Projekts nicht mehr:
- Es gibt nun zwei [dao]-Schichten, eine für den [nuxt]-Client und eine für den [nuxt]-Server. Beide sind über eine injizierte Funktion namens [$dao] verfügbar. Das bedeutet, dass in den Seiten der Anwendung [this.$dao] durch [this.$dao()] ersetzt werden muss;
- Die von der [nuxt-20]-Anwendung verwaltete [nuxt]-Sitzung hat nichts mehr mit dem [$session]-Objekt aus der [vuejs-22]-Anwendung zu tun, in der es kein Konzept von Sitzungscookies gab. Dennoch dienen sie einem ähnlichen Zweck: dem Speichern persistenter Informationen, während der Benutzer mit der Anwendung interagiert. Die [nuxt]-Sitzung speichert Informationen im Store und nicht direkt in der Sitzung. Auf den Seiten der Anwendung muss [this.$session] durch [this.$store] ersetzt werden, wenn Informationen in der Sitzung gespeichert werden, und durch [this.$session()], wenn die Sitzung selbst bearbeitet wird;
- um den Status einer Eigenschaft P im Store zu überprüfen, müssen Sie [this.$store.state.P] schreiben;
- Um die Eigenschaft P des Stores zu ändern, müssen Sie [this.$store.commit('replace', {P:value}] schreiben
Wir nehmen diese Änderungen auf der Seite [index] vor:
<!-- définition HTML de la vue -->
<template>
<Layout :left="false" :right="true">
<template slot="right">
<!-- formulaire HTML - on poste ses valeurs avec l'action [authentifier-utilisateur] -->
<b-form @submit.prevent="login">
<!-- titre -->
<b-alert show variant="primary">
<h4>Bienvenue. Veuillez vous authentifier pour vous connecter</h4>
</b-alert>
<!-- 1ère ligne -->
<b-form-group label="Nom d'utilisateur" label-for="user" label-cols="3">
<!-- zone de saisie user -->
<b-col cols="6">
<b-form-input id="user" v-model="user" type="text" placeholder="Nom d'utilisateur" />
</b-col>
</b-form-group>
<!-- 2ième ligne -->
<b-form-group label="Mot de passe" label-for="password" label-cols="3">
<!-- zone de saisie password -->
<b-col cols="6">
<b-input id="password" v-model="password" type="password" placeholder="Mot de passe" />
</b-col>
</b-form-group>
<!-- 3ième ligne -->
<b-alert v-if="showError" show variant="danger" class="mt-3">L'erreur suivante s'est produite : {{ message }}</b-alert>
<!-- bouton de type [submit] sur une 3ième ligne -->
<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>
<!-- dynamique de la vue -->
<script>
/* eslint-disable no-console */
import Layout from '@/components/layout'
export default {
// components used
components: {
Layout
},
// component status
data() {
return {
// user
user: '',
// password
password: '',
// controls the display of an error msg
showError: false,
// the error message
message: ''
}
},
// calculated properties
computed: {
// valid entries
valid() {
return this.user && this.password && this.$store.state.started
}
},
// life cycle: the component has just been created
mounted() {
// eslint-disable-next-line
console.log("Authentification mounted");
// can the user run simulations?
if (this.$store.state.started && this.$store.state.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 is not restarted again
if (!this.$store.state.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.$store.commit('replace', { started: true })
console.log('[authentification], session=', this.$session())
})
// 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()
})
}
},
// event managers
methods: {
// ----------- authentication
async login() {
try {
// start waiting
this.$emit('loading', true)
// you are not yet authenticated
this.$store.commit('replace', { 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.$store.commit('replace', { 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 store
this.$store.commit('replace', { métier: this.$métier })
// save the session
this.$session().save()
}
}
}
}
</script>
Beachten Sie folgende Punkte:
- Zeile 69: Die Funktion [created2] wurde in [mounted] umbenannt, damit der [nuxt]-Server sie nicht ausführt (er führt weder [beforeMount] noch [mounted] aus). Nur der [nuxt]-Client führt sie aus, wie es auch beim [vuejs-22]-Beispiel der Fall war;
- Zeile 73: Wir verweisen auf [this.$business], das derzeit nicht existiert;
- Zeile 75: Wir haben diese Methode noch nie in einer [nuxt]-Anwendung verwendet. Wir müssen prüfen, ob sie im [nuxt]-Kontext funktioniert;
- Zeilen 112, 172: In [vuejs-22] wurde die Projektsitzung auf diese Weise gespeichert. Beim [nuxt-20]-Projekt muss die [save]-Methode den aktuellen Kontext erhalten. Wir wissen, dass in einer [nuxt]-Seite das [context]-Objekt unter [this.$nuxt.context] verfügbar ist;
Die Zeilen 112 und 172 werden daher wie folgt umgeschrieben:
this.$session().save(this.$nuxt.context)
Beachten Sie, 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 dann die Variable [session] zu verwenden. Das Gleiche gilt für die Funktion [this.$dao()].
Nachdem diese Korrekturen vorgenommen wurden, können wir die URL [http://localhost:81/nuxt-20/] in einem Browser neu laden. Wir erhalten immer noch dieselbe Seite wie zuvor:

Schauen wir uns die Browser-Protokolle an:

Log [1] ist das letzte vom [nuxt]-Client generierte Log. In [2] sehen wir, dass die Eigenschaft [started] auf [true] gesetzt ist, was bedeutet, dass die Funktion [mounted] erfolgreich eine JSON-Sitzung mit dem Steuerberechnungsserver gestartet hat. Wir sehen auch, dass der Store Eigenschaften enthält, die entweder verworfen oder umbenannt werden müssen. Denken Sie daran, dass wir den Store aus dem [nuxt-12]-Beispiel verwenden.
Rufen wir nun die URL [http://localhost:81/nuxt-20/] erneut auf, während der Steuerberechnungsserver nicht läuft. Zunächst stellen wir sicher, dass das [nuxt]-Sitzungscookie gelöscht wird:

Der obige Screenshot stammt aus Chrome. Sobald dies erledigt ist, liefert die URL [http://localhost:81/nuxt-20/] das folgende Ergebnis:

Der Fehler wurde vom [vuejs-22]-Projekt korrekt behandelt. Er wird auch weiterhin vom [nuxt-20]-Projekt korrekt behandelt.
17.4. Schritt 3
Da wir nun die Authentifizierungsseite haben, müssen wir uns den Code ansehen, der ausgeführt wird, wenn der Benutzer auf die Schaltfläche [Validate] klickt:
// event managers
methods: {
// ----------- authentication
async login() {
try {
// start waiting
this.$emit('loading', true)
// you are not yet authenticated
this.$store.commit('replace', { 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.$store.commit('replace', { 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 store
this.$store.commit('replace', { métier: this.$métier })
// save the session
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 [$métier]-Funktion in den [nuxt]-Client-Kontext einbinden, die den Zugriff auf diese Klasse ermöglicht;
Kopieren Sie zunächst die Klasse [Métier] in den Ordner [api]:

Sobald sich die Klasse [Métier] im Projekt befindet, erstellen wir ein neues Plugin für den [nuxt]-Client. 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 */
// on crée un point d'accès à la couche [métier]
import Métier from '@/api/client/Métier'
export default (context, inject) => {
// instanciation de la couche [métier]
const métier = new Métier()
// injection d'une fonction [$métier] dans le contexte
inject('métier', () => métier)
// log
console.log('[fonction client $métier créée]')
}
Nachdem dies erledigt ist, können wir die [Index]-Seite aktualisieren:
// life cycle: the component has just been created
mounted() {
// eslint-disable-next-line
console.log("Authentification mounted");
// can the user run simulations?
if (this.$store.state.started && this.$store.state.authenticated && this.$métier().taxAdminData) {
// then the user can run simulations
this.$router.push({ name: 'calcul-impot' })
// return to event loop
return
}
// if the jSON session has already been started, it is not restarted again
...
},
// event managers
methods: {
// ----------- authentication
async login() {
try {
// start waiting
this.$emit('loading', true)
// you are not yet authenticated
this.$store.commit('replace', { 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.$store.commit('replace', { 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: 'calcul-impot' })
} catch (error) {
// the error is traced back to the main component
this.$emit('error', error)
} finally {
// maj store
this.$store.commit('replace', { métier: this.$métier() })
// save the session
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] im Projekt [vuejs-22] wurde zur Seite [calcul-impot] im Projekt [nuxt-20];
Nachdem diese Korrekturen vorgenommen wurden, können wir versuchen, die Authentifizierungsseite 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] sehen wir, dass der Authentifizierungsstatus korrekt gespeichert wurde. In [3-4] sehen wir, dass die [taxAdminData]-Daten abgerufen wurden, wodurch die Steuer von der [Métier]-Klasse berechnet werden kann.
17.5. Schritt 4
Sehen wir uns die Seite [calcul-impot] an, die wir erhalten haben:
<!-- 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>
<script>
// imports
import FormCalculImpot from '@/components/form-calcul-impot'
import Menu from '@/components/menu'
import Layout from '@/components/layout'
export default {
// composants utilisés
components: {
Layout,
FormCalculImpot,
Menu
},
// é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
}
},
// cycle de vie
created() {
// eslint-disable-next-line
console.log("CalculImpot created");
},
// 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
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
// affichage du résultat
this.résultatObtenu = true
// ---- maj du store [Vuex]
// une simulation de +
this.$store.commit('addSimulation', résultat)
// on sauvegarde la session
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 mit Routing gelöst. Wir werden dasselbe mit dem Projekt [nuxt-20] tun;
- Zeile 76: Wir verweisen auf eine Methode [addSimulation], die derzeit nicht existiert. Wir werden sie erstellen;
- Zeile 78: Wie auf der [index]-Seite müssen wir [this.$session().save(this.$nuxt.context)] schreiben;
Lassen Sie uns den Store [store/index] ändern. Er wurde vom [nuxt-12]-Projekt übernommen und sieht derzeit wie folgt aus:
/* eslint-disable no-console */
// awning status
export const state = () => ({
// session jSON started
jsonSessionStarted: false,
// authenticated user
userAuthenticated: false,
// session cookie PHP
phpSessionCookie: '',
// adminData
adminData: ''
})
// changes in the awning
export const mutations = {
// state replacement
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
},
// awning reset
reset() {
this.commit('replace', { jsonSessionStarted: false, userAuthenticated: false, phpSessionCookie: '', adminData: '' })
}
}
// awning actions
export const actions = {
nuxtServerInit(store, context) {
// who executes this code?
console.log('nuxtServerInit, client=', process.client, 'serveur=', process.server, 'env=', context.env)
// init session
initStore(store, context)
}
}
function initStore(store, context) {
// store is the blind to be initialized
// retrieve the session
const session = context.app.$session()
// has the session already been initiated?
if (!session.value.initStoreDone) {
// start a new blind
console.log("nuxtServerInit, initialisation d'un nouveau store")
// put the blind in the session
session.value.store = store.state
// the blind is now initialized
session.value.initStoreDone = true
} else {
console.log("nuxtServerInit, reprise d'un store existant")
// update the store with the session store
store.commit('replace', session.value.store)
}
// save the session
session.save(context)
// log
console.log('initStore terminé, store=', store.state)
}
- Zeilen 3–27: Wir werden den Zustand und die Mutationen der [vuejs-22]-Anwendung wieder aufnehmen (siehe Dokument [3]):
// awning status
export const state = () => ({
// session jSON started
started: false,
// authenticated user
authenticated: false,
// session cookie PHP
phpSessionCookie: '',
// list of simulations
simulations: [],
// last simulation number
idSimulation: 0,
// business] layer
métier: null
})
// changes in the awning
export const mutations = {
// state replacement
replace(state, newState) {
for (const attr in newState) {
state[attr] = newState[attr]
}
},
// awning reset
reset() {
this.commit('replace', { started: false, authenticated: false, phpSessionCookie: '', idSimulation: 0, simulations: [], métier: null })
},
// delete line n° index
deleteSimulation(state, index) {
// eslint-disable-next-line no-console
console.log('mutation deleteSimulation')
// delete line no. [index]
state.simulations.splice(index, 1)
console.log('store simulations', state.simulations)
},
// add a simulation
addSimulation(state, simulation) {
// eslint-disable-next-line no-console
console.log('mutation addSimulation')
// simulation no
state.idSimulation++
simulation.id = state.idSimulation
// add the simulation to the simulation table
state.simulations.push(simulation)
}
}
- Zeilen 4 und 6: Wir führen die bereits verwendeten Eigenschaften ein;
- Zeile 8: Wir speichern das PHP-Sitzungs-Cookie. Dies ist unerlässlich, damit der Client und der [nuxt]-Server dieselbe PHP-Sitzung mit dem Steuerberechnungsserver teilen;
- Zeile 10: die Liste der vom Benutzer durchgeführten Simulationen;
- Zeile 12: die Nummer der letzten vom Benutzer durchgeführten Simulation;
- Zeile 14: die [business]-Schicht;
- Zeilen 30–47: die Mutationen, die im Store des [vuejs-22]-Projekts vorhanden sind und auf die die Seiten der Anwendung verweisen. Das [vuejs-22]-Projekt verfügte über eine Mutation namens [clear], die die Liste der Simulationen löschte. Wir nehmen sie nicht auf, da die bereits vorhandene [reset]-Mutation ausreichen sollte;
- Zeilen 26–28: Die Mutation [reset] wird angepasst, um den neuen Zustand des Inhalts zu berücksichtigen;
Die Seite [calcul-impot] verwendet die folgende Komponente [form-calcul-impot]:
<!-- définition HTML de la vue -->
<template>
<!-- formulaire HTML -->
<b-form @submit.prevent="calculerImpot" class="mb-3">
<!-- message sur 12 colonnes sur fond bleu -->
<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>
<!-- éléments du formulaire -->
<!-- première ligne -->
<b-form-group label="Etes-vous marié(e) ou pacsé(e) ?">
<!-- boutons radio sur 5 colonnes-->
<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>
<!-- deuxième ligne -->
<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>
<!-- message d'erreur éventuel -->
<b-form-invalid-feedback :state="enfantsValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
</b-form-group>
<!-- troisème ligne -->
<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>
<!-- message d'erreur éventuel -->
<b-form-invalid-feedback :state="salaireValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
</b-form-group>
<!-- quatrième ligne, bouton [submit] -->
<b-col sm="3">
<b-button :disabled="formInvalide" type="submit" variant="primary">Valider</b-button>
</b-col>
</b-form>
</template>
<!-- script -->
<script>
export default {
// inner state
data() {
return {
// married or not
marié: 'non',
// number of children
enfants: '',
// annual salary
salaire: ''
}
},
// calculated internal state
computed: {
// form validation
formInvalide() {
return (
// disabled salary
!this.salaire.match(/^\s*\d+\s*$/) ||
// or disabled children
!this.enfants.match(/^\s*\d+\s*$/) ||
// or tax data not obtained
!this.$métier.taxAdminData
)
},
// salary validation
salaireValide() {
// must be numeric >=0
return Boolean(this.salaire.match(/^\s*\d+\s*$/) || this.salaire.match(/^\s*$/))
},
// child validation
enfantsValide() {
// must be numeric >=0
return Boolean(this.enfants.match(/^\s*\d+\s*$/) || this.enfants.match(/^\s*$/))
}
},
// life cycle
created() {
// log
// eslint-disable-next-line
console.log("FormCalculImpot created");
},
// event manager
methods: {
calculerImpot() {
// tax is calculated using the [business] layer
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);
// complete the result
résultat.marié = this.marié
résultat.enfants = this.enfants
résultat.salaire = this.salaire
// the [resultatObtenu] event is issued
this.$emit('resultatObtenu', résultat)
}
}
}
</script>
- Zeilen 65, 89: Der Verweis [this.$métier] muss in [this.$métier()] geändert werden;
Sobald diese Korrekturen vorgenommen wurden, können wir versuchen, eine Simulation auszuführen:

Wir erhalten folgende Antwort:

Wenn wir uns die Protokolle ansehen:

- in [9-10] sehen wir, dass sich die erste Simulation tatsächlich im [Speicher] befindet;
- in [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 der Simulationen]. Wir gelangen auf die folgende Seite:

Die Weiterleitung für den [nuxt]-Client war erfolgreich. Sehen wir uns den Code für die Seite [simulation-list] an:
<!-- définition HTML de la vue -->
<template>
<div>
<!-- mise en page -->
<Layout :left="true" :right="true">
<!-- simulations dans colonne de droite -->
<template slot="right">
<template v-if="simulations.length == 0">
<!-- pas de simulations -->
<b-alert show variant="primary">
<h4>Votre liste de simulations est vide</h4>
</b-alert>
</template>
<template v-if="simulations.length != 0">
<!-- il y a des simulations -->
<b-alert show variant="primary">
<h4>Liste de vos simulations</h4>
</b-alert>
<!-- tableau des simulations -->
<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>
<!-- menu de navigation dans colonne de gauche -->
<Menu slot="left" :options="options" />
</Layout>
</div>
</template>
<script>
// imports
import Layout from '@/components/layout'
import Menu from '@/components/menu'
export default {
// composants
components: {
Layout,
Menu
},
// état interne
data() {
return {
// options du menu de navigation
options: [
{
text: "Calcul de l'impôt",
path: '/calcul-impot'
},
{
text: 'Fin de session',
path: '/fin-session'
}
],
// paramètres de la table HTML
fields: [
{ label: '#', key: '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' }
]
}
},
// état interne calculé
computed: {
// liste des simulations prise dans le store Vuex
simulations() {
return this.$store.state.simulations
}
},
// cycle de vie
created() {
// eslint-disable-next-line
console.log("ListeSimulations created");
},
// 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()
}
}
}
</script>
- Zeilen 47–56: Die Ziele des Navigationsmenüs sind korrekt;
- Zeile 75: Auf das Store wird korrekt verwiesen;
- Zeile 89: Wir verwenden eine Mutation [deleteSimulation], die wir im vorherigen Schritt hinzugefügt haben;
- Zeile 91: Diese Zeile muss wie folgt umgeschrieben werden: [this.$session().save(this.$nuxt.context)];
Wir nehmen die erforderlichen Änderungen vor und versuchen dann, die angezeigte Simulation zu löschen:

Daraufhin wird folgende Seite angezeigt:

Schauen wir uns die Protokolle an:

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

Wir gelangen auf die folgende Seite:

Das Routing hat also funktioniert.
17.7. Schritt 6
Wir müssen noch die Navigationsoption [Sitzung beenden] im Navigationsmenü bearbeiten:
- Zeile 9: Die Seite [/end-session] existiert nicht. Das Projekt [vuejs-22] hat diesen Fall mit Routing-Regeln in einer [router.js]-Datei behandelt:
// 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: 'authentification2', 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
- Die Zeilen 64–76 behandelten den Sonderfall des Pfads zum Pfad [/end-session];
- Zeile 66: Löschen der aktuellen Sitzung;
- Zeilen 68–70: Anzeige der Ansicht [authentication];
Wir werden versuchen, etwas Ähnliches in der [nuxt]-Client-Routing-Datei zu implementieren:

Das Skript [client/routing.js] sieht nun wie folgt aus:
/* eslint-disable no-console */
export default function(context) {
// who executes this code?
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// management of the PHP session cookie in the browser
// the browser's PHP session cookie must be identical to the one found in the nuxt session
// the [end-session] action receives a new PHP cookie (server as nuxt client)
// if the server receives it, the client must pass it on to the browser
// for its own exchanges with the PHP server
// this is customer routing
// retrieve the session cookie PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// if it exists, we assign the PHP session cookie to the browser
document.cookie = phpSessionCookie
}
// where are we going?
const to = context.route.path
if (to === '/fin-session') {
// clean the session
const session = context.app.$session()
session.reset(context)
// redirects to the index page
context.redirect({ name: 'index' })
}
}
- Wir haben die Zeilen [19–27] zum bestehenden Code hinzugefügt;
- Zeile 20: Abrufen des [Pfads] des Ziels der aktuellen Route;
- Zeile 21: Wir prüfen, ob es sich um [/end-session] handelt. Wenn ja:
- Zeilen 23–24: Die Sitzung wird zurückgesetzt;
- Zeile 26: Wir leiten den [nuxt]-Client auf die Startseite weiter;
Die Methode [session.reset(context)] der Sitzung (Zeile 24) lautet wie folgt:
// reset de la session
reset(context) {
console.log('nuxt-session reset')
// reset du store
context.store.commit('reset')
// sauvegarde du nouveau store en session et sauvegarde de la session
this.save(context)
}
Die Methode [context.store.commit('reset')] (Zeile 5) lautet wie folgt:
// reset du store
reset() {
this.commit('replace', { started: false, authenticated: false, phpSessionCookie: '', idSimulation: 0, simulations: [], métier: null })
}
Wenn wir nun auf den Link [Sitzung beenden] klicken, wird die Startseite mit den folgenden Protokollen angezeigt:

- In [3] sehen wir, dass wir nicht mehr authentifiziert sind;
- in [4] sehen wir, dass die JSON-Sitzung gestartet wurde;
- in [6] ist die [business]-Schicht nicht mehr im Speicher vorhanden (sie ist über [this.$business()] weiterhin auf den Seiten vorhanden);
- in [5, 7] gibt es keine Simulationen mehr;
Es ist wichtig zu verstehen, was am Ende einer Sitzung geschieht:
- Die [nuxt]-Sitzung wird zurückgesetzt: Die Eigenschaft [started] des Stores ändert sich auf [false];
- es erfolgt eine Weiterleitung zur [index]-Seite;
- die [mounted]-Methode der [index]-Seite wird ausgeführt. Dadurch wird eine neue JSON-Sitzung mit dem Steuerberechnungsserver gestartet. Wenn der Vorgang erfolgreich ist, wird die [started]-Eigenschaft des Stores auf [true] gesetzt;
17.8. Schritt 7
Zu diesem Zeitpunkt verfügt die [nuxt-20]-Anwendung über alle Funktionen der [vuejs-22]-Anwendung. Die Portierung scheint abgeschlossen zu sein.
Wir gehen im Sinne von [nuxt] noch einen Schritt weiter. Die [mounted]-Methode der [index]-Seite stellt ein Problem dar. Sie startet einen asynchronen Vorgang, auf dessen Abschluss eine Suchmaschine nicht warten wird. Wir wissen, dass wir in diesem Fall den asynchronen Vorgang in eine [asyncData]-Funktion einbinden müssen, da der [nuxt]-Server, der ihn ausführt, dann auf dessen Abschluss wartet, bevor er die Seite an die Suchmaschine übergibt.
Hier verwenden wir die in der [nuxt-12]-Anwendung für die [index]-Seite geschriebene [asyncData]-Funktion:
export default {
name: 'InitSession',
// components used
components: {
Layout,
Navigation
},
// asynchronous data
async asyncData(context) {
// log
console.log('[index asyncData started]')
// don't do things twice if the page has already been requested
if (process.server && context.store.state.jsonSessionStarted) {
console.log('[index asyncData canceled]')
return { result: '[succès]' }
}
try {
// start a jSON session
const dao = context.app.$dao()
const response = await dao.initSession()
// log
console.log('[index asyncData response=]', response)
// retrieve session cookie PHP for future requests
const phpSessionCookie = dao.getPhpSessionCookie()
// we store the PHP session cookie in the [nuxt] session
context.store.commit('replace', { phpSessionCookie })
// was there a mistake?
if (response.état !== 700) {
// the error is in response.réponse
throw new Error(response.réponse)
}
// note that the jSON session has started
context.store.commit('replace', { jsonSessionStarted: true })
// we return the result
return { result: '[succès]' }
} catch (e) {
// log
console.log('[index asyncData error=]', e)
// note that session jSON has not started
context.store.commit('replace', { jsonSessionStarted: false })
// we report the error
return { result: '[échec]', showErrorLoading: true, errorLoadingMessage: e.message }
} finally {
// save the blind
const session = context.app.$session()
session.save(context)
// log
console.log('[index asyncData finished]')
}
},
// life cycle
beforeCreate() {
console.log('[index beforeCreate]')
},
created() {
console.log('[index created]')
},
beforeMount() {
console.log('[index beforeMount]')
},
mounted() {
console.log('[index mounted]')
// customer only
if (this.showErrorLoading) {
console.log('[index mounted, showErrorLoading=true]')
this.$eventBus().$emit('errorLoading', true, this.errorLoadingMessage)
}
}
- Zeilen 13, 33, 40: Ändere die Eigenschaft [jsonSessionStarted] in [started];
- Zeile 13: In der [nuxt-12]-Anwendung führte nur der [nuxt]-Server die [index]-Seite und deren [asyncData]-Funktion aus. Der [nuxt]-Client führte die [index]-Seite erst aus, nachdem er sie vom [nuxt]-Server erhalten hatte, und führte daher die [asyncData]-Funktion nicht aus. In [nuxt-20] ist das anders: Der Link [End Session] zeigt die [index]-Seite in der [nuxt]-Client-Umgebung an. Die [asyncData]-Funktion wird dann ausgeführt. Wenn man jedoch auf diese Weise auf die [index]-Seite gelangt, wurde die [nuxt]-Sitzung in der Zwischenzeit zurückgesetzt, und die [started]-Eigenschaft des Stores ist [false], sodass die Bedingung in Zeile 13 zwangsläufig falsch ist. Wir können daher [process.server] weglassen, und der [nuxt]-Client führt diese Überprüfung nicht durch;
- Zeilen 15, 35, 42: Den [data]-Eigenschaften der [index]-Seite wird eine [result]-Eigenschaft hinzugefü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 [mounted]-Methode muss beibehalten werden, da sie es dem [nuxt]-Client ermöglicht, die Fehlermeldung anzuzeigen. Die Art und Weise, wie der Fehler behandelt wird, wird jedoch geändert;
Auf der aktuellen [index]-Seite integrieren wir die obige [asyncData]-Funktion anstelle der alten [mounted]-Funktion und fügen eine neue [mounted]-Funktion hinzu. Der Code für die [index]-Seite im [nuxt-20]-Beispiel sieht dann wie folgt aus:
...
<!-- dynamique de la vue -->
<script>
/* eslint-disable no-console */
import Layout from '@/components/layout'
export default {
// components used
components: {
Layout
},
// component status
data() {
return {
// user
user: '',
// password
password: '',
// error display
showError: false
}
},
// calculated properties
computed: {
// valid entries
valid() {
return this.user && this.password && this.$store.state.started
}
},
// asynchronous data
async asyncData(context) {
// log
console.log('[index asyncData started]')
// don't do things twice if the page has already been requested
if (process.server && context.store.state.started) {
console.log('[index asyncData canceled]')
return
}
try {
// start a jSON session
const dao = context.app.$dao()
const response = await dao.initSession()
// log
console.log('[index asyncData response=]', response)
// retrieve session cookie PHP for future requests
const phpSessionCookie = dao.getPhpSessionCookie()
// we store the PHP session cookie in the [nuxt] session
context.store.commit('replace', { phpSessionCookie })
// was there a mistake?
if (response.état !== 700) {
// the error is in response.réponse
throw new Error(response.réponse)
}
// note that the jSON session has started
context.store.commit('replace', { started: true })
// no result
return
} catch (e) {
// log
console.log('[index asyncData error=]', e.message)
// note that session jSON has not started
context.store.commit('replace', { started: false })
// we report the error
return { showErrorLoading: true, errorLoadingMessage: e.message }
} finally {
// save the blind
const session = context.app.$session()
session.save(context)
// log
console.log('[index asyncData finished]')
}
},
// life cycle
beforeCreate() {
console.log('[index beforeCreate]')
},
created() {
console.log('[index created]')
},
beforeMount() {
// customer only
console.log('[index beforeMount]')
// error handling
if (this.showErrorLoading) {
// log
console.log('[index beforeMount, showErrorLoading=true]')
// the error is traced back to the main component [default]
this.$emit('error', new Error(this.errorLoadingMessage))
}
},
mounted() {
console.log('[index mounted]')
},
// event managers
methods: {
// ----------- authentication
async login() {
...
}
</script>
- Zeilen 58, 65: Die Funktion [asyncData] rendert die Eigenschaft [result] hier nicht mehr ungenutzt;
- Zeile 81: die Methode [beforeMount] des [nuxt]-Clients. Sie wurde der Methode [mounted] vorgezogen, um mögliche Fehler von [asyncData] zu behandeln;
- Zeile 85: Wir prüfen, ob die Eigenschaft [errorLoading] gesetzt wurde. Sie kann nur von der Funktion [asyncData] gesetzt werden;
- Zeilen 85–90: Wenn die Funktion [asyncData] einen Fehler gemeldet hat, leiten wir diesen über das [error]-Ereignis an die [default]-Seite weiter. Auf diese Weise hat die alte [created]-Funktion, die wir gerade ersetzt haben, mögliche Fehler behandelt;
Führen wir einige Tests durch.
Zunächst löschen wir sowohl das [nuxt]-Sitzungscookie als auch das PHP-Sitzungscookie, sofern diese vorhanden sind. Anschließend rufen wir die Seite [http://localhost:81/nuxt-20/] auf, während der Steuerberechnungsserver nicht läuft. Wir erhalten die folgende Seite:

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

Schauen wir uns die Protokolle an:

- In [2-3] sehen wir, dass die JSON-Sitzung gestartet wurde;
- in [4] sehen wir das PHP-Sitzungs-Cookie, das der [nuxt]-Server während seines Austauschs mit dem Steuerberechnungsserver abgerufen hat. Der [nuxt]-Client wird es nun verwenden;
Melden wir uns nun an:

Wir erhalten die folgende Seite:

In [1] erhielten wir eine Fehlermeldung. Das bedeutet, dass der Browser nicht das richtige Sitzungscookie aus der PHP-Sitzung gesendet hat, die im vorherigen Schritt vom [nuxt]-Server gestartet wurde. In [nuxt-12] wurde das PHP-Sitzungscookie im [nuxt]-Client-Routing des Skripts [middleware/client/routing] vom [nuxt]-Server an den [nuxt]-Client übergeben:
/* eslint-disable no-console */
export default function(context) { // who executes this code?
console.log('[middleware client], process.server', process.server, ', process.client=', process.client)
// management of the PHP session cookie in the browser
// the browser's PHP session cookie must be identical to the one found in the nuxt session
// acion [fin-session] receives a new cookie PHP (server as nuxt client)
// if the server receives it, the client must pass it on to the browser
// for its own exchanges with the PHP server
// this is customer routing
// retrieve the session cookie PHP
const phpSessionCookie = context.store.state.phpSessionCookie
if (phpSessionCookie) {
// if it exists, we assign the PHP session cookie to the browser
document.cookie = phpSessionCookie
}
// where are we going?
const to = context.route.path
if (to === '/fin-session') {
// clean the session
const session = context.app.$session()
session.reset(context)
// redirects to the index page
context.redirect({ name: 'index' })
}
}
Die Zeilen 13–17 ermöglichen es dem [nuxt]-Client, das PHP-Session-Cookie vom [nuxt]-Server abzurufen.
Das Problem hierbei ist, dass beim Klicken auf die Schaltfläche [Validate] kein Routing vom [nuxt]-Client erfolgt. Die Routing-Funktion wird daher nicht aufgerufen. Wir beheben das Problem, indem wir die Zeilen 12–17 am Anfang der Authentifizierungsmethode auf der [index]-Seite duplizieren:
// event managers
methods: {
// ----------- authentication
async login() {
// retrieve the PHP session cookie from the store
const phpSessionCookie = this.$store.state.phpSessionCookie
if (phpSessionCookie) {
// if it exists, we assign the PHP session cookie to the browser
document.cookie = phpSessionCookie
}
try {
// start waiting
this.$emit('loading', true)
// you are not yet authenticated
Zeilen 5–10: Wir rufen das vom [nuxt]-Server initiierte PHP-Session-Cookie aus dem Store ab. Sobald diese Änderung vorgenommen wurde, wird die Seite zur Steuerberechnung erfolgreich geladen, was darauf hinweist, dass die Authentifizierung funktioniert hat.
17.9. Schritt 8
Wir haben eine funktionsfähige Anwendung, die im Sinne von [nuxt] funktioniert. Wie bereits bei der [nuxt-13]-Anwendung werden wir uns auf die Navigation auf dem [nuxt]-Server konzentrieren. Wie bereits erwähnt, soll der Benutzer die URLs der Anwendung nicht manuell eingeben. Er soll die ihm angezeigten Links verwenden, die vom [nuxt]-Client ausgeführt werden, der dann im SPA-Modus arbeitet. Dennoch werden wir sicherstellen, dass die Navigation auf dem [nuxt]-Server die Anwendung stets in einem stabilen Zustand hält.
Aus der für [nuxt-13] durchgeführten Studie (siehe verlinkten Absatz) wissen wir, dass wir Folgendes tun müssen:
- das Skript [middleware/routing] ändern;
- ein Skript [middleware/server/routing] hinzufügen;

Das Skript [middleware/routing] wird wie folgt geändert:
/* eslint-disable no-console */
// on importe les middleware du serveur et du client
import serverRouting from './server/routing'
import clientRouting from './client/routing'
export default function(context) {
// qui exécute ce code ?
console.log('[middleware], process.server', process.server, ', process.client=', process.client)
if (process.server) {
// routage serveur
serverRouting(context)
} else {
// routage client
clientRouting(context)
}
}
- Zeile 4: Wir importieren das Routing-Skript vom [nuxt]-Server;
- Zeilen 10–12: Wenn der [nuxt]-Server den Code ausführt, verwenden wir dessen Routing-Funktion;
Das Skript [middleware/server/routing] lautet wie folgt:
/* eslint-disable no-console */
export default function(context) {
// qui exécute ce code ?
console.log('[middleware server], process.server', process.server, ', process.client=', process.client)
// on récupère quelques informations ici et là
const store = context.store
// d'où vient-on ?
const from = store.state.from || 'nowhere'
// où va-t-on ?
let to = context.route.name
// cas particulier de /fin-session qui n'a pas d'attribut [name]
if (context.route.path === '/fin-session') {
to = 'fin-session'
}
// éventuelle redirection
let redirection = ''
// gestion du routage terminé
let done = false
// est-on déjà dans une redirection du serveur [nuxt]?
if (store.state.serverRedirection) {
// rien à faire
done = true
}
// s'agit-il d'un rechargement de page ?
if (!done && from === to) {
// rien à faire
done = true
}
// contrôle de la navigation du serveur [nuxt]
// on se calque sur le menu de navigation du client
// on traite d'abord le cas de fin-session
if (!done && store.state.started && store.state.authenticated && to === 'fin-session') {
// on nettoie la session
const session = context.app.$session()
session.reset(context)
// on redirige vers la page index
redirection = 'index'
// travail terminé
done = true
}
// cas où la session PHP n'a pas démarré
if (!done && !store.state.started && to !== 'index') {
// redirection vers [index]
redirection = 'index'
// travail terminé
done = true
}
// cas où l'utilisateur n'est pas authentifié
if (!done && store.state.started && !store.state.authenticated && to !== 'index') {
redirection = 'index'
// travail terminé
done = true
}
// cas où [adminData] n'a pas été obtenu
if (!done && store.state.started && store.state.authenticated && !store.state.métier.taxAdminData && to !== 'index') {
// redirection vers [index]
redirection = 'index'
// travail terminé
done = true
}
// cas où [adminData] a été obtenu
if (
!done &&
store.state.started &&
store.state.authenticated &&
store.state.métier.taxAdminData &&
to !== 'calcul-impot' &&
to !== 'liste-des-simulations'
) {
// on reste sur la même page
redirection = from
// travail terminé
done = true
}
// on a normalement fait tous les contrôles ---------------------
// redirection ?
if (redirection) {
// on note la redirection dans le store
store.commit('replace', { serverRedirection: true })
} else {
// pas de redirection
store.commit('replace', { serverRedirection: false, from: to })
}
// on sauvegarde le store dans la session [nuxt]
const session = context.app.$session()
session.value.store = store.state
session.save(context)
// on fait l'éventuelle redirection du serveur [nuxt]
if (redirection) {
context.redirect({ name: redirection })
}
}
- In diesem Skript verwenden wir die Konzepte wieder, die bereits im [nuxt]-Server-Routing der [nuxt-13]-Anwendung entwickelt und verwendet wurden;
- Wir fügen dem Anwendungsspeicher zwei Eigenschaften hinzu:
- [from]: der Name der zuletzt angezeigten Seite. Wir wissen, dass der [nuxt]-Client über diese Information verfügt, der [nuxt]-Server jedoch nicht. Wir fügen diese Information hinzu, indem wir den Namen der anzuzeigenden Seite bei jedem Routing des [nuxt]-Servers im Store speichern. Das Gleiche tun wir bei jedem Routing auf dem [nuxt]-Client. So findet der [nuxt]-Server beim nächsten Routing im Store den Namen der zuletzt von der Anwendung angezeigten Seite;
- [serverRedirection]: Wenn ein Routing-Ziel vom [nuxt]-Server abgelehnt wird, führt er eine Umleitung durch. Er vermerkt dann im Store, dass das nächste Ziel des [nuxt]-Servers eine Umleitungsseite ist. Diese Umleitung löst eine neue Ausführung des Routers des [nuxt]-Servers aus. Wenn der Router erkennt, dass das aktuelle Ziel das Ergebnis einer Umleitung ist, lässt er den Vorgang zu;
- Zeilen 6–11: Wir rufen die für das Routing erforderlichen Informationen ab;
- Zeilen 13–16: Das Ziel [/end-session] ist nicht mit einer Seite namens [end-session] verknüpft. Es hat daher keinen Namen. Wir geben ihm einen;
- Zeile 19: das Ziel einer möglichen Weiterleitung;
- Zeile 21: [done=true], wenn die Routing-Prüfungen abgeschlossen sind;
- Zeilen 23–27: Wie bereits erwähnt, gibt es nichts zu tun, wenn das aktuelle Routing aus einer Weiterleitung resultiert. Tatsächlich hat der Router während des vorherigen Routings entschieden, dass der Browser des Clients weitergeleitet werden soll. Es besteht keine Notwendigkeit, diese Entscheidung zu überdenken;
- Zeilen 29–33: Handelt es sich um einen Seitenneuladung, lassen wir diese zu. Dies ist keine allgemeingültige Regel für alle [Nuxt]-Anwendungen: Sie müssen die Auswirkungen einer Neuladung für jede Seite prüfen. Hier stellt sich heraus, dass das Neuladen der Seiten [index, tax-calculation, simulation-list] keine unerwünschten Auswirkungen hat;
- Zeilen 35–85: Das [nuxt]-Server-Routing spiegelt das [nuxt]-Client-Routing wider. Befindet man sich auf einer Seite, muss das [nuxt]-Server-Routing das Navigationsmenü widerspiegeln, das vom [nuxt]-Client auf dieser Seite bereitgestellt wird;
- Zeilen 38–47: Wir behandeln zunächst den Fall, in dem das Ziel [end-session] keiner vorhandenen Seite entspricht. Sind die Bedingungen erfüllt (Sitzung gestartet, Benutzer authentifiziert), löschen wir die Sitzung und leiten den Benutzer auf die Seite [index] weiter;
- Zeilen 49–55: Wenn die JSON-Sitzung mit dem Steuerberechnungsserver noch nicht gestartet wurde, ist die einzige mögliche Zielseite die [index]-Seite;
- Zeilen 57–62: Wenn die JSON-Sitzung gestartet wurde und der Benutzer nicht authentifiziert ist und die Authentifizierungsseite nicht angefordert hat, leiten wir ihn auf die Authentifizierungsseite weiter, bei der es sich um die [index]-Seite handelt;
- Zeilen 64–70: Wenn der Benutzer authentifiziert ist, aber die [adminData] noch nicht abgerufen wurden, wird der Benutzer zur Authentifizierungsseite weitergeleitet. Die Authentifizierung erfüllt zwei Aufgaben: Sie authentifiziert den Benutzer und fordert bei erfolgreicher Authentifizierung zusätzlich die [adminData]-Daten an. Wenn diese Daten nicht abgerufen wurden, muss die Authentifizierung neu gestartet werden;
- Zeilen 72–85: Wenn die [adminData]-Daten abgerufen wurden, sind die einzigen möglichen Ziele [tax-calculation] und [simulation-list]. Ist dies nicht der Fall, wird das Routing verweigert;
- Zeilen 88–95: Der Speicher wird aktualisiert, je nachdem, ob eine Weiterleitung erfolgt oder nicht;
- Zeile 94: Es findet keine Weiterleitung statt. Daher wird das aktuelle [to] zum [from] für das nächste Routing;
- Zeilen 96–99: Die Informationen des Speichers werden im [nuxt]-Session-Cookie gespeichert;
- Zeilen 100–103: Wenn eine Weiterleitung erforderlich ist, wird diese durchgeführt;
Um die Tests auszuführen, stellen Sie sicher, dass Sie mit einer sauberen Basis beginnen, indem Sie das [nuxt]-Session-Cookie und das PHP-Session-Cookie beim Steuerberechnungsserver löschen:

Um das Routing des [nuxt]-Servers zu testen, probieren Sie alle möglichen URLs [/, /tax-calculation, /simulation-list] auf jeder Seite aus. Die Anwendung muss jedes Mal in einem konsistenten Zustand bleiben.
17.10. Schritt 9
Schritt 9 umfasst die Bereitstellung der [nuxt-20]-Anwendung. Dazu ist ein Hosting erforderlich, das eine [node.js]-Umgebung zur Ausführung des [nuxt]-Servers bereitstellt. Ich verfüge nicht darüber. Der Leser kann die im verlinkten Abschnitt beschriebenen Schritte befolgen, um die [nuxt-20]-Anwendung auf seinem Entwicklungsrechner bereitzustellen und sie mit einem HTTPS-Protokoll zu sichern.
17.11. Fazit
Die Portierung der [vuejs-22]-Anwendung auf die [nuxt-20]-Anwendung ist nun abgeschlossen. Lassen Sie uns einige wichtige Punkte dieser Portierung noch einmal zusammenfassen:
- Die [vuejs-22]-Seiten wurden beibehalten;
- die asynchronen Operationen, die in den [vuejs-22]-Seiten vorhanden waren, wurden in eine [asyncData]-Funktion migriert;
- in [nuxt-20] mussten wir zwei Entitäten verwalten: den [nuxt]-Client und den [nuxt]-Server. Letztere Entität existierte in [vuejs-22] nicht. Um die Konsistenz zwischen den beiden Entitäten zu gewährleisten, benötigten wir eine [nuxt]-Sitzung;
- wir mussten das [nuxt]-Server-Routing verwalten;
In der Praxis ist es wahrscheinlich vorzuziehen, direkt mit einer [nuxt]-Architektur zu beginnen, anstatt eine [vue.js]-Architektur aufzubauen und diese dann in eine [nuxt]-Umgebung zu portieren.