17. Esempio [nuxt-20]: Porting dell'esempio [vuejs-22]
17.1. Introduzione
Qui proponiamo di trasferire l'esempio [vuejs-22], che era un'applicazione di tipo SPA [vue.js], in un contesto SSR [nuxt]. [vuejs-22] era un'applicazione client per il server di calcolo delle imposte che presentava le seguenti viste:
La prima vista è quella di autenticazione:

La seconda schermata è quella del calcolo delle imposte:

La terza schermata mostra l'elenco delle simulazioni eseguite dall'utente:

La schermata sopra mostra che la simulazione n. 1 può essere eliminata. Ciò porta alla seguente schermata:

Se ora eliminiamo l'ultima simulazione, otteniamo la seguente nuova schermata:

Migreremo gradualmente l'applicazione [vuejs-22] all'applicazione [nuxt-20]. Non spiegheremo nuovamente il codice di [vuejs-22]. Invitiamo i lettori a consultare il documento |Introduzione al framework VUE.JS attraverso esempi|. I vari passaggi dovrebbero evidenziare le differenze tra un'applicazione [vuejs] e un'applicazione [nuxt].
17.2. Passaggio 1
Il progetto [nuxt-20] viene inizialmente creato clonando il progetto [nuxt-12]. Questo è infatti un buon punto di partenza:
- può comunicare con il server di calcolo delle imposte;
- gestisce correttamente gli errori inviati dal server;
- il client e il server [Nuxt] possono comunicare tramite una sessione [Nuxt];
Abbiamo quindi una solida infrastruttura di partenza. Il nostro compito principale dovrebbe essere quello di modificare:
- le pagine. Useremo quelle del progetto [vuejs-22], che dovranno essere adattate al nuovo ambiente;
- gestione dello store. Dovrebbero apparire informazioni aggiuntive (elenco delle simulazioni), mentre altre potrebbero diventare superflue;
- gestione del routing client e server [nuxt];
Quindi, per prima cosa, creiamo il progetto [nuxt-20] clonando il progetto [nuxt-12]:

Quindi rimuoviamo le pagine e i componenti che non sono più necessari [2]:
- il componente [components/navigation] scompare;
- il layout [layout/default] scompare;
- le pagine [index, authentication, get-admindata, end-session] vengono rimosse;
Quindi integriamo gli elementi da [vuejs-22] in [nuxt-20] [3]:
- le tre pagine [Authentication, TaxCalculation, SimulationList] dell'applicazione [vuejs-22] vengono inserite nella cartella [pages];
- i componenti [FormCalculImpot, Menu, Layout] dell'applicazione [vuejs-22] vengono inseriti nella cartella [components];
- la pagina [Main] di [vuejs-22], che fungeva da [layout] per l'applicazione [vuejs-22], va nella cartella [layouts];
Rinominiamo gli elementi integrati [4]:

- in [layouts], [Main] è diventato [default] poiché questo è il nome predefinito per il layout di un'applicazione [nuxt];
- in [pages], la pagina [Authentication] è diventata [index], poiché [Authentication] svolgeva questo ruolo nell'applicazione [vuejs-22];
A questo punto, possiamo compilare il progetto per vedere i primi errori. Modifichiamo il file [nuxt.config] dall'esempio [nuxt-12] per eseguire ora [nuxt-20]:
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
}
}
Successivamente, [compiliamo] il progetto:

Vengono segnalati i seguenti errori:
- L'errore alla riga 1 indica che si sta facendo riferimento a un'immagine inesistente. La recupereremo in [vuejs-22];
- L'errore alla riga 2 mostra che il componente [./FormCalculImpot] non esiste. In effetti, questo componente si trova ora in [@/components/form-calcul-impot];
- Gli errori alle righe [3-5] indicano che il componente [./Layout] non esiste. In realtà, questo componente si trova ora in [@/components/layout];
- gli errori alle righe [6-7] indicano che il componente [./Menu] non esiste. Infatti, ora si chiama [@/components/menu];
Aggiungiamo l'immagine [assets/logo.jpg] al progetto [nuxt-20]:

Inoltre, correggeremo i percorsi dei componenti su tutte le pagine. Prendiamo come esempio la pagina [calcul-impot]:
<!-- 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
},
Le tre istruzioni [import] alle righe 26–28 diventano:
// imports
import FormCalculImpot from '@/components/form-calcul-impot'
import Menu from '@/components/menu'
import Layout from '@/components/layout'
Controlla e, se necessario, correggi le istruzioni [import] per tutti i componenti, i layout e le pagine. Una volta apportate queste correzioni, puoi provare a eseguire una nuova [build]. Normalmente, non dovrebbero esserci più errori.
È quindi possibile provare a eseguire l'applicazione:

Vengono visualizzati degli errori:
Possiamo notare che il comando [dev], combinato con il modulo [eslint], è più rigoroso, dal punto di vista sintattico, rispetto al comando [build]. In questo caso, richiede che l'operatore di confronto [!=] sia scritto come [!==], che è un operatore più rigoroso (controlla anche il tipo degli operandi). Questi errori si verificano nella pagina [index.vue].
Correggiamo gli errori sopra indicati e rieseguiamo il progetto. Riceviamo quindi un avviso dal modulo [eslint]:

Risolviamo questo errore utilizzando la [Correzione rapida] dal modulo [eslint] [2].
Riavviamo il progetto. Non ci sono più errori di compilazione. Richiediamo quindi l'URL [http://localhost:81/nuxt-20/] in un browser. Otteniamo un errore di runtime:

L'errore si trova in [index.vue] [2]. L'errore [1] deriva dal fatto che in [vuejs-22] il livello [dao] era disponibile in [this.$dao], mentre in [nuxt-12], la cui infrastruttura abbiamo adottato, è disponibile nella funzione [this.$dao()].
L'errore si trova nella funzione [created] del ciclo di vita della pagina [index]:

Per ora, rinomineremo semplicemente [created] in [created2] in modo che la funzione del ciclo di vita [created] non venga eseguita [3].
Salviamo la modifica e ricarichiamo la pagina [index] nel browser. Questa volta funziona:

17.3. Passaggio 2
Le pagine del progetto [vuejs-22] utilizzavano i seguenti elementi iniettati:
- $dao: per il livello [dao] del client [vue.js];
- $session: per una sessione memorizzata nel [localStorage] del browser;
Questi elementi non esistono più nell'infrastruttura del progetto [nuxt-12] che abbiamo copiato:
- ora ci sono due livelli [dao], uno per il client [nuxt] e l'altro per il server [nuxt]. Entrambi sono disponibili tramite una funzione iniettata chiamata [$dao]. Ciò significa che nelle pagine dell'applicazione, [this.$dao] deve essere sostituito con [this.$dao()];
- la sessione [nuxt] gestita dall'applicazione [nuxt-20] non ha più nulla a che vedere con l'oggetto [$session] dell'applicazione [vuejs-22], dove non esisteva il concetto di cookie di sessione. Tuttavia, hanno uno scopo simile: memorizzare informazioni persistenti mentre l'utente interagisce con l'applicazione. La sessione [nuxt] memorizza le informazioni nello store piuttosto che direttamente nella sessione. Nelle pagine dell'applicazione, [this.$session] deve essere sostituito con [this.$store] quando si memorizzano informazioni nella sessione, e con [this.$session()] quando si manipola la sessione stessa;
- per verificare lo stato di una proprietà P nello store, è necessario scrivere [this.$store.state.P];
- per modificare la proprietà P dello store, è necessario scrivere [this.$store.commit('replace', {P:value}]
Apportiamo queste modifiche nella pagina [index]:
<!-- 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>
Si prega di tenere presente quanto segue:
- riga 69: la funzione [created2] è stata rinominata [mounted] in modo che il server [nuxt] non la esegua (non esegue né [beforeMount] né [mounted]). Solo il client [nuxt] la eseguirà, come nel caso dell'esempio [vuejs-22];
- riga 73: facciamo riferimento a [this.$business], che al momento non esiste;
- riga 75: non abbiamo mai utilizzato questo metodo in un'applicazione [nuxt]. Dovremo verificare se funziona in un contesto [nuxt];
- righe 112, 172: in [vuejs-22], la sessione del progetto veniva salvata in questo modo. Con il progetto [nuxt-20], il metodo [save] deve ricevere il contesto corrente. Sappiamo che in una pagina [nuxt], l'oggetto [context] è disponibile in [this.$nuxt.context];
Le righe 112 e 172 vengono quindi riscritte come segue:
this.$session().save(this.$nuxt.context)
Si noti che questo codice non è ottimizzato. Anziché utilizzare più volte la funzione [this.$session()], sarebbe meglio scrivere:
const session=this.$session()
e poi utilizzare la variabile [session]. Lo stesso ragionamento vale per la funzione [this.$dao()].
Una volta apportate queste correzioni, possiamo ricaricare l'URL [http://localhost:81/nuxt-20/] in un browser. Otteniamo ancora la stessa pagina di prima:

Diamo un'occhiata ai log del browser:

Il log [1] è l'ultimo log generato dal client [nuxt]. In [2], vediamo che la proprietà [started] è impostata su [true], il che significa che la funzione [mounted] ha avviato con successo una sessione JSON con il server di calcolo delle imposte. Vediamo anche che lo store ha proprietà che dovranno essere scartate o rinominate. Ricordiamo che stiamo utilizzando lo store dell'esempio [nuxt-12].
Ora richiediamo nuovamente l'URL [http://localhost:81/nuxt-20/] mentre il server di calcolo delle imposte non è in esecuzione. Per prima cosa, ci assicuriamo di eliminare il cookie di sessione [nuxt]:

Lo screenshot qui sopra è stato fatto su Chrome. Una volta fatto questo, l'URL [http://localhost:81/nuxt-20/] restituisce il seguente risultato:

L'errore è stato gestito correttamente dal progetto [vuejs-22]. Continua a essere gestito correttamente dal progetto [nuxt-20].
17.4. Passaggio 3
Ora che abbiamo la pagina di autenticazione, dobbiamo esaminare il codice che viene eseguito quando l'utente fa clic sul pulsante [Convalida]:
// 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)
}
}
}
Il problema principale sembra essere l'assenza dei dati [this.$métier]. Per risolvere questo problema, faremo quanto segue:
- includere la classe [Métier] dall'esempio [vuejs-22]. La inseriremo nella cartella [api];
- inseriremo una funzione [$métier] nel contesto client [nuxt], che fornirà l'accesso a questa classe;
Per prima cosa, copia la classe [Métier] nella cartella [api]:

Una volta che la classe [Métier] è nel progetto, creeremo un nuovo plugin per il client [nuxt]. Questo plugin, chiamato [pluginMétier], inietterà una funzione [$métier] che fornisce l'accesso alla classe [Métier]:
/* 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]')
}
Ora che questo è fatto, possiamo aggiornare la pagina [index]:
// 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)
}
}
}
- righe 43, 61, 69: [this.$métier] è stato sostituito da [this.$métier()];
- righe 8, 63: il nome della pagina [CalculImpot] nel progetto [vuejs-22] è diventato la pagina [calcul-impot] nel progetto [nuxt-20];
Una volta apportate queste correzioni, possiamo provare a convalidare la pagina di autenticazione:

La pagina risultante è la seguente:

Siamo arrivati alla pagina di calcolo delle imposte. Ora diamo un'occhiata ai log:

In [2] vediamo che lo stato di autenticazione è stato memorizzato correttamente. In [3-4] vediamo che i dati [taxAdminData] sono stati recuperati, il che consente alla classe [Métier] di calcolare l'imposta.
17.5. Passaggio 4
Esaminiamo la pagina [calcul-impot] che abbiamo ottenuto:
<!-- 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>
- righe 44 e 48: i link del menu di navigazione sono corretti. La pagina [/fin-session] non esiste. Il progetto [vuejs-22] ha risolto questo problema con il routing. Faremo lo stesso con il progetto [nuxt-20];
- riga 76: stiamo facendo riferimento a un metodo [addSimulation] che al momento non esiste. Lo creeremo;
- riga 78: come nella pagina [index], dobbiamo scrivere [this.$session().save(this.$nuxt.context)];
Modifichiamo lo store [store/index]. Ereditato dal progetto [nuxt-12], attualmente si presenta così:
/* 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)
}
- righe 3–27: riprenderemo lo stato e le mutazioni dell'applicazione [vuejs-22] (vedi documento [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)
}
}
- righe 4 e 6: introduciamo le proprietà già utilizzate;
- riga 8: memorizziamo il cookie di sessione PHP. Questo è essenziale affinché il client e il server [nuxt] condividano la stessa sessione PHP con il server di calcolo delle imposte;
- riga 10: l'elenco delle simulazioni eseguite dall'utente;
- riga 12: il numero dell'ultima simulazione eseguita dall'utente;
- riga 14: il livello [business];
- righe 30–47: le mutazioni presenti nello store del progetto [vuejs-22] e a cui fanno riferimento le pagine dell'applicazione. Il progetto [vuejs-22] aveva una mutazione chiamata [clear] che cancellava l'elenco delle simulazioni. Non la includiamo perché la mutazione [reset] già presente dovrebbe essere sufficiente;
- righe 26–28: la mutazione [reset] viene modificata per tenere conto del nuovo contenuto dello stato;
La pagina [calcul-impot] utilizza il seguente componente [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>
- righe 65, 89: il riferimento [this.$métier] deve essere modificato in [this.$métier()];
Una volta apportate queste correzioni, possiamo provare a eseguire una simulazione:

Otteniamo la seguente risposta:

Se diamo un'occhiata ai log:

- in [9-10], vediamo che la prima simulazione si trova effettivamente nello [store];
- in [5], il numero dell'ultima simulazione è stato effettivamente incrementato;
17.6. Passaggio 5
Ora che abbiamo eseguito una simulazione, clicchiamo sul link [Elenco delle simulazioni]. Otteniamo la seguente pagina:

L'instradamento per il client [nuxt] è andato a buon fine. Diamo un'occhiata al codice della pagina [simulation-list]:
<!-- 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>
- righe 47–56: i collegamenti del menu di navigazione sono corretti;
- riga 75: il negozio è correttamente referenziato;
- riga 89: stiamo utilizzando una mutazione [deleteSimulation] che abbiamo aggiunto nel passaggio precedente;
- riga 91: questa riga deve essere riscritta come [this.$session().save(this.$nuxt.context)];
Apportiamo le modifiche necessarie, quindi proviamo a eliminare la simulazione visualizzata:

Otteniamo quindi la seguente pagina:

Diamo un'occhiata ai log:

- In [6], vediamo che la tabella di simulazione è vuota;
Ora torniamo al modulo di calcolo delle imposte:

Otteniamo la seguente pagina:

Quindi il routing ha funzionato.
17.7. Passaggio 6
Dobbiamo ancora gestire l'opzione di navigazione [Fine sessione] nel menu di navigazione:
- alla riga 9, la pagina [/end-session] non esiste. Il progetto [vuejs-22] ha gestito questo caso con regole di routing in un file [router.js]:
// 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
- Le righe 64–76 gestiscono il caso particolare del percorso verso il percorso [/end-session];
- riga 66: cancella la sessione corrente;
- righe 68–70: visualizza la vista [authentication];
Proveremo a fare qualcosa di simile nel file di routing del client [nuxt]:

Lo script [client/routing.js] diventa il seguente:
/* 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' })
}
}
- Abbiamo aggiunto le righe [19-27] al codice esistente;
- riga 20: recuperiamo il [path] della destinazione del percorso corrente;
- riga 21: controlliamo se è [/end-session]. Se lo è:
- righe 23-24: la sessione viene resettata;
- riga 26: reindirizziamo il client [nuxt] alla home page;
Il metodo [session.reset(context)] della sessione (riga 24) è il seguente:
// 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)
}
Il metodo [context.store.commit('reset')] (riga 5) è il seguente:
// reset du store
reset() {
this.commit('replace', { started: false, authenticated: false, phpSessionCookie: '', idSimulation: 0, simulations: [], métier: null })
}
Quando ora clicchiamo sul link [End Session], viene visualizzata la home page con i seguenti log:

- in [3], vediamo che non siamo più autenticati;
- in [4], vediamo che la sessione JSON è stata avviata;
- in [6], il livello [business] non è più presente nello store (è ancora presente nelle pagine tramite [this.$business()]);
- in [5, 7], non ci sono più simulazioni;
È importante capire cosa succede alla fine di una sessione:
- la sessione [nuxt] viene resettata: la proprietà [started] dello store passa a [false];
- c'è un reindirizzamento alla pagina [index];
- viene eseguito il metodo [mounted] della pagina [index]. Questo avvia una nuova sessione JSON con il server di calcolo delle imposte. Se l'operazione ha esito positivo, la proprietà [started] dello store diventa [true];
17.8. Passaggio 7
A questo punto, l'applicazione [nuxt-20] presenta tutte le funzionalità dell'applicazione [vuejs-22]. Il porting sembra completato.
Faremo un ulteriore passo avanti nello spirito [nuxt]. Il metodo [mounted] della pagina [index] pone un problema. Avvia un'operazione asincrona che un motore di ricerca non aspetterà che finisca. Sappiamo che in questo caso dobbiamo inserire l'operazione asincrona all'interno di una funzione [asyncData] perché, in questo modo, il server [nuxt] che la esegue attenderà che sia terminata prima di consegnare la pagina al motore di ricerca.
Qui, usiamo la funzione [asyncData] scritta nell'applicazione [nuxt-12] per la pagina [index]:
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)
}
}
- righe 13, 33, 40: modificare la proprietà [jsonSessionStarted] in [started];
- riga 13: nell'applicazione [nuxt-12], solo il server [nuxt] ha eseguito la pagina [index] e la sua funzione [asyncData]. Il client [nuxt] ha eseguito la pagina [index] solo dopo averla ricevuta dal server [nuxt] e, di conseguenza, non ha eseguito la funzione [asyncData]. In [nuxt-20] è diverso: il link [End Session] visualizzerà la pagina [index] nell'ambiente client [nuxt]. La funzione [asyncData] verrà quindi eseguita. Tuttavia, quando si arriva alla pagina [index] in questo modo, la sessione [nuxt] è stata nel frattempo resettata e la proprietà [started] dello store è [false], quindi la condizione alla riga 13 sarà necessariamente falsa. Possiamo quindi omettere [process.server] e il client [nuxt] non eseguirà questo controllo;
- righe 15, 35, 42: una proprietà [result] viene aggiunta alle proprietà [data] della pagina [index]. In [nuxt-20], questa proprietà non verrà utilizzata, quindi la rimuoveremo dal risultato restituito dalla funzione;
- Righe 61–67: questo metodo [mounted] deve essere mantenuto perché è ciò che permette al client [nuxt] di visualizzare il messaggio di errore. Tuttavia, il modo in cui l'errore viene gestito verrà modificato;
Nella pagina [index] attuale, integriamo la funzione [asyncData] sopra riportata al posto della vecchia funzione [mounted] e aggiungiamo una nuova funzione [mounted]. Il codice per la pagina [index] nell'esempio [nuxt-20] diventa quindi il seguente:
...
<!-- 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>
- righe 58, 65: la funzione [asyncData] non rende più inutilizzata la proprietà [result] in questo punto;
- riga 81: il metodo [beforeMount] del client [nuxt]. È stato preferito al metodo [mounted] per gestire eventuali errori provenienti da [asyncData];
- riga 85: controlliamo se la proprietà [errorLoading] è stata impostata. Può essere impostata solo dalla funzione [asyncData];
- righe 85–90: se la funzione [asyncData] ha segnalato un errore, lo passiamo alla pagina [default] tramite l'evento [error]. È così che la vecchia funzione [created], che abbiamo appena sostituito, gestiva i potenziali errori;
Eseguiamo alcuni test.
Per prima cosa, eliminiamo sia il cookie di sessione [nuxt] che quello PHP, se presenti. Quindi richiediamo la pagina [http://localhost:81/nuxt-20/] mentre il server di calcolo delle imposte non è in esecuzione. Otteniamo la seguente pagina:

Ricarichiamo la stessa pagina dopo aver avviato il server di calcolo delle imposte:

Diamo un'occhiata ai log:

- In [2-3] vediamo che la sessione JSON è stata avviata;
- in [4], vediamo il cookie di sessione PHP che il server [nuxt] ha recuperato durante lo scambio con il server di calcolo delle imposte. Il client [nuxt] lo utilizzerà ora;
Ora effettuiamo l'accesso:

Otteniamo la seguente pagina:

In [1], abbiamo ricevuto un messaggio di errore. Ciò significa che il browser non ha inviato il cookie di sessione corretto dalla sessione PHP avviata dal server [nuxt] nel passaggio precedente. In [nuxt-12], il cookie di sessione PHP è stato passato dal server [nuxt] al client [nuxt] nel routing client [nuxt] dello script [middleware/client/routing]:
/* 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' })
}
}
Le righe 13–17 consentono al client [nuxt] di recuperare il cookie di sessione PHP dal server [nuxt].
Il problema qui è che quando si fa clic sul pulsante [Validate], non c'è alcun routing dal client [nuxt]. La sua funzione di routing non viene quindi chiamata. Risolviamo il problema duplicando le righe 12–17 all'inizio del metodo di autenticazione nella pagina [index]:
// 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
Righe 5–10: recuperiamo dallo store il cookie di sessione PHP generato dal server [nuxt]. Una volta apportata questa modifica, la pagina di calcolo delle imposte si carica correttamente, indicando che l'autenticazione ha funzionato.
17.9. Passaggio 8
Abbiamo un'applicazione funzionante che opera secondo lo spirito [nuxt]. Come abbiamo fatto per l'applicazione [nuxt-13], ci concentreremo sulla navigazione sul server [nuxt]. Come accennato in precedenza, l'utente non dovrebbe digitare manualmente gli URL dell'applicazione. Dovrebbe invece utilizzare i link che gli vengono presentati, che vengono eseguiti dal client [nuxt], il quale opera quindi in modalità SPA. Ciononostante, ci assicureremo che la navigazione sul server [nuxt] mantenga sempre l'applicazione in uno stato stabile.
Dallo studio condotto per [nuxt-13] (vedi paragrafo collegato), sappiamo che dobbiamo:
- modificare lo script [middleware/routing];
- aggiungere uno script [middleware/server/routing];

Lo script [middleware/routing] viene modificato come segue:
/* 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)
}
}
- riga 4: importiamo lo script di routing dal server [nuxt];
- righe 10–12: se il server [nuxt] sta eseguendo il codice, utilizziamo la sua funzione di routing;
Lo script [middleware/server/routing] è il seguente:
/* 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 questo script riutilizziamo i concetti già sviluppati e utilizzati nel routing del server [nuxt] dell'applicazione [nuxt-13];
- Aggiungiamo due proprietà all'application store:
- [from]: il nome dell'ultima pagina visualizzata. Sappiamo che il client [nuxt] dispone di questa informazione, ma il server [nuxt] no. Aggiungeremo questa informazione memorizzando il nome della pagina da visualizzare nello store ogni volta che il server [nuxt] esegue il routing. Faremo lo stesso per ogni routing sul client [nuxt]. In questo modo, al successivo routing da parte del server [nuxt], questo troverà nello store il nome dell'ultima pagina visualizzata dall'applicazione;
- [serverRedirection]: quando una destinazione di routing viene rifiutata dal server [nuxt], questo eseguirà un reindirizzamento. Indicherà quindi nello store che la destinazione successiva del server [nuxt] è una pagina di reindirizzamento. Questo reindirizzamento attiverà una nuova esecuzione del router del server [nuxt]. Se il router rileva che la destinazione corrente è il risultato di un reindirizzamento, ne consentirà il proseguimento;
- righe 6–11: recuperiamo le informazioni necessarie per il routing;
- righe 13–16: la destinazione [/end-session] non è associata a una pagina denominata [end-session]. Non ha quindi alcun nome. Ne assegniamo uno;
- riga 19: la destinazione di un possibile reindirizzamento;
- riga 21: [done=true] quando i test di routing sono completati;
- righe 23–27: come accennato, se l'instradamento corrente deriva da un reindirizzamento, non c'è nulla da fare. Infatti, durante l'instradamento precedente, il router ha deciso che il browser del client dovesse essere reindirizzato. Non c'è bisogno di riconsiderare questa decisione;
- righe 29–33: se si tratta di un ricaricamento della pagina, lo lasciamo avvenire. Questa non è una regola universale per tutte le applicazioni [Nuxt]: è necessario esaminare gli effetti di un ricaricamento per ogni pagina. In questo caso, risulta che il ricaricamento delle pagine [index, tax-calculation, simulation-list] non causi effetti indesiderati;
- righe 35–85: il routing del server [nuxt] rispecchia il routing del client [nuxt]. Quando ci si trova su una pagina, il routing del server [nuxt] deve riflettere il menu di navigazione fornito dal client [nuxt] mentre ci si trova su quella pagina;
- righe 38–47: gestiamo innanzitutto il caso in cui la destinazione [end-session] non corrisponda a una pagina esistente. Se le condizioni sono soddisfatte (sessione avviata, utente autenticato), cancelliamo la sessione e reindirizziamo l'utente alla pagina [index];
- righe 49–55: se la sessione JSON con il server di calcolo delle imposte non è stata avviata, l'unica destinazione possibile è la pagina [index];
- righe 57–62: se la sessione JSON è stata avviata e l'utente non è autenticato e non ha richiesto la pagina di autenticazione, allora reindirizziamo alla pagina di autenticazione, che è la pagina [index];
- righe 64–70: se l'utente è autenticato ma i dati [adminData] non sono stati ottenuti, l'utente viene reindirizzato alla pagina di autenticazione. L'autenticazione svolge due funzioni: autentica l'utente e, se l'autenticazione ha esito positivo, richiede anche i dati [adminData]. Se questi dati non sono stati ottenuti, l'autenticazione deve essere riavviata;
- righe 72–85: se i dati [adminData] sono stati ottenuti, gli unici destinatari possibili sono [tax-calculation] e [simulation-list]. In caso contrario, l'instradamento viene negato;
- righe 88–95: l'archivio viene aggiornato a seconda che ci sia o meno un reindirizzamento;
- riga 94: non c'è reindirizzamento. Pertanto, l'attuale [to] diventa il [from] per il routing successivo;
- righe 96–99: le informazioni dell'archivio vengono salvate nel cookie di sessione [nuxt];
- righe 100–103: se è richiesto un reindirizzamento, questo viene eseguito;
Per eseguire i test, assicurati di partire da zero eliminando il cookie di sessione [nuxt] e il cookie di sessione PHP con il server di calcolo delle imposte:

Per testare il routing del server [nuxt], prova tutti gli URL possibili [/, /tax-calculation, /simulation-list] su ogni pagina. Ogni volta, l'applicazione deve rimanere in uno stato coerente.
17.10. Passaggio 9
Il passaggio 9 prevede la distribuzione dell'applicazione [nuxt-20]. Ciò richiede un hosting che fornisca un ambiente [node.js] per eseguire il server [nuxt]. Io non ne dispongo. Il lettore può seguire le procedure descritte nella sezione collegata per distribuire l'applicazione [nuxt-20] sul proprio computer di sviluppo e proteggerla con un protocollo HTTPS.
17.11. Conclusione
Il porting dell'applicazione [vuejs-22] all'applicazione [nuxt-20] è ora completo. Rivediamo alcuni punti chiave di questo porting:
- le pagine [vuejs-22] sono state mantenute;
- le operazioni asincrone presenti nelle pagine [vuejs-22] sono state migrate in una funzione [asyncData];
- in [nuxt-20], abbiamo dovuto gestire due entità: il client [nuxt] e il server [nuxt]. Quest'ultima entità non esisteva in [vuejs-22]. Per mantenere la coerenza tra le due entità, avevamo bisogno di una sessione [nuxt];
- dovevamo gestire il routing del server [nuxt];
In pratica, è probabilmente preferibile iniziare direttamente con un'architettura [nuxt] piuttosto che costruire un'architettura [vue.js] e poi portarla in un ambiente [nuxt].