Skip to content

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:

Image

Die zweite Ansicht ist die Steuerberechnungsansicht:

Image

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

Image

Der obige Bildschirm zeigt, dass Simulation Nr. 1 gelöscht werden kann. Dies führt zu folgender Ansicht:

Image

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

Image

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:

Image

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:

Image

  • 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:

Image

Die folgenden Fehler werden gemeldet:

1
2
3
4
5
6
7
Module not found: Error: Can't resolve '../assets/logo.jpg' @ ./nuxt-20/layouts/default.vue?...
Module not found: Error: Can't resolve './FormCalculImpot' @ ./nuxt-20/pages/calcul-impot.vue?...
Module not found: Error: Can't resolve './Layout' @ ./nuxt-20/pages/_.vue?...
Module not found: Error: Can't resolve './Layout' @ ./nuxt-20/pages/liste-des-simulations...
Module not found: Error: Can't resolve './Layout' @ ./nuxt-20/pages/calcul-impot.vue?...
Module not found: Error: Can't resolve './Menu' @ ./nuxt-20/pages/_.vue?...
Module not found: Error: Can't resolve './Menu' @ ./nuxt-20/pages/calcul-impot.vue
  • 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:

Image

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:

Image

Es treten Fehler auf:

1
2
3
4
5
6
Module Error (from ./node_modules/eslint-loader/dist/cjs.js):

c:\Data\st-2019\dev\nuxtjs\dvp\nuxt-20\pages\index.vue
   92:29  error  Expected '!==' and instead saw '!='  eqeqeq
  129:27  error  Expected '!==' and instead saw '!='  eqeqeq
150:28  error  Expected '!==' and instead saw '!='  eqeqeq

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:

c:\Data\st-2019\dev\nuxtjs\dvp\nuxt-20\components\menu.vue
14:5  warning  Prop 'options' requires default value to be set  vue/require-default-prop

Image

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:

Image

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]:

Image

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:

Image

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:

Image

Schauen wir uns die Browser-Protokolle an:

Image

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:

Image

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

Image

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]:

Image

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:

Image

Die resultierende Seite sieht wie folgt aus:

Image

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

Image

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:

Image

Wir erhalten folgende Antwort:

Image

Wenn wir uns die Protokolle ansehen:

Image

  • 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:

Image

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:

Image

Daraufhin wird folgende Seite angezeigt:

Image

Schauen wir uns die Protokolle an:

Image

  • In [6] sehen wir, dass die Simulationstabelle leer ist;

Kehren wir nun zum Formular für die Steuerberechnung zurück:

Image

Wir gelangen auf die folgende Seite:

Image

Das Routing hat also funktioniert.

17.7. Schritt 6

Wir müssen noch die Navigationsoption [Sitzung beenden] im Navigationsmenü bearbeiten:

// options du menu
      options: [
        {
          text: 'List of simulations',
          path: '/list-of-simulations
        },
        {
          text: 'End of session',
          path: '/end-session'
        }
]
  • 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:

Image

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:

Image

  • 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:

Image

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

Image

Schauen wir uns die Protokolle an:

Image

  • 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:

Image

Wir erhalten die folgende Seite:

Image

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;

Image

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:

Image

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.