18. Client Vue.js del server di calcolo delle imposte
18.1. Architecture
Implementeremo un’applicazione client/server con la seguente architettura:

Il server di calcolo delle imposte sarà la versione 14 sviluppata nel documento |https://tahe.developpez.com/tutoriels-cours/php7|
18.2. Le viste dell’applicazione
Le viste dell'applicazione [vuejs-10] sono quelle della versione 13 del documento |https://tahe.developpez.com/tutoriels-cours/php7| del server di calcolo delle imposte quando viene utilizzato in modalità HTML. Tuttavia, nell'applicazione in questione, queste viste saranno generate dal client JavaScript e non dal server PHP.
La prima vista è quella di autenticazione:

La seconda vista è quella del calcolo dell’imposta:

La terza vista è quella che mostra l’elenco delle simulazioni effettuate dall’utente:

La schermata sopra mostra che è possibile eliminare la simulazione n. 1. Si ottiene quindi la seguente schermata:

Se ora si elimina l’ultima simulazione, si ottiene la seguente nuova schermata:

18.3. Elementi del progetto [vuejs-20]
La struttura ad albero del progetto [vuejs-20] è la seguente:

Gli elementi del progetto sono i seguenti:
- [assets/logo.jpg]: il logo del progetto;
- [couches]: i livelli [métier] e [dao] dell’applicazione;
- [plugins]: i plugin dell’applicazione;
- [views]: le viste dell'applicazione;
- [config.js]: configura l'applicazione;
- [router.js]: definisce il routing dell’applicazione;
- [store.js]: lo store di [Vuex];
- [main.js]: lo script principale dell'applicazione;
18.3.1. I livelli [métier] e [dao]
18.3.1.1. Il livello [dao]
Il livello [dao] è implementato dalla classe [Dao] del paragrafo |vuejs-10|
18.3.1.2. Il livello [métier]
Il livello [métier] è implementato dalla classe [Métier] del documento |https://tahe.developpez.com/tutoriels-cours/php7|. È stato aggiunto il seguente metodo [setTaxAdminData]:
// costruttore
constructor(taxAdmindata) {
// this.taxAdminData: dati dell'amministrazione fiscale
this.taxAdminData = taxAdmindata;
}
// setter
setTaxAdminData(taxAdmindata) {
// this.taxAdminData: dati dell'amministrazione fiscale
this.taxAdminData = taxAdmindata;
}
Il metodo [setTaxAdminData] svolge la stessa funzione del costruttore. La sua presenza consente la seguente sequenza:
- istanziare la classe [Métier] con un'istruzione [métier=new Métier()] quando si desidera istanziare la classe ma non si dispone ancora del dato [taxAdminData];
- quindi compilare successivamente la sua proprietà [taxAdminData] tramite un'operazione [métier.setTaxAdminData(taxAdmindata)];
18.3.2. Il file di configurazione [config]
Il file [config.js] è il seguente:
// utilizzo della libreria [axios]
const axios = require('axios');
// timeout delle richieste HTTP
axios.defaults.timeout = 2000;
// il database del server di calcolo delle imposte URL
// lo schema [https] causa problemi a Firefox perché il server di calcolo
// invia un certificato autofirmato. Funziona con Chrome ed Edge. Safari non testato.
axios.defaults.baseURL = 'https://localhost/php7/scripts-web/impots/version-14';
// utilizzeremo i cookie
axios.defaults.withCredentials = true;
// esportazione della configurazione
export default {
axios: axios
}
Questa configurazione è quella della libreria [axios] che il livello [dao] utilizza per effettuare le proprie richieste HTTP. Si noti, alla riga 8, che il server opera su una porta sicura [https].
18.3.3. I plugin
I plugin [pluginDao, pluginMétier, pluginConfig] hanno lo scopo di creare tre nuove proprietà nella funzione/classe [Vue]:
- [$dao]: avrà come valore un'istanza della classe [Dao];
- [$métier]: avrà come valore un'istanza della classe [Métier];
- [$config]: avrà come valore l'oggetto esportato dal file di configurazione [config];
[pluginDao]
export default {
install(Vue, dao) {
// aggiunge una proprietà [$dao] alla classe Vue
Object.defineProperty(Vue.prototype, '$dao', {
// quando viene richiamata Vue.$dao, si restituisce il secondo parametro [dao]
get: () => dao,
})
}
}
[pluginMétier]
export default {
install(Vue, métier) {
// aggiunge una proprietà [$métier] alla classe Vue
Object.defineProperty(Vue.prototype, '$métier', {
// quando viene richiamato Vue.$métier, si imposta il secondo parametro su [métier]
get: () => métier,
})
}
}
[pluginConfig]
export default {
install(Vue, config) {
// aggiunge una proprietà [$config] alla classe Vue
Object.defineProperty(Vue.prototype, '$config', {
// quando viene fatto riferimento a Vue.$config, si imposta il secondo parametro su [config]
get: () => config,
})
}
}
18.3.4. Il negozio [Vuex]
Lo store [Vuex] è implementato dal seguente file [store]:
// plugin Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
// store Vuex
const store = new Vuex.Store({
state: {
// la tabella delle simulazioni
simulations: [],
// il numero dell'ultima simulazione
idSimulation: 0
},
mutations: {
// eliminazione della riga n. indice
deleteSimulation(state, index) {
// eslint-disable-next-line no-console
console.log("mutation deleteSimulation");
// si elimina la riga n. [index]
state.simulations.splice(index, 1);
// eslint-disable-next-line no-console
console.log("store simulations", state.simulations);
},
// aggiunta di una simulazione
addSimulation(state, simulation) {
// eslint-disable-next-line no-console
console.log("mutation addSimulation");
// numero della simulazione
state.idSimulation++;
simulation.id = state.idSimulation;
// si aggiunge la simulazione alla tabella delle simulazioni
state.simulations.push(simulation);
},
// pulizia dello stato
clear(state) {
state.simulations = [];
state.idSimulation = 1;
}
}
});
// esportazione dell'oggetto [store]
export default store;
Commenti
- righe 2-4: il plugin [Vuex] è integrato nel framework [Vue];
- righe 8-13: inseriamo nello store di [Vuex] i seguenti elementi:
- [simulations]: l'elenco delle simulazioni effettuate dall'utente;
- [idSimulation]: il numero dell'ultima simulazione effettuata dall'utente;
Ricordiamo che lo store sarà condiviso tra le viste e che il suo contenuto è reattivo: quando viene modificato, le viste che lo utilizzano vengono aggiornate automaticamente. Nella nostra applicazione, solo l’elemento [simulations] deve essere reattivo, non l’elemento [idSimulation]. Abbiamo lasciato questo elemento nello store per comodità;
- righe 14-40: le modifiche consentite sull’oggetto [state] delle righe 8-13. Si ricorda che queste ricevono sempre l’oggetto [state] delle righe 8-13 come primo parametro;
- riga 16: la modifica [deleteSimulation] consente di eliminare una simulazione il cui numero è [index];
- riga 25: la modifica [addSimulation] consente di aggiungere una nuova simulazione alla tabella delle simulazioni;
- riga 35: la modifica [clear] consente di reimpostare l’oggetto [state] delle righe 8-13;
18.3.5. Il file di instradamento [router]
Il file di instradamento è il seguente:
// importazioni
import Vue from 'vue'
import VueRouter from 'vue-router'
// le viste
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'
// plugin di routing
Vue.use(VueRouter)
// percorsi dell'applicazione
const routes = [
// autenticazione
{
path: '/', name: 'authentification', component: Authentification
},
// calcolo delle imposte
{
path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
},
// elenco delle simulazioni
{
path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations
},
// fine sessione
{
path: '/fin-session', name: 'finSession', component: Authentification
}
]
// il router
const router = new VueRouter({
// le rotte
routes,
// modalità di visualizzazione delle rotte nel navigatore
mode: 'history',
})
// esportazione del router
export default router
Commenti
- riga 16: all’avvio dell’applicazione viene visualizzata la vista [Authentification] poiché il suo URL è la radice [/];
- riga 20: la vista [CalculImpot] viene visualizzata quando viene richiesta la vista URL [/calcul-impot];
- riga 24: la vista [ListeSimulations] viene visualizzata quando viene richiesta la vista URL o [/liste-des-simualtions];
- riga 28: la vista [Authentification] viene visualizzata quando viene richiesta la vista URL [/fin-session];
- righe 33-38: viene creato un oggetto [router] con questi percorsi (riga 35) e la modalità [history] (riga 37) di gestione di URL;
- riga 41: questo router viene esportato;
18.3.6. Lo script principale [main.js]
Lo script [main.js] è il seguente:
// importazioni
import Vue from 'vue'
// schermata principale
import Main from './views/Main.vue'
// plugin [bootstrap-vue]
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue);
// CSS bootstrap
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
// router
import router from './router'
// plugin [config]
import config from './config';
import pluginConfig from './plugins/pluginConfig'
Vue.use(pluginConfig, config)
// istanza del livello [dao]
import Dao from './couches/Dao';
const dao = new Dao(config.axios);
// plugin [dao]
import pluginDao from './plugins/pluginDao'
Vue.use(pluginDao, dao)
// istanza del livello [métier]
import Métier from './couches/Métier';
const métier = new Métier();
// plugin [métier]
import pluginMétier from './plugins/pluginMétier'
Vue.use(pluginMétier, métier)
// store Vuex
import store from './store'
// avvio dell'app UI
new Vue({
el: '#app',
// il router
router: router,
// la tenda Vuex
store: store,
// la schermata principale
render: h => h(Main),
})
Si notino i seguenti punti:
- righe 18-21: l’oggetto esportato dallo script [./config] sarà disponibile nell’attributo [Vue.$config] e quindi accessibile a tutte le viste dell’applicazione. In questo caso non era necessario, poiché l’oggetto [config] viene utilizzato solo dallo script [main] (riga 25). Tuttavia, capita spesso che la configurazione sia necessaria per più viste. Abbiamo quindi voluto mantenere il principio di renderla disponibile in un attributo della vista;
- righe 24-25: istanziamento del livello [dao]. La classe [Dao] viene importata alla riga 24 e poi istanziata alla riga 25. Il suo costruttore accetta come unico parametro l’oggetto [axios], proprietà di configurazione;
- righe 27-29: il livello [dao] viene reso disponibile nell’attributo [$dao] di tutte le viste;
- righe 31-37: si ripete la stessa sequenza per il livello [métier]. Il costruttore della classe [Métier] ha come parametro [taxAdminData], che rappresenta i dati dell’amministrazione fiscale. Non disponiamo ancora di questo dato. L’oggetto [métier] della riga 33 dovrà quindi essere completato in un secondo momento;
- riga 40: si importa lo store [Vuex];
- righe 43-51: si istanzia la vista principale [Main] (righe 5 e 50), passandole due parametri:
- riga 46: il router [router] definito alla riga 16;
- riga 48: si definisce lo store [Vuex] [store] alla riga 40;
- in entrambi i casi, il nome della proprietà è a sinistra e il suo valore a destra. I nomi delle proprietà [router, store] sono definiti dai framework [vue-router] e [vuex]. I valori associati possono invece essere qualsiasi;
18.4. Le viste dell’applicazione
18.4.1. La vista principale [Main]
Il codice della vista principale [Main] è il seguente:
<!-- definizione HTML della vista -->
<template>
<div class="container">
<b-card>
<!-- jumbotron -->
<b-jumbotron>
<b-row>
<b-col cols="4">
<img src="../assets/logo.jpg" alt="Cerisier en fleurs" />
</b-col>
<b-col cols="8">
<h1>Calculez votre impôt</h1>
</b-col>
</b-row>
</b-jumbotron>
<!-- errore nella richiesta HTTP -->
<b-alert
show
variant="danger"
v-if="showError"
>L'erreur suivante s'est produite : {{error.message}}</b-alert>
<!-- vista corrente -->
<router-view v-if="showView" @loading="mShowLoading" @error="mShowError" />
<!-- caricamento in corso -->
<b-alert show v-if="showLoading" variant="light">
<strong>Requête au serveur de calcul d'impôt en cours...</strong>
<div class="spinner-border ml-auto" role="status" aria-hidden="true"></div>
</b-alert>
</b-card>
</div>
</template>
<script>
export default {
// nome
name: "app",
// stato interno
data() {
return {
// controllo avviso in attesa
showLoading: false,
// controllo dell'allarme di errore
showError: false,
// controlla la visualizzazione della vista di instradamento corrente
showView: true,
// un messaggio di errore
error: ""
};
},
// gestori di eventi
methods: {
// errore nella richiesta asincrona
mShowError(error) {
// eslint-disable-next-line
console.log("Main evt error");
// viene visualizzato il messaggio di errore
this.error = error;
this.showError = true;
// si nasconde la vista instradata
this.showView = false;
// si nasconde il messaggio di attesa
this.showLoading = false;
},
// visualizzazione o meno di un'icona di attesa
mShowLoading(value) {
// eslint-disable-next-line
console.log("Main evt showLoading");
// visualizza o meno l'avviso di attesa
this.showLoading = value;
}
}
};
</script>
Commenti
- la vista [Main] garantisce l’impaginazione della vista instradata e visualizzata alla riga 23:

- le righe 5-15 visualizzano l'area 1;
- la riga 23 visualizza la vista instradata [2];
- righe 16-19: un avviso visualizzato solo in caso di errore di comunicazione con il server di calcolo delle imposte;
- righe 25-28: un messaggio di attesa visualizzato ad ogni richiesta HTTP inviata al server;
- tutte le viste verranno visualizzate con questo layout poiché ogni vista instradata viene visualizzata dalle righe 20-24. La vista [Main] serve a fattorizzare ciò che può essere condiviso dalle diverse viste;
- riga 23: ogni vista instradata può generare tre eventi:
- [loading]: è stata avviata una richiesta HTTP. È necessario visualizzare il messaggio di attesa della risposta;
- [error]: la richiesta HTTP si è conclusa con un errore. È necessario visualizzare il messaggio di errore e nascondere la vista instradata;
- righe 38-49: lo stato della vista:
- riga 41: [showLoading] controlla la visualizzazione del messaggio di attesa della fine di una richiesta HTTP (riga 25);
- riga 43: [showError] controlla la visualizzazione del messaggio di errore di una richiesta HTTP (righe 17-21);
- riga 45: [showView] controlla la visualizzazione della vista instradata (riga 23);
- righe 53-63: il metodo [mShowError] gestisce l’evento [error] emesso dalla vista instradata (riga 23);
- righe 65-70: il metodo [mShowLoading] gestisce l’evento [loading] emesso dalla vista instradata (riga 23);
- riga 23: si presterà attenzione agli eventi [error] e [loading]. Essi vengono intercettati solo se la vista instradata è visualizzata ([showView=true]). Per questo motivo la vista instradata viene inizialmente visualizzata (riga 45). Viene nascosta solo in caso di errore (riga 60). Per evitare questo problema si sarebbe potuta utilizzare la direttiva [v-show] al posto di [v-if]. La differenza tra queste due direttive è la seguente:
- [v-if=’false’] nasconde il blocco controllato eliminandolo dal codice globale HTML. Gli eventi della vista instradata non possono quindi più essere intercettati;
- [v-show=’false’] nasconde il blocco controllato modificandone il CSS, ma il codice del blocco rimane presente nel HTML globale e può quindi intercettare gli eventi della vista instradata;
18.4.2. La vista di layout [Layout]
Il codice della vista [Layout] è il seguente:
<!-- definizione HTML del layout della vista instradata -->
<template>
<!-- riga -->
<div>
<b-row>
<!-- area a tre colonne a sinistra -->
<b-col cols="3" v-if="left">
<slot name="left" />
</b-col>
<!-- area a nove colonne a destra -->
<b-col cols="9" v-if="right">
<slot name="right" />
</b-col>
</b-row>
</div>
</template>
<script>
export default {
// parametri della vista
props: {
// controlla la colonna di sinistra
left: {
type: Boolean
},
// controllo della colonna di destra
right: {
type: Boolean
}
}
};
</script>
Commenti
- La vista [Layout] consente di dividere la vista instradata in due aree:
- un'area a 3 colonne Bootstrap a sinistra (righe 7-9). Quest'area ospiterà il menu di navigazione, qualora presente;
- un'area a 9 colonne a destra (righe 11-13). Quest'area ospiterà le informazioni fornite dalla vista instradata;
18.4.3. La vista [Authentification]
La vista di autenticazione è la seguente:

Questa vista si ottiene dalla vista [Layout] eliminando la colonna di sinistra per visualizzare solo quella di destra.
Il suo codice è il seguente:
<!-- definizione HTML della vista -->
<template>
<Layout :left="false" :right="true">
<template slot="right">
<!-- modulo HTML - i valori vengono inviati tramite l'azione [authentifier-utilisateur] -->
<b-form @submit.prevent="login">
<!-- titolo -->
<b-alert show variant="primary">
<h4>Bienvenue. Veuillez vous authentifier pour vous connecter</h4>
</b-alert>
<!-- prima riga -->
<b-form-group label="Nom d'utilisateur" label-for="user" label-cols="3">
<!-- campo di immissione utente -->
<b-col cols="6">
<b-form-input type="text" id="user" placeholder="Nom d'utilisateur" v-model="user" />
</b-col>
</b-form-group>
<!-- seconda riga -->
<b-form-group label="Mot de passe" label-for="password" label-cols="3">
<!-- campo di inserimento password -->
<b-col cols="6">
<b-input type="password" id="password" placeholder="Mot de passe" v-model="password" />
</b-col>
</b-form-group>
<!-- terza riga -->
<b-alert
show
variant="danger"
v-if="showError"
class="mt-3"
>L'erreur suivante s'est produite : {{message}}</b-alert>
<!-- pulsante di tipo [submit] sulla terza riga -->
<b-row>
<b-col cols="2">
<b-button variant="primary" type="submit" :disabled="!valid">Valider</b-button>
</b-col>
</b-row>
</b-form>
</template>
</Layout>
</template>
<!-- dinamica della vista -->
<script>
import Layout from "./Layout";
export default {
// stato del componente
data() {
return {
// utente
user: "",
// la sua password
password: "",
// controlla la visualizzazione di un messaggio di errore
showError: false,
// il messaggio di errore
message: "",
// sessione avviata
sessionStarted: false
};
},
// componenti utilizzati
components: {
Layout
},
// proprietà calcolate
computed: {
// dati inseriti validi
valid() {
return this.user && this.password && this.sessionStarted;
}
},
// gestori di eventi
methods: {
// ----------- autenticazione
async login() {
try {
// inizio attesa
this.$emit("loading", true);
// autenticazione bloccante presso il server
const response = await this.$dao.authentifierUtilisateur(
this.user,
this.password
);
// fine del caricamento
this.$emit("loading", false);
// analisi della risposta
if (response.état != 200) {
// viene visualizzato l'errore
this.message = response.réponse;
this.showError = true;
return;
}
// nessun errore
this.showError = false;
// --------- ora si richiedono i dati dell'amministrazione fiscale
// inizio attesa
this.$emit("loading", true);
// richiesta in sospeso presso il server
const response2 = await this.$dao.getAdminData();
// fine del caricamento
this.$emit("loading", false);
// analisi della risposta
if (response2.état != 1000) {
// viene visualizzato l'errore
this.message = response2.réponse;
this.showError = true;
return;
}
// nessun errore
this.showError = false;
// i dati ricevuti vengono memorizzati nel livello [métier]
this.$métier.setTaxAdminData(response2.réponse);
// si passa alla schermata del calcolo dell'imposta
this.$router.push({ name: "calculImpot" });
} catch (error) {
// si segnala l'errore al componente principale
this.$emit("error", error);
}
}
},
// ciclo di vita: il componente è stato appena creato
created() {
// eslint-disable-next-line
console.log("authentification", "created");
// si avvia una sessione jSON con il server
// inizio attesa
this.$emit("loading", true);
// si inizializza la sessione con il server - richiesta asincrona
// si utilizza la promessa restituita dai metodi del livello [dao]
this.$dao
// si inizializza una sessione jSON
.initSession()
// si è ottenuta la risposta
.then(response => {
// fine attesa
this.$emit("loading", false);
// analisi della risposta
if (response.état != 700) {
// viene visualizzato l'errore
this.message = response.réponse;
this.showError = true;
return;
}
// la sessione è stata avviata
this.sessionStarted = true;
})
// in caso di errore
.catch(error => {
// l'errore viene segnalato alla vista [Main]
this.$emit("error", error);
});
}
};
</script>
Commenti
- riga 3: la vista [Authentification] utilizza solo la colonna di destra del [Layout] (righe 3 e 4);
- righe 6-38: il modulo Bootstrap che genera l’area 1 della schermata sopra riportata;
- riga 6: l’evento [@submit] si verifica quando l’utente clicca sul pulsante di tipo [submit] della riga 35. Il modificatore [prevent] richiede che la pagina non venga ricaricata durante l’evento [submit]. Si sarebbe potuto scrivere anche:
- un tag <b-form> senza gestione dell’evento [submit];
- un tag <b-button> con l’evento [@click=’login’] e senza l’attributo [type=’submit’];
Funziona lo stesso. Il vantaggio della soluzione scelta è che l’invio avviene non solo cliccando sul pulsante [Valider], ma anche tramite la convalida (tasto [Entrée]) nei campi di inserimento. È quindi per comodità dell’utente che in questo caso è stata scelta la soluzione [<b-form @submit.prevent="login">];
- righe 33-37: un messaggio di avviso che appare quando il server ha rifiutato le credenziali inserite dall’utente:

- riga 35: il pulsante [Valider] non è sempre attivo. Il suo stato dipende dall’attributo calcolato [valid] delle righe 71-73. L’attributo [valid] è vero se:
- i campi [user, password] del modulo contengono dei dati;
- la sessione jSON è stata avviata. Inizialmente, questa sessione non è stata avviata (riga 59) e quindi il pulsante [Valider] è inattivo.
- righe 49-60: lo stato della vista;
- [user] rappresenta l’immissione dell’utente nel campo [user] (righe 12-17) del modulo. La direttiva [v-model] alla riga 15 stabilisce un collegamento bidirezionale tra l’input dell’utente e l’attributo [user] della vista;
- [password] rappresenta l’immissione dell’utente nel campo [password] (righe 19-24) del modulo. La direttiva [v-model] alla riga 22 stabilisce un collegamento bidirezionale tra l'input dell'utente e l'attributo [password] della vista;
- [showError] controlla (riga 29) la visualizzazione dell’avviso delle righe 26-31;
- [message] è il messaggio di errore (riga 31) da visualizzare nell’avviso delle righe 26-31;
- [sessionStarted] indica se la sessione jSON con il server è stata avviata o meno. Inizialmente questo attributo ha il valore [false] (riga 59). La sessione jSON con il server viene inizializzata nell’evento [created] del ciclo di vita della vista, righe 126-156. Se il server risponde positivamente, l’attributo [sessionStarted] viene aggiornato a [true] (riga 149);
- righe 126-156: la funzione [created] viene eseguita quando la vista [Authentification] è stata creata (ma non necessariamente visualizzata). In background, viene quindi inizializzata una sessione jSON con il server. Si sa che questa è la prima operazione da eseguire con il server di calcolo delle imposte. A tal fine, si utilizza il livello [dao] dell’applicazione (riga 134). Tutti i metodi di questo livello sono asincroni. Qui si utilizza la promessa (Promise) restituita dal metodo [$dao.initSession] che inizializza la sessione jSON con il server.
- righe 138-150: il codice eseguito quando il server ha restituito la risposta senza errori;
- riga 142: si verifica la proprietà [état] della risposta. Per un’operazione riuscita, deve avere il valore [700]. In caso contrario, si è verificato un errore la cui causa è indicata nella proprietà [response.réponse] (riga 144). Viene quindi visualizzato il messaggio di errore della vista (riga 145);
- riga 149: si nota che la sessione jSON è stata avviata;
- righe 152-155: il codice eseguito in caso di errore. L’errore viene segnalato alla vista padre [Main], che
- visualizzerà l’errore;
- nasconderà il messaggio di attesa;
- nasconderà la vista instradata, la vista [Autentification];
- righe 79-124: il metodo [login] gestisce il clic sul pulsante [Valider];
- riga 79: al metodo è stata aggiunta la parola chiave [async] per consentire l’uso della parola chiave [await], righe 84 e 103;
- righe 84-87: chiamata bloccante al metodo [$dao.authentifierUtilisateur(user, password)]. Si sarebbe potuto utilizzare una promessa [Promise] come è stato fatto nella funzione [created]. Abbiamo voluto variare gli stili. Non c’è alcun rischio di bloccare l’utente poiché abbiamo impostato un [timeout] di 2 secondi su tutte le richieste HTTP. Non dovrà attendere a lungo. Inoltre, non può fare nulla finché il server non ha restituito la risposta, poiché in tal caso il pulsante [Valider] rimane inattivo;
- riga 91: il server di calcolo delle imposte invia risposte jSON, tutte con la struttura [{‘action’:action, ‘état’:val, ‘réponse’:réponse}]. L’autenticazione ha avuto esito positivo se [état==200]. In caso contrario, viene visualizzato un messaggio di errore, righe 93-94;
- riga 98: si nasconde un eventuale messaggio di errore relativo a un’operazione precedente;
- righe 99-116: ora si richiedono al server i dati dell’amministrazione fiscale che consentono il calcolo dell’imposta. In [this.$métier] abbiamo un’istanza della classe [Métier] che per il momento non può fare nulla poiché non dispone di tali dati;
- riga 103: i dati dell’amministrazione fiscale vengono richiesti al server tramite un’operazione bloccante;
- righe 107-112: viene analizzata la risposta del server. Deve avere un valore di stato pari a 1000; in caso contrario, significa che si è verificato un errore. In quest’ultimo caso, viene visualizzato il messaggio di errore (righe 109-110);
- righe 113-118: se l’operazione va a buon fine, si:
- si nasconde il messaggio di errore, riga 114;
- si trasmettono i dati dell’amministrazione fiscale al livello [métier] (riga 116);
- si visualizza la vista [CalculImpot], riga 118. Ricordiamo che [this.$router] indica il router dell’applicazione. Il metodo [push] consente di impostare la vista successiva da instradare. Qui la si identifica tramite il suo attributo [name]. Si sarebbe potuto identificarla anche tramite il suo attributo [path]. Queste informazioni si trovano nel file di instradamento:
// calcolo dell'imposta
{
path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
},
- righe 119-122: il [catch] si attiva quando una delle due richieste HTTP ha dato esito negativo (server non presente, timeout superato, ...). L’errore viene quindi segnalato alla vista padre [Main], che lo visualizzerà, nascondendo il messaggio di attesa e la vista [Authentification];
18.4.4. La vista [CalculImpot]
La vista [CalculImpot] è la seguente:

- [1]: un menu di navigazione occupa la colonna di sinistra della vista instradata;
- [2]: il modulo di calcolo dell’imposta occupa la colonna di destra della vista indirizzata;
Il codice della vista [CalculImpot] è il seguente:
<!-- definizione HTML della vista -->
<template>
<div>
<Layout :left="true" :right="true">
<!-- modulo di calcolo dell'imposta a destra -->
<FormCalculImpot slot="right" @resultatObtenu="handleResultatObtenu" />
<!-- menu di navigazione a sinistra -->
<Menu slot="left" :options="options" />
</Layout>
<!-- area di visualizzazione dei risultati del calcolo dell'imposta sotto il modulo -->
<b-row v-if="résultatObtenu" class="mt-3">
<!-- area vuota a tre colonne -->
<b-col cols="3" />
<!-- area a nove colonne -->
<b-col cols="9">
<b-alert show variant="success">
<span v-html="résultat"></span>
</b-alert>
</b-col>
</b-row>
</div>
</template>
<script>
// importazioni
import FormCalculImpot from "./FormCalculImpot";
import Menu from "./Menu";
import Layout from "./Layout";
export default {
// stato interno
data() {
return {
// opzioni del menu
options: [
{
text: "Liste des simulations",
path: "/liste-des-simulations"
},
{
text: "Fin de session",
path: "/fin-session"
}
],
// risultato del calcolo dell'imposta
résultat: "",
résultatObtenu: false
};
},
// componenti utilizzati
components: {
Layout,
FormCalculImpot,
Menu
},
// metodi di gestione degli eventi
methods: {
// risultato del calcolo dell'imposta
handleResultatObtenu(résultat) {
// si costruisce il risultato in sequenza 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;
// visualizzazione del risultato
this.résultatObtenu = true;
// ---- aggiornamento dello store [Vuex]
// una simulazione di +
this.$store.commit("addSimulation", résultat);
}
}
};
</script>
Commenti
- riga 4: qui sono presenti entrambe le colonne di [Layout];
- riga 6: il modulo di calcolo dell’imposta occupa la colonna di destra. Emette l’evento [resultatObtenu] quando si ottiene il risultato del calcolo dell’imposta. Si noti che i nomi degli eventi e dei metodi che li gestiscono non possono contenere caratteri accentati;
- riga 8: il menu di navigazione occupa la colonna di sinistra;
- righe 11-20: il risultato del calcolo dell’imposta viene visualizzato sotto il modulo:

- riga 11: il risultato viene visualizzato solo se l'attributo [résultatObtenu] (riga 47) è pari a [true];
- righe 34-48: lo stato della vista:
- [options]: l'elenco delle opzioni del menu di navigazione. Questa tabella viene passata come parametro al componente [Menu], riga 8;
- [résultat]: il risultato del calcolo dell’imposta. Questo risultato è una stringa HTML. Per questo motivo è stata utilizzata la direttiva [v-html] alla riga 17 per visualizzarlo;
- [résultatObtenu]: il valore booleano che controlla la visualizzazione del risultato, riga 11;
- righe 59-81: il metodo [handleResultatObtenu] visualizza il risultato del calcolo dell’imposta che gli è stato inviato dalla vista figlia [FormCalculImpot], riga 6. Questo risultato è un oggetto con le proprietà [impot, décôte, réduction, surcôte, taux, marié, enfants, salaire];
- righe 61-75: si inserisce l’oggetto [impot, décôte, réduction, surcôte, taux] in un testo HTML che viene visualizzato dalla riga 17 del template;
- riga 77: si visualizza questo risultato;
- riga 80: si richiama la mutazione [addSimulation] dello store Vuex, che aggiungerà [résultat] alle simulazioni già presenti nello store;
18.4.5. Il menu di navigazione [Menu]
Il menu di navigazione viene visualizzato nella colonna di sinistra delle viste instradate:

Il codice della vista [Menu] è il seguente:
<!-- definizione HTML della vista -->
<template>
<!-- menu Bootstrap verticale -->
<b-nav vertical>
<!-- opzioni del menu -->
<b-nav-item
v-for="(option,index) of options"
:key="index"
:to="option.path"
exact
exact-active-class="active"
>{{option.text}}</b-nav-item>
</b-nav>
</template>
<script>
export default {
// parametri della vista
props: {
options: {
type: Array
}
}
};
</script>
Commenti
- le opzioni del menu sono fornite dal parametro [options] (righe 7, 20-22);
- ogni elemento della tabella [options] ha una proprietà [text] (riga 12) che rappresenta il testo del collegamento e una proprietà [path] (riga 9) che indica il percorso della vista di destinazione del collegamento;
18.4.6. La vista [FormCalculImpot]
Questa vista fornisce il modulo per il calcolo dell’imposta:

Il suo codice è il seguente:
<!-- definizione HTML della vista -->
<template>
<!-- modulo HTML -->
<b-form @submit.prevent="calculerImpot" class="mb-3">
<!-- messaggio su 12 colonne su sfondo blu -->
<b-alert show variant="primary">
<h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
</b-alert>
<!-- elementi del modulo -->
<!-- prima riga -->
<b-form-group label="Etes-vous marié(e) ou pacsé(e) ?" label-cols="4">
<!-- pulsanti di opzione su 5 colonne-->
<b-col cols="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>
<!-- seconda riga -->
<b-form-group label="Nombre d'enfants à charge" label-cols="4" label-for="enfants">
<b-input
type="text"
id="enfants"
placeholder="Indiquez votre nombre d'enfants"
v-model="enfants"
:state="enfantsValide"
/>
<!-- eventuale messaggio di errore -->
<b-form-invalid-feedback :state="enfantsValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
</b-form-group>
<!-- terza riga -->
<b-form-group
label="Salaire annuel"
label-cols="4"
label-for="salaire"
description="Arrondissez à l'euro inférieur"
>
<b-input
type="text"
id="salaire"
placeholder="Salaire annuel"
v-model="salaire"
:state="salaireValide"
/>
<!-- eventuale messaggio di errore -->
<b-form-invalid-feedback :state="salaireValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
</b-form-group>
<!-- quarta riga, pulsante [submit] su 5 colonne -->
<b-col cols="5">
<b-button type="submit" variant="primary" :disabled="formInvalide">Valider</b-button>
</b-col>
</b-form>
</template>
<!-- script -->
<script>
export default {
// stato interno
data() {
return {
// sposato o no
marié: "non",
// numero di figli
enfants: "",
// stipendio annuo
salaire: ""
};
},
// stato interno calcolato
computed: {
// convalida del modulo
formInvalide() {
return (
// stipendio non valido
!this.salaireValide ||
// o figli non validi
!this.enfantsValide ||
// o dati fiscali non disponibili
!this.$métier.taxAdminData
);
},
// convalida dello stipendio
salaireValide() {
// deve essere un numero >=0
return Boolean(this.salaire.match(/^\s*\d+\s*$/));
},
// convalida dei figli
enfantsValide() {
// deve essere un numero >=0
return Boolean(this.enfants.match(/^\s*\d+\s*$/));
}
},
// gestore eventi
methods: {
calculerImpot() {
// si calcola l'imposta utilizzando il livello [métier]
const résultat = this.$métier.calculerImpot(
this.marié,
this.enfants,
this.salaire
);
// eslint-disable-next-line
console.log("résultat=", résultat);
// si completa il risultato
résultat.marié = this.marié;
résultat.enfants = this.enfants;
résultat.salaire = this.salaire;
// si emette l'evento [resultatObtenu]
this.$emit("resultatObtenu", résultat);
}
}
};
</script>
Commenti
- righe 4-51: il modulo Bootstrap;
- righe 11-17: un gruppo di pulsanti di opzione con le relative etichette;
- righe 14-15: il tag <b-form-radio> garantisce la visualizzazione di un pulsante radio:
- riga 14: la direttiva [v-model] garantisce che, quando si fa clic sul pulsante, l'attributo [marié] della riga 61 riceva il valore [oui] (attributo [value="oui"]);
- riga 15: la direttiva [v-model] garantisce che, quando si fa clic sul pulsante, l'attributo [marié] della riga 61 assuma il valore [non] (attributo [value="non"]);
- righe 19-29: la sezione relativa all’inserimento del numero di figli:
- riga 24: l’inserimento del numero di figli è collegato all’attributo [enfants] della riga 63;
- riga 25: la validità dell’inserimento viene verificata dall’attributo calcolato [enfantsValide] delle righe 87-89;
- riga 28: garantisce la visualizzazione di un messaggio di errore se l’inserimento non è valido;
- righe 31-45: la parte relativa all’inserimento dello stipendio annuale:
- riga 35: visualizza un messaggio di aiuto proprio sotto il campo di immissione;
- riga 41: l’immissione dello stipendio è collegata all’attributo [salaire] della riga 65;
- riga 42: la validità dell’immissione viene verificata dall’attributo calcolato [salaireValide] delle righe 82-85;
- riga 45: garantisce la visualizzazione di un messaggio di errore se l’immissione non è valida;
- righe 48-50: un pulsante di tipo [submit]. Quando si fa clic su questo pulsante o quando si conferma un inserimento con il tasto [Entrée], viene eseguito il metodo [calculerImpot] (riga 94);
- riga 49: lo stato del pulsante (attivo/inattivo) è controllato dall’attributo calcolato [formInvalide] delle righe 71-80;
- righe 71-80: il modulo è valido se:
- il numero di figli è valido;
- lo stipendio è valido;
- l’applicazione ha ottenuto dal server i dati dell’amministrazione fiscale che consentono il calcolo dell’imposta. Si ricorda che questo dato è registrato nella proprietà [$métier.taxAdminData]. La vista [FormCalculImpot] può essere visualizzata prima che questo dato sia stato ottenuto, poiché viene richiesto in modo asincrono contemporaneamente alla visualizzazione della vista. In questo modo si garantisce che l’utente non possa cliccare sul pulsante [Valider] finché il dato non è stato ottenuto;
- righe 94-109: il metodo di calcolo dell’imposta:
- righe 96-100: è il livello [métier] che esegue questo calcolo. Si tratta di un calcolo sincrono. Una volta ottenuto il dato [taxAdminData], il client [Vue] non deve più comunicare con il server. Tutto avviene localmente. Si ottiene un oggetto [résultat] con le proprietà [impôt, décôte, surcôte, réduction, taux];
- righe 104-106: si aggiungono le proprietà [marié, enfants, salaire] al risultato;
- riga 108: il risultato viene passato alla vista padre [CalculImpot] tramite l’evento [resultatObtenu]. Questa vista ha il compito di visualizzare il risultato;
18.4.7. La vista [ListeSimulations]
La vista [ListeSimulations] visualizza l’elenco delle simulazioni effettuate dall’utente:

Il codice della vista è il seguente:
<!-- definizione HTML della vista -->
<template>
<div>
<!-- impaginazione -->
<Layout :left="true" :right="true">
<!-- simulazioni nella colonna di destra -->
<template slot="right">
<template v-if="simulations.length==0">
<!-- nessuna simulazione -->
<b-alert show variant="primary">
<h4>Votre liste de simulations est vide</h4>
</b-alert>
</template>
<template v-if="simulations.length!=0">
<!-- ci sono simulazioni -->
<b-alert show variant="primary">
<h4>Liste de vos simulations</h4>
</b-alert>
<!-- tabella delle simulazioni -->
<b-table striped hover responsive :items="simulations" :fields="fields">
<template v-slot:cell(action)="data">
<b-button variant="link" @click="supprimerSimulation(data.index)">Supprimer</b-button>
</template>
</b-table>
</template>
</template>
<!-- menu di navigazione nella colonna di sinistra -->
<Menu slot="left" :options="options" />
</Layout>
</div>
</template>
<script>
// importazioni
import Layout from "./Layout";
import Menu from "./Menu";
export default {
// componenti
components: {
Layout,
Menu
},
// stato interno
data() {
return {
// opzioni del menu di navigazione
options: [
{
text: "Calcul de l'impôt",
path: "/calcul-impot"
},
{
text: "Fin de session",
path: "/fin-session"
}
],
// parametri della tabella 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" }
]
};
},
// stato interno calcolato
computed: {
// elenco delle simulazioni prelevate dallo store Vuex
simulations() {
return this.$store.state.simulations;
}
},
// metodi
methods: {
supprimerSimulation(index) {
// eslint-disable-next-line
console.log("supprimerSimulation", index);
// eliminazione della simulazione n. [index]
this.$store.commit("deleteSimulation", index);
}
}
};
</script>
Commenti
- riga 5: la vista occupa entrambe le colonne del layout [Layout] delle viste instradate;
- righe 7-26: le simulazioni vengono visualizzate nella colonna di destra;
- riga 28: il menu di navigazione va nella colonna di sinistra;
- righe 8, 14, 20, 75: le simulazioni provengono dallo store [Vuex] [$this.store];
- righe 8-13: avviso visualizzato quando l'elenco delle simulazioni è vuoto;
- righe 14-25: la tabella HTML viene visualizzata quando l'elenco delle simulazioni non è vuoto;
- righe 20-24: la tabella HTML viene generata da un tag <b-table>;
- riga 20: la tabella delle simulazioni è fornita dall’attributo calcolato [simulations] delle righe 74-76;
- riga 20: la configurazione della tabella HTML è definita dall’attributo calcolato [fields] delle righe 58-69. Riga 67: la colonna chiave [action] è l’ultima colonna della tabella HTML;
- righe 21-23: modello dell'ultima colonna della tabella HTML;
- riga 22: qui si inserisce un pulsante di tipo link. Quando si fa clic su di esso, viene chiamato il metodo [supprimerSimulation(data.index)], dove [data] rappresenta la riga corrente (riga 21). [data.index] rappresenta il numero di questa riga nell’elenco delle righe visualizzate;
- riga 28: generazione del menu di navigazione. Le opzioni di quest’ultimo sono fornite dall’attributo [options] delle righe 47-56;
- righe 80-85: il metodo che risponde al clic su un link [Supprimer] della pagina HTML;
- riga 84: viene richiamata la mutazione [deleteSimulation] dello store [Vuex] (cfr. paragrafo |vuejs-15|);
18.5. Esecuzione del progetto

È inoltre necessario avviare il server [Laragon] (cfr. documento |https://tahe.developpez.com/tutoriels-cours/php7|) affinché il server di calcolo delle imposte sia online.
18.6. Implementazione dell'applicazione su un server locale
Attualmente, il nostro client [Vue] è distribuito su un server di test all'indirizzo URL [http://localhost:8080]. Lo implementeremo sul server [Laragon] presso URL e [http://localhost:80]. Per raggiungere questo obiettivo, è necessario seguire diversi passaggi.
Fase 1
Per prima cosa, faremo in modo che il client [Vue] venga distribuito sul server di test URL e [http://localhost:8080/client-vuejs-impot/].
Creiamo un file [vue.config.js] nella directory principale del nostro progetto attuale [VSCode]:

Il file [vue.config.js] [1] avrà il seguente contenuto:
// vue.config.js
module.exports = {
// URL del servizio clienti [vuejs] del server di calcolo delle imposte
publicPath: '/client-vuejs-impot/'
}
Dobbiamo inoltre modificare il file di routing [router.js] [2]:
// importazioni
import Vue from 'vue'
import VueRouter from 'vue-router'
// le visualizzazioni
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'
// plugin di routing
Vue.use(VueRouter)
// percorsi dell'applicazione
const routes = [
// autenticazione
{
path: '/', name: 'authentification', component: Authentification
},
// calcolo delle imposte
{
path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
},
// elenco delle simulazioni
{
path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations
},
// fine sessione
{
path: '/fin-session', name: 'finSession', component: Authentification
}
]
// il router
const router = new VueRouter({
// le rotte
routes,
// modalità di visualizzazione delle rotte nel navigatore
mode: 'history',
// URL di base dell'applicazione
base: '/client-vuejs-impot/'
})
// esportazione del router
export default router
- riga 39: si indica al router che i percorsi delle rotte definite alle righe 13-30 sono relativi al percorso definito alla riga 39. Ad esempio, il percorso della riga 20 [/calcul-impot] diventerà [/client-vuejs-impot/calcul-impot];
È quindi possibile testare nuovamente il progetto [vuejs-20] per verificare la modifica dei percorsi dell’applicazione:

Fase 2
Ora compiliamo la versione di produzione del progetto [vuejs-20]:

- in [1-2], configuriamo l’attività [build] [2] nel file [package.json] [1];
- in [3-5], eseguiamo questa attività. È questa che costruirà la versione di produzione del progetto [vuejs-20];
L’esecuzione dell’attività [build] avviene in un terminale di [VSCode]:


- in [3-6], alcuni avvisi ci segnalano che il codice generato è troppo voluminoso e che andrebbe suddiviso in [8]. Si tratta di un aspetto relativo all’ottimizzazione dell’architettura del codice che non tratteremo in questa sede;
- in [7], ci viene indicato che la cartella [dist] contiene la versione di produzione generata:

- in [3], il file [index.html] è il file che verrà utilizzato quando si richiederà URL [https://localhost:80/client-vue-js-impot/];
In questo caso si tratta di un sito statico che può essere distribuito su qualsiasi server. Lo distribuiremo sul server Laragon locale (vedi documento |https://tahe.developpez.com/tutoriels-cours/php7|). La cartella [dist] [2] viene copiata nella cartella [<laragon>/www] [4], dove <laragon> è la cartella di installazione del server Laragon. Rinominiamo questa cartella [client-vuejs-impot] [5] poiché abbiamo configurato la versione di produzione per funzionare su URL [/client-vuejs-impot/].
Fase 3
Aggiungiamo alla cartella [client-vuejs-impot] appena creata il seguente file [.htaccess]:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /client-vuejs-impot/
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /client-vuejs-impot/index.html [L]
</IfModule>

Questo file è un file di configurazione del server web Apache. Se non lo inseriamo e richiediamo direttamente i file URL e [https://localhost/client-vuejs-impot/calcul-impot], senza passare prima per URL e [https://localhost/client-vuejs-impot/], otteniamo un errore 404. Con questo file, otteniamo correttamente la vista [CalculImpot].
Fatto ciò, avviamo il server Laragon, se non è già stato fatto, e richiediamo la pagina URL [https://localhost/client-vuejs-impot/]:

Il lettore è invitato a provare la versione di produzione della nostra applicazione.
Possiamo modificare il server di calcolo delle imposte su un punto: le intestazioni CORS che invia sistematicamente ai propri clienti. Ciò era stato reso necessario per la versione del client eseguita dal dominio [localhost:8080]. Ora che sia il client che il server vengono eseguiti entrambi nel dominio [localhost:80], le intestazioni CORS diventano superflue.
Modifichiamo il file [config.json] della versione 14 del server:

- in [4], specificando che d’ora in poi le richieste CORS saranno rifiutate;
Salviamo questa modifica e richiediamo nuovamente l’URL [https://localhost/client-vuejs-impot/]. Dovrebbe continuare a funzionare.
18.7. Gestione delle richieste manuali
Invece di utilizzare correttamente i link del menu di navigazione, l’utente potrebbe voler digitare manualmente gli URL dell’applicazione nella barra degli indirizzi del browser. Proviamo ad esempio a richiamare l’URL [https://client-vuejs-impot/calcul-impot] senza passare dalla pagina di autenticazione. Un hacker ci proverebbe sicuramente. Si ottiene la seguente schermata:

Si ottiene effettivamente la schermata del calcolo dell’imposta. Ora proviamo a compilare i campi di inserimento e a confermarli:

Si nota quindi che il pulsante [1] [Valider] rimane sempre disattivato anche se i dati inseriti sono corretti. Diamo un’occhiata al codice della vista [FormCalculImpot]:
<b-col cols="5">
<b-button type="submit" variant="primary" :disabled="formInvalide">Valider</b-button>
</b-col>
Alla riga 2 si vede che il suo stato attivo/inattivo dipende dalla proprietà [formInvalide]. Si tratta della seguente proprietà calcolata:
formInvalide() {
return (
// stipendio non valido
!this.salaireValide ||
// o figli non validi
!this.enfantsValide ||
// oppure dati fiscali non disponibili
!this.$métier.taxAdminData
);
},
Alla riga 8 si vede che, affinché il modulo sia valido, è necessario aver ottenuto i dati fiscali. Tuttavia, questi vengono ottenuti durante la convalida della vista [Authentification] che l’utente ha “saltato”. Non potrà quindi convalidare il modulo. Se fosse riuscito a farlo, avrebbe ricevuto un messaggio di errore dal server che gli avrebbe indicato che non era autenticato. Le verifiche devono sempre essere effettuate lato server. Le verifiche lato browser possono sempre essere aggirate. È sufficiente utilizzare un client di tipo [Postman] che invierà richieste grezze al server.
Ora richiediamo il URL [https://localhost/client-vuejs-impot/liste-des-simulations]. Si ottiene la seguente schermata:

Ora proviamo con URL e [https://localhost/client-vuejs-impot/fin-session]. Otteniamo la seguente visualizzazione:

Ora una vista che non esiste, [https://localhost/client-vuejs-impot/abcd]:

La nostra applicazione resiste piuttosto bene ai URL digitati manualmente. Quando questi vengono richiamati, il router dell’applicazione ne viene a conoscenza. È quindi possibile intervenire prima che la vista venga effettivamente visualizzata. Esamineremo questo aspetto nel progetto [vuejs-21].
Un altro aspetto da considerare è il seguente. Immaginiamo che l’utente abbia effettuato alcune simulazioni secondo le regole:

Ora aggiorniamo la pagina con un F5:

Abbiamo fatto qualcosa che non è consigliabile: digitare manualmente URL (fare F5 equivale a questo). Abbiamo quindi perso le nostre simulazioni.
Il progetto successivo, [vuejs-21], si propone di apportare due miglioramenti:
- controllare i URL digitati dall’utente;
- mantenere la memoria dell’applicazione anche se l’utente digita un URL. Sopra, si vede che abbiamo perso l’elenco delle simulazioni;