18. Klient Vue.js serwera obliczającego podatek
18.1. Architecture
Zaimplementujemy aplikację klient-serwer o następującej architekturze:

Serwer obliczający podatek będzie wersją 14 opracowaną w dokumencie |https://tahe.developpez.com/tutoriels-cours/php7|
18.2. Widoki aplikacji
Widoki aplikacji [vuejs-10] są zgodne z widokami wersji 13 dokumentu |https://tahe.developpez.com/tutoriels-cours/php7| serwera obliczającego podatek, gdy jest on używany w trybie HTML. Jednak w niniejszej aplikacji widoki te będą generowane przez klienta JavaScript, a nie przez serwer PHP.
Pierwszy widok to widok uwierzytelniania:

Drugi widok to widok obliczania podatku:

Trzeci widok to widok wyświetlający listę symulacji wykonanych przez użytkownika:

Powyższy ekran pokazuje, że można usunąć symulację nr 1. Otrzymujemy wówczas następujący widok:

Jeśli teraz usuniemy ostatnią symulację, otrzymamy następujący widok:

18.3. Elementy projektu [vuejs-20]
Struktura drzewa projektu [vuejs-20] wygląda następująco:

Elementy projektu są następujące:
- [assets/logo.jpg]: logo projektu;
- [couches]: warstwy [métier] i [dao] aplikacji;
- [plugins]: wtyczki aplikacji;
- [views]: widoki aplikacji;
- [config.js]: konfiguruje aplikację;
- [router.js]: definiuje routing aplikacji;
- [store.js]: magazyn dla [Vuex];
- [main.js]: główny skrypt aplikacji;
18.3.1. Warstwy [métier] i [dao]
18.3.1.1. Warstwa [dao]
Warstwa [dao] jest zaimplementowana przez klasę [Dao] z sekcji |vuejs-10|
18.3.1.2. Warstwa [métier]
Warstwa [métier] jest zaimplementowana przez klasę [Métier] z dokumentu |https://tahe.developpez.com/tutoriels-cours/php7|. Dodano do niej następującą metodę [setTaxAdminData]:
// konstruktor
constructor(taxAdmindata) {
// this.taxAdminData: dane z urzędu skarbowego
this.taxAdminData = taxAdmindata;
}
// ustawianie
setTaxAdminData(taxAdmindata) {
// this.taxAdminData: dane z urzędu skarbowego
this.taxAdminData = taxAdmindata;
}
Metoda [setTaxAdminData] działa tak samo jak konstruktor. Jej obecność umożliwia wykonanie następującej sekwencji:
- utworzenie instancji klasy [Métier] za pomocą instrukcji [métier=new Métier()], gdy chcemy utworzyć instancję klasy, ale nie dysponujemy jeszcze danymi [taxAdminData];
- a następnie uzupełnienie jej właściwości [taxAdminData] za pomocą operacji [métier.setTaxAdminData(taxAdmindata)];
18.3.2. Plik konfiguracyjny [config]
Plik [config.js] ma następującą treść:
// korzystanie z biblioteki [axios]
const axios = require('axios');
// limit czasu dla żądań HTTP
axios.defaults.timeout = 2000;
// baza serwera obliczającego podatek URL
// schemat [https] powoduje problemy w przeglądarce Firefox, ponieważ serwer
// wysyła certyfikat z podpisem własnym. Działa poprawnie w przeglądarkach Chrome i Edge. Nie testowano w przeglądarce Safari.
axios.defaults.baseURL = 'https://localhost/php7/scripts-web/impots/version-14';
// będziemy używać plików cookie
axios.defaults.withCredentials = true;
// eksport konfiguracji
export default {
axios: axios
}
Ta konfiguracja dotyczy biblioteki [axios], z której warstwa [dao] korzysta do wysyłania zapytań HTTP. W wierszu 8 należy zwrócić uwagę, że serwer działa na zabezpieczonym porcie [https].
18.3.3. Wtyczki
Wtyczki [pluginDao, pluginMétier, pluginConfig] mają na celu utworzenie trzech nowych właściwości dla funkcji/klasy [Vue]:
- [$dao]: będzie miało wartością instancję klasy [Dao];
- [$métier]: będzie miało za wartość instancję klasy [Métier];
- [$config]: będzie miało wartością obiekt wyeksportowany przez plik konfiguracyjny [config];
[pluginDao]
export default {
install(Vue, dao) {
// dodaj właściwość [$dao] do klasy Vue
Object.defineProperty(Vue.prototype, '$dao', {
// gdy odwołujemy się do Vue.$dao, zwracamy drugi parametr [dao]
get: () => dao,
})
}
}
[pluginMétier]
export default {
install(Vue, métier) {
// dodaje właściwość [$métier] do klasy Vue
Object.defineProperty(Vue.prototype, '$métier', {
// gdy odwołuje się do Vue.$métier, zwracany jest drugi parametr [métier]
get: () => métier,
})
}
}
[pluginConfig]
export default {
install(Vue, config) {
// dodaje właściwość [$config] do klasy widoku
Object.defineProperty(Vue.prototype, '$config', {
// gdy odwołuje się do Vue.$config, zwracany jest drugi parametr [config]
get: () => config,
})
}
}
18.3.4. Magazyn [Vuex]
Roletę o numerze [Vuex] implementuje następujący plik: [store]:
// wtyczka Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
// magazyn Vuex
const store = new Vuex.Store({
state: {
// tabela symulacji
simulations: [],
// numer ostatniej symulacji
idSimulation: 0
},
mutations: {
// usunięcie wiersza o numerze indeksu
deleteSimulation(state, index) {
// eslint-disable-next-line no-console
console.log("mutation deleteSimulation");
// usuwamy wiersz nr [index]
state.simulations.splice(index, 1);
// eslint-disable-next-line no-console
console.log("store simulations", state.simulations);
},
// dodano symulację
addSimulation(state, simulation) {
// eslint-disable-next-line no-console
console.log("mutation addSimulation");
// numer symulacji
state.idSimulation++;
simulation.id = state.idSimulation;
// dodajemy symulację do tabeli symulacji
state.simulations.push(simulation);
},
// czyszczenie stanu
clear(state) {
state.simulations = [];
state.idSimulation = 1;
}
}
});
// eksport obiektu [store]
export default store;
Komentarze
- wiersze 2–4: wtyczka [Vuex] jest zintegrowana z frameworkiem [Vue];
- wiersze 8–13: umieszczamy w magazynie [Vuex] następujące elementy:
- [simulations]: lista symulacji wykonanych przez użytkownika;
- [idSimulation]: numer ostatniej symulacji przeprowadzonej przez użytkownika;
Przypominamy, że store będzie współdzielony między widokami, a jego zawartość jest reaktywna: gdy zostanie zmodyfikowana, widoki, które z niego korzystają, są automatycznie aktualizowane. W naszej aplikacji tylko element [simulations] musi być reaktywny, a nie element [idSimulation]. Element ten pozostawiono w magazynie dla wygody;
- wiersze 14–40: operacje dozwolone na obiekcie [state] z wierszy 8–13. Przypominamy, że operacje te zawsze otrzymują obiekt [state] z wierszy 8–13 jako pierwszy parametr;
- wiersz 16: modyfikacja [deleteSimulation] pozwala usunąć symulację o numerze [index];
- wiersz 25: operacja [addSimulation] pozwala dodać nową symulację do tabeli symulacji;
- wiersz 35: zmiana [clear] pozwala zresetować obiekt [state] z wierszy 8–13;
18.3.5. Plik routingu [router]
Plik routingu ma następującą postać:
// importy
import Vue from 'vue'
import VueRouter from 'vue-router'
// widoki
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'
// wtyczka routingu
Vue.use(VueRouter)
// trasy aplikacji
const routes = [
// uwierzytelnianie
{
path: '/', name: 'authentification', component: Authentification
},
// obliczanie podatku
{
path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
},
// lista symulacji
{
path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations
},
// zakończenie sesji
{
path: '/fin-session', name: 'finSession', component: Authentification
}
]
// router
const router = new VueRouter({
// trasy
routes,
// sposób wyświetlania tras w przeglądarce
mode: 'history',
})
// eksport routera
export default router
Uwagi
- wiersz 16: po uruchomieniu aplikacji wyświetlany jest widok [Authentification], ponieważ jego URL jest katalogiem głównym [/];
- wiersz 20: widok [CalculImpot] jest wyświetlany, gdy żądany jest widok URL, który jest potomkiem widoku [/calcul-impot];
- wiersz 24: widok [ListeSimulations] jest wyświetlany po wywołaniu widoku URL lub [/liste-des-simualtions];
- wiersz 28: widok [Authentification] jest wyświetlany po wywołaniu widoku URL oraz [/fin-session];
- wiersze 33–38: tworzony jest obiekt [router] z tymi trasami (wiersz 35) oraz trybem zarządzania [history] (wiersz 37) dla URL;
- wiersz 41: ten router jest eksportowany;
18.3.6. Główny skrypt [main.js]
Skrypt [main.js] ma następującą treść:
// importy
import Vue from 'vue'
// ekran główny
import Main from './views/Main.vue'
// wtyczka [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'
// wtyczka [config]
import config from './config';
import pluginConfig from './plugins/pluginConfig'
Vue.use(pluginConfig, config)
// instancja warstwy [dao]
import Dao from './couches/Dao';
const dao = new Dao(config.axios);
// wtyczka [dao]
import pluginDao from './plugins/pluginDao'
Vue.use(pluginDao, dao)
// instancja warstwy [métier]
import Métier from './couches/Métier';
const métier = new Métier();
// wtyczka [métier]
import pluginMétier from './plugins/pluginMétier'
Vue.use(pluginMétier, métier)
// magazyn Vuex
import store from './store'
// uruchomienie aplikacji UI
new Vue({
el: '#aplikacja,
// router
router: router,
// Vuex Store
store: store,
// ekran główny
render: h => h(Main),
})
Należy zwrócić uwagę na następujące kwestie:
- w wierszach 18–21 obiekt wyeksportowany przez skrypt [./config] będzie dostępny w atrybucie [Vue.$config], a zatem we wszystkich widokach aplikacji. W tym przypadku było to zbędne, ponieważ obiekt [config] jest używany wyłącznie przez skrypt [main] (wiersz 25). Niemniej jednak często zdarza się, że konfiguracja jest potrzebna w wielu widokach. Dlatego postanowiliśmy tutaj zachować zasadę udostępniania jej w atrybucie widoku;
- wiersze 24–25: instancjonowanie warstwy [dao]. Klasa [Dao] jest importowana w wierszu 24, a następnie instancjonowana w wierszu 25. Jej konstruktor przyjmuje jako jedyny parametr obiekt [axios], będący właściwością konfiguracyjną;
- wiersze 27–29: warstwa [dao] jest udostępniana w atrybucie [$dao] wszystkich widoków;
- wiersze 31–37: ta sama sekwencja jest powtarzana dla warstwy [métier]. Konstruktor klasy [Métier] ma jako parametr [taxAdminData], który reprezentuje dane administracji podatkowej. Nie dysponujemy jeszcze tymi danymi. Obiekt [métier] z wiersza 33 będzie zatem musiał zostać uzupełniony w późniejszym terminie;
- wiersz 40: importujemy widok [Vuex];
- wiersze 43–51: tworzymy instancję głównego widoku [Main] (wiersze 5 i 50), przekazując mu dwa parametry:
- wiersz 46: router [router] zdefiniowany w wierszu 16;
- wiersz 48: roletę [Vuex] [store] zdefiniowaną w wierszu 40;
- w obu przypadkach nazwa właściwości znajduje się po lewej stronie, a jej wartość po prawej. Nazwy właściwości [router, store] są ustalane przez frameworki [vue-router] i [vuex]. Przypisane do nich wartości mogą być dowolne;
18.4. Widoki aplikacji
18.4.1. Widok główny [Main]
Kod widoku głównego [Main] wygląda następująco:
<!-- definicja widoku HTML -->
<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>
<!-- błąd żądania HTTP -->
<b-alert
show
variant="danger"
v-if="showError"
>L'erreur suivante s'est produite : {{error.message}}</b-alert>
<!-- aktualny widok -->
<router-view v-if="showView" @loading="mShowLoading" @error="mShowError" />
<!-- ładowanie -->
<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 {
// nazwa
name: "app",
// stan wewnętrzny
data() {
return {
// kontroluje alert oczekujący
showLoading: false,
// kontroluje alert o błędzie
showError: false,
// kontroluje wyświetlanie bieżącego widoku trasowania
showView: true,
// komunikat o błędzie
error: ""
};
},
// obsługa zdarzeń
methods: {
// błąd żądania asynchronicznego
mShowError(error) {
// eslint-disable-next-line
console.log("Main evt error");
// wyświetla się komunikat o błędzie
this.error = error;
this.showError = true;
// ukrywanie widoku przekierowanego
this.showView = false;
// ukrywanie komunikatu o oczekiwaniu
this.showLoading = false;
},
// wyświetlanie lub nie ikony oczekiwania
mShowLoading(value) {
// eslint-disable-next-line
console.log("Main evt showLoading");
// wyświetlanie lub nie wyświetlanie alertu o oczekiwaniu
this.showLoading = value;
}
}
};
</script>
Komentarze
- Widok [Main] zapewnia formatowanie widoku przekierowanego i wyświetlanego w wierszu 23:

- wiersze 5–15 wyświetlają obszar 1;
- w wierszu 23 wyświetlany jest widok przekierowany [2];
- wiersze 16–19: alert wyświetlany wyłącznie w przypadku błędu komunikacji z serwerem obliczającym podatek;
- wiersze 25–28: komunikat o oczekiwaniu wyświetlany przy każdym zapytaniu HTTP kierowanym do serwera;
- wszystkie widoki będą wyświetlane w tym układzie, ponieważ każdy widok przekierowany jest wyświetlany w wierszach 20–24. Widok [Main] służy do wyodrębnienia elementów, które mogą być wspólne dla różnych widoków;
- wiersz 23: każdy przekierowany widok może wygenerować trzy zdarzenia:
- [loading]: wysłano zapytanie HTTP. Należy wyświetlić komunikat o oczekiwaniu na odpowiedź;
- [error]: żądanie HTTP zakończyło się błędem. Należy wyświetlić komunikat o błędzie i ukryć przekierowany widok;
- wiersze 38–49: stan widoku:
- wiersz 41: [showLoading] kontroluje wyświetlanie komunikatu o oczekiwaniu na zakończenie żądania HTTP (wiersz 25);
- wiersz 43: [showError] kontroluje wyświetlanie komunikatu o błędzie zapytania HTTP (wiersze 17–21);
- wiersz 45: [showView] kontroluje wyświetlanie widoku przekierowanego (wiersz 23);
- wiersze 53–63: metoda [mShowError] obsługuje zdarzenie [error] wygenerowane przez widok przekierowany (wiersz 23);
- wiersze 65–70: metoda [mShowLoading] obsługuje zdarzenie [loading] wygenerowane przez widok przekierowany (wiersz 23);
- wiersz 23: należy zwrócić uwagę na zdarzenia [error] i [loading]. Są one przechwytywane tylko wtedy, gdy wyświetlany jest widok przekierowany [showView=true]. Dlatego widok przekierowany jest początkowo wyświetlany (wiersz 45). Jest on ukrywany tylko w przypadku błędu (wiersz 60). Aby uniknąć tego problemu, można było użyć dyrektywy [v-show] zamiast [v-if]. Różnica między tymi dwiema dyrektywami jest następująca:
- [v-if=’false’] ukrywa kontrolowany blok, usuwając go z globalnego kodu HTML. Zdarzenia z widoku przekierowanego nie mogą wówczas być przechwytywane;
- [v-show=’false’] ukrywa kontrolowany blok, modyfikując jego CSS, ale kod bloku pozostaje w globalnym HTML i dzięki temu może przechwytywać zdarzenia z przekierowanego widoku;
18.4.2. Widok układu strony [Layout]
Kod widoku [Layout] wygląda następująco:
<!-- definicja HTML układu widoku przekierowanego -->
<template>
<!-- wiersz -->
<div>
<b-row>
<!-- obszar z trzema kolumnami po lewej stronie -->
<b-col cols="3" v-if="left">
<slot name="left" />
</b-col>
<!-- obszar z dziewięcioma kolumnami po prawej stronie -->
<b-col cols="9" v-if="right">
<slot name="right" />
</b-col>
</b-row>
</div>
</template>
<script>
export default {
// parametry widoku
props: {
// kontroluje lewą kolumnę
left: {
type: Boolean
},
// sterowanie prawą kolumną
right: {
type: Boolean
}
}
};
</script>
Komentarze
- Widok [Layout] pozwala podzielić przekierowany widok na dwie strefy:
- strefa z 3 kolumnami Bootstrap po lewej stronie (wiersze 7–9). W tej strefie będzie wyświetlane menu nawigacyjne, o ile takie istnieje;
- obszar z 9 kolumnami po prawej stronie (wiersze 11–13). W tym obszarze będą wyświetlane informacje dostarczane przez widok przekierowany;
18.4.3. Widok [Authentification]
Widok uwierzytelniania wygląda następująco:

Ten widok uzyskano na podstawie pliku [Layout] poprzez usunięcie lewej kolumny, tak aby wyświetlać wyłącznie prawą kolumnę.
Jego kod wygląda następująco:
<!-- definicja widoku HTML -->
<template>
<Layout :left="false" :right="true">
<template slot="right">
<!-- formularz HTML – wartości są wysyłane za pomocą akcji [authentifier-utilisateur] -->
<b-form @submit.prevent="login">
<!-- tytuł -->
<b-alert show variant="primary">
<h4>Bienvenue. Veuillez vous authentifier pour vous connecter</h4>
</b-alert>
<!-- pierwszy wiersz -->
<b-form-group label="Nom d'utilisateur" label-for="user" label-cols="3">
<!-- pole wprowadzania danych użytkownika -->
<b-col cols="6">
<b-form-input type="text" id="user" placeholder="Nom d'utilisateur" v-model="user" />
</b-col>
</b-form-group>
<!-- drugi wiersz -->
<b-form-group label="Mot de passe" label-for="password" label-cols="3">
<!-- pole wprowadzania hasła -->
<b-col cols="6">
<b-input type="password" id="password" placeholder="Mot de passe" v-model="password" />
</b-col>
</b-form-group>
<!-- trzeci wiersz -->
<b-alert
show
variant="danger"
v-if="showError"
class="mt-3"
>L'erreur suivante s'est produite : {{message}}</b-alert>
<!-- przycisk typu [submit] w trzecim wierszu -->
<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>
<!-- dynamika widoku -->
<script>
import Layout from "./Layout";
export default {
// stan komponentu
data() {
return {
// użytkownik
user: "",
// jego hasło
password: "",
// kontroluje wyświetlanie komunikatu o błędzie
showError: false,
// komunikat o błędzie
message: "",
// sesja rozpoczęta
sessionStarted: false
};
},
// używane komponenty
components: {
Layout
},
// właściwości obliczone
computed: {
// prawidłowe dane wprowadzone
valid() {
return this.user && this.password && this.sessionStarted;
}
},
// obsługa zdarzeń
methods: {
// ----------- uwierzytelnianie
async login() {
try {
// początek oczekiwania
this.$emit("loading", true);
// blokujące uwierzytelnianie na serwerze
const response = await this.$dao.authentifierUtilisateur(
this.user,
this.password
);
// koniec ładowania
this.$emit("loading", false);
// analiza odpowiedzi
if (response.état != 200) {
// wyświetlanie błędu
this.message = response.réponse;
this.showError = true;
return;
}
// brak błędu
this.showError = false;
// --------- teraz wysyłamy zapytanie do urzędu skarbowego
// początek oczekiwania
this.$emit("loading", true);
// żądanie blokujące wysłane do serwera
const response2 = await this.$dao.getAdminData();
// koniec ładowania
this.$emit("loading", false);
// analiza odpowiedzi
if (response2.état != 1000) {
// wyświetlanie błędu
this.message = response2.réponse;
this.showError = true;
return;
}
// brak błędu
this.showError = false;
// zapisano otrzymane dane w warstwie [métier]
this.$métier.setTaxAdminData(response2.réponse);
// przechodzi się do widoku obliczeń podatku
this.$router.push({ name: "calculImpot" });
} catch (error) {
// błąd jest przekazywany do głównego komponentu
this.$emit("error", error);
}
}
},
// cykl życia: komponent został właśnie utworzony
created() {
// eslint-disable-next-line
console.log("authentification", "created");
// rozpoczynamy sesję jSON z serwerem
// początek oczekiwania
this.$emit("loading", true);
// inicjowanie sesji z serwerem – żądanie asynchroniczne
// wykorzystuje się obietnicę zwróconą przez metody warstwy [dao]
this.$dao
// inicjowanie sesji jSON
.initSession()
// otrzymano odpowiedź
.then(response => {
// koniec oczekiwania
this.$emit("loading", false);
// analiza odpowiedzi
if (response.état != 700) {
// wyświetlono błąd
this.message = response.réponse;
this.showError = true;
return;
}
// rozpoczęto sesję
this.sessionStarted = true;
})
// w przypadku błędu
.catch(error => {
// błąd jest przekazywany do widoku [Main]
this.$emit("error", error);
});
}
};
</script>
Uwagi
- wiersz 3: widok [Authentification] wykorzystuje wyłącznie prawą kolumnę z pliku [Layout] (wiersze 3 i 4);
- wiersze 6–38: formularz Bootstrap, który generuje obszar 1 na powyższym zrzucie ekranu;
- wiersz 6: zdarzenie [@submit] ma miejsce, gdy użytkownik kliknie przycisk typu [submit] z wiersza 35. Modyfikator [prevent] powoduje, że strona nie jest odświeżana podczas zdarzenia [submit]. Można było również zapisać:
- tag <b-form> bez obsługi zdarzenia [submit];
- tag <b-button> z obsługą zdarzenia [@click=’login’] i bez atrybutu [type=’submit’];
To również działa. Zaletą wybranego rozwiązania jest to, że przesłanie danych następuje nie tylko po kliknięciu przycisku [Valider], ale również po zatwierdzeniu (klawisz [Entrée]) w polach wprowadzania danych. Wybór rozwiązania [<b-form @submit.prevent="login">] wynika zatem z dbałości o wygodę użytkownika;
- wiersze 33–37: komunikat ostrzegawczy wyświetlany w przypadku odrzucenia przez serwer danych uwierzytelniających wprowadzonych przez użytkownika:

- wiersz 35: przycisk [Valider] nie zawsze jest aktywny. Jego stan zależy od atrybutu obliczeniowego [valid] z wierszy 71–73. Atrybut [valid] ma wartość prawdziwą, jeśli:
- w polach [user, password] formularza znajduje się jakaś wartość;
- sesja jSON została uruchomiona. Początkowo sesja ta nie została uruchomiona (wiersz 59), dlatego przycisk [Valider] jest nieaktywny.
- wiersze 49–60: stan widoku;
- [user] reprezentuje dane wprowadzone przez użytkownika w polu [user] (wiersze 12–17) formularza. Dyrektywa [v-model] w wierszu 15 ustanawia dwukierunkowe powiązanie między danymi wprowadzonymi przez użytkownika a atrybutem [user] widoku;
- [password] reprezentuje dane wprowadzone przez użytkownika w polu [password] (wiersze 19–24) formularza. Dyrektywa [v-model] w wierszu 22 ustanawia dwukierunkowe powiązanie między danymi wprowadzonymi przez użytkownika a atrybutem [password] widoku;
- [showError] kontroluje (wiersz 29) wyświetlanie ostrzeżenia w wierszach 26–31;
- [message] to komunikat o błędzie (wiersz 31), który ma zostać wyświetlony w ostrzeżeniu w wierszach 26–31;
- [sessionStarted] wskazuje, czy sesja jSON z serwerem została uruchomiona, czy nie. Początkowo atrybut ten ma wartość [false] (wiersz 59). Sesja jSON z serwerem jest inicjowana w zdarzeniu [created] cyklu życia widoku, wiersze 126–156. Jeśli serwer odpowie pozytywnie, wówczas atrybut [sessionStarted] zostaje zmieniony na [true] (wiersz 149);
- wiersze 126–156: funkcja [created] jest wykonywana po utworzeniu widoku [Authentification] (niekoniecznie jeszcze wyświetlonego). W tle inicjowana jest wówczas sesja jSON z serwerem. Wiadomo, że jest to pierwsza czynność, jaką należy wykonać z serwerem obliczającym podatek. W tym celu wykorzystuje się warstwę [dao] aplikacji (wiersz 134). Wszystkie metody tej warstwy są asynchroniczne. Wykorzystujemy tutaj obietnicę (Promise) zwróconą przez metodę [$dao.initSession], która inicjuje sesję jSON z serwerem.
- wiersze 138–150: kod wykonywany, gdy serwer zwrócił odpowiedź bez błędów;
- wiersz 142: sprawdzana jest właściwość [état] odpowiedzi. W przypadku pomyślnej operacji powinna ona przyjmować wartość [700]. W przeciwnym razie wystąpił błąd, którego przyczyna jest wskazana we właściwości [response.réponse] (wiersz 144). Wyświetlany jest wówczas komunikat o błędzie widoku (wiersz 145);
- wiersz 149: odnotowuje się rozpoczęcie sesji o numerze jSON;
- wiersze 152–155: kod wykonywany w przypadku wystąpienia błędu. Błąd ten jest przekazywany do nadrzędnego widoku [Main], który
- wyświetli błąd;
- ukryje komunikat o oczekiwaniu;
- ukryje przekierowany widok, widok [Autentification];
- wiersze 79–124: metoda [login] obsługuje kliknięcie przycisku [Valider];
- wiersz 79: metoda została opatrzona prefiksem w postaci słowa kluczowego [async], aby umożliwić użycie słowa kluczowego [await] w wierszach 84 i 103;
- wiersze 84–87: blokujące wywołanie metody [$dao.authentifierUtilisateur(user, password)]. Można było użyć obietnicy [Promise], tak jak zrobiono to w funkcji [created]. Chcieliśmy urozmaicić style. Nie ma ryzyka zablokowania użytkownika, ponieważ do wszystkich żądań HTTP dodaliśmy [timeout] o czasie trwania 2 sekund. Nie będzie musiał długo czekać. Ponadto nie może nic zrobić, dopóki serwer nie zwróci odpowiedzi, ponieważ wówczas przycisk [Valider] pozostaje nieaktywny;
- wiersz 91: serwer obliczający podatek wysyła odpowiedzi o numerze jSON, z których wszystkie mają strukturę [{‘action’:action, ‘état’:val, ‘réponse’:réponse}]. Uwierzytelnienie zakończyło się powodzeniem, jeśli [état==200]. W przeciwnym razie wyświetlany jest komunikat o błędzie, wiersze 93–94;
- wiersz 98: ukrywa się ewentualny komunikat o błędzie z poprzedniej operacji;
- wiersze 99–116: teraz wysyłamy do serwera żądanie o dane z urzędu skarbowego, które pozwalają na obliczenie podatku. W [this.$métier] mamy instancję klasy [Métier], która na razie nie może nic zrobić, ponieważ nie posiada tych danych;
- wiersz 103: dane z urzędu skarbowego są pobierane z serwera za pomocą operacji blokującej;
- wiersze 107–112: analizowana jest odpowiedź serwera. Musi ona mieć wartość stanu równą 1000, w przeciwnym razie oznacza to, że wystąpił błąd. W tym ostatnim przypadku wyświetlany jest komunikat o błędzie (wiersze 109–110);
- wiersze 113–118: w przypadku pomyślnego zakończenia operacji:
- ukrywa się komunikat o błędzie (wiersz 114);
- przesyła się dane z urzędu skarbowego do warstwy [métier] (wiersz 116);
- wyświetla się widok [CalculImpot], wiersz 118. Przypomnijmy, że [this.$router] oznacza router aplikacji. Metoda [push] pozwala ustalić następny widok, do którego zostanie przekierowany. W tym przypadku określamy go za pomocą atrybutu [name]. Można było również określić go za pomocą atrybutu [path]. Informacje te znajdują się w pliku routingu:
// obliczenie podatku
{
path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
},
- wiersze 119–122: widok [catch] uruchamia się, gdy jedno z dwóch zapytań HTTP zakończyło się niepowodzeniem (serwer nieobecny, przekroczono limit czasu itp.). Błąd jest wówczas zgłaszany do widoku nadrzędnego [Main], który go wyświetli, ukryje komunikat o oczekiwaniu oraz widok [Authentification];
18.4.4. Widok [CalculImpot]
Widok [CalculImpot] wygląda następująco:

- [1]: menu nawigacyjne zajmuje lewą kolumnę widoku przekierowanego;
- [2]: formularz obliczania podatku zajmuje prawą kolumnę widoku przekierowanego;
Kod widoku [CalculImpot] jest następujący:
<!-- definicja widoku HTML -->
<template>
<div>
<Layout :left="true" :right="true">
<!-- formularz obliczania podatku po prawej stronie -->
<FormCalculImpot slot="right" @resultatObtenu="handleResultatObtenu" />
<!-- menu nawigacyjne po lewej stronie -->
<Menu slot="left" :options="options" />
</Layout>
<!-- obszar wyświetlania wyników obliczeń podatku pod formularzem -->
<b-row v-if="résultatObtenu" class="mt-3">
<!-- pusty obszar z trzema kolumnami -->
<b-col cols="3" />
<!-- obszar z dziewięcioma kolumnami -->
<b-col cols="9">
<b-alert show variant="success">
<span v-html="résultat"></span>
</b-alert>
</b-col>
</b-row>
</div>
</template>
<script>
// importy
import FormCalculImpot from "./FormCalculImpot";
import Menu from "./Menu";
import Layout from "./Layout";
export default {
// raport wewnętrzny
data() {
return {
// opcje menu
options: [
{
text: "Liste des simulations",
path: "/liste-des-simulations"
},
{
text: "Fin de session",
path: "/fin-session"
}
],
// wynik obliczenia podatku
résultat: "",
résultatObtenu: false
};
},
// użyte komponenty
components: {
Layout,
FormCalculImpot,
Menu
},
// metody zarządzania zdarzeniami
methods: {
// wynik obliczenia podatku
handleResultatObtenu(résultat) {
// tworzymy wynik w łańcuchu 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;
// wyświetlanie wyniku
this.résultatObtenu = true;
// ---- aktualizacja sklepu [Vuex]
// symulacja dodania
this.$store.commit("addSimulation", résultat);
}
}
};
</script>
Uwagi
- wiersz 4: obie kolumny widoku [Layout] są tutaj obecne;
- wiersz 6: formularz obliczania podatku zajmuje prawą kolumnę. Generuje on zdarzenie [resultatObtenu] po uzyskaniu wyniku obliczenia podatku. Należy zauważyć, że nazwy zdarzeń oraz nazwy metod, które je obsługują, nie mogą zawierać znaków akcentowanych;
- wiersz 8: menu nawigacyjne zajmuje lewą kolumnę;
- wiersze 11–20: wynik obliczeń podatku jest wyświetlany pod formularzem:

- wiersz 11: wynik jest wyświetlany tylko wtedy, gdy atrybut [résultatObtenu] (wiersz 47) ma wartość [true];
- wiersze 34–48: stan widoku:
- [options]: lista opcji menu nawigacyjnego. Tabela ta jest przekazywana jako parametr do komponentu [Menu], wiersz 8;
- [résultat]: wynik obliczenia podatku. Wynik ten jest ciągiem znaków HTML. Dlatego w wierszu 17 użyto dyrektywy [v-html], aby go wyświetlić;
- [résultatObtenu]: wartość logiczna kontrolująca wyświetlanie wyniku, wiersz 11;
- wiersze 59–81: metoda [handleResultatObtenu] wyświetla wynik obliczenia podatku przesłany jej przez widok potomny [FormCalculImpot], wiersz 6. Wynikiem tym jest obiekt o właściwościach [impot, décôte, réduction, surcôte, taux, marié, enfants, salaire];
- wiersze 61–75: obiekt [impot, décôte, réduction, surcôte, taux] jest zapisywany w tekście HTML, który jest wyświetlany w wierszu 17 szablonu;
- wiersz 77: wyświetla się ten wynik;
- wiersz 80: wywołuje się mutację [addSimulation] z magazynu Vuex, która doda [résultat] do symulacji już znajdujących się w magazynie;
18.4.5. Menu nawigacyjne [Menu]
Menu nawigacyjne wyświetla się w lewej kolumnie przekierowanych widoków:

Kod widoku [Menu] jest następujący:
<!-- definicja widoku HTML -->
<template>
<!-- pionowe menu Bootstrap -->
<b-nav vertical>
<!-- opcje 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 {
// parametry widoku
props: {
options: {
type: Array
}
}
};
</script>
Uwagi
- opcje menu są określane przez parametr [options] (wiersze 7, 20–22);
- każdy element tablicy [options] posiada właściwość [text] (wiersz 12), która stanowi tekst linku, oraz właściwość [path] (wiersz 9), która będzie ścieżką do widoku docelowego linku;
18.4.6. Widok [FormCalculImpot]
Ten widok udostępnia formularz do obliczania podatku:

Jego kod wygląda następująco:
<!-- definicja widoku HTML -->
<template>
<!-- formularz HTML -->
<b-form @submit.prevent="calculerImpot" class="mb-3">
<!-- komunikat w 12 kolumnach na niebieskim tle -->
<b-alert show variant="primary">
<h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
</b-alert>
<!-- elementy formularza -->
<!-- pierwszy wiersz -->
<b-form-group label="Etes-vous marié(e) ou pacsé(e) ?" label-cols="4">
<!-- przyciski opcji w 5 kolumnach-->
<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>
<!-- drugi wiersz -->
<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"
/>
<!-- ewentualny komunikat o błędzie -->
<b-form-invalid-feedback :state="enfantsValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
</b-form-group>
<!-- trzeci wiersz -->
<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"
/>
<!-- ewentualny komunikat o błędzie -->
<b-form-invalid-feedback :state="salaireValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
</b-form-group>
<!-- czwarty wiersz, przycisk [submit] w 5 kolumnach -->
<b-col cols="5">
<b-button type="submit" variant="primary" :disabled="formInvalide">Valider</b-button>
</b-col>
</b-form>
</template>
<!-- skrypt -->
<script>
export default {
// stan wewnętrzny
data() {
return {
// stan cywilny
marié: "non",
// liczba dzieci
enfants: "",
// roczne wynagrodzenie
salaire: ""
};
},
// obliczony stan wewnętrzny
computed: {
// zatwierdzenie formularza
formInvalide() {
return (
// wynagrodzenie nieprawidłowe
!this.salaireValide ||
// lub nieprawidłowa liczba dzieci
!this.enfantsValide ||
// lub brak danych podatkowych
!this.$métier.taxAdminData
);
},
// weryfikacja wynagrodzenia
salaireValide() {
// musi być liczbą >=0
return Boolean(this.salaire.match(/^\s*\d+\s*$/));
},
// weryfikacja danych dotyczących dzieci
enfantsValide() {
// musi być liczbą >=0
return Boolean(this.enfants.match(/^\s*\d+\s*$/));
}
},
// menedżer zdarzeń
methods: {
calculerImpot() {
// podatek oblicza się przy użyciu warstwy [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);
// uzupełnia się wynik
résultat.marié = this.marié;
résultat.enfants = this.enfants;
résultat.salaire = this.salaire;
// wysyła się zdarzenie [resultatObtenu]
this.$emit("resultatObtenu", résultat);
}
}
};
</script>
Uwagi
- wiersze 4–51: formularz Bootstrap;
- wiersze 11–17: grupa przycisków opcji wraz z ich etykietami;
- wiersze 14–15: tag <b-form-radio> zapewnia wyświetlenie przycisku opcji:
- wiersz 14: dyrektywa [v-model] gwarantuje, że po kliknięciu przycisku atrybut [marié] w wierszu 61 otrzyma wartość [oui] (atrybut [value="oui"]);
- wiersz 15: dyrektywa [v-model] gwarantuje, że po kliknięciu przycisku atrybut [marié] w wierszu 61 przyjmie wartość [non] (atrybut [value="non"]);
- wiersze 19–29: część dotycząca wprowadzania liczby dzieci:
- wiersz 24: wprowadzona liczba dzieci jest powiązana z atrybutem [enfants] w wierszu 63;
- wiersz 25: poprawność wprowadzonych danych jest sprawdzana przez atrybut obliczeniowy [enfantsValide] z wierszy 87–89;
- wiersz 28: zapewnia wyświetlenie komunikatu o błędzie, jeśli wprowadzone dane są nieprawidłowe;
- wiersze 31–45: część dotycząca wprowadzenia wynagrodzenia rocznego:
- wiersz 35: wyświetla komunikat pomocy tuż pod polem wprowadzania danych;
- wiersz 41: wprowadzone dane dotyczące wynagrodzenia są powiązane z atrybutem [salaire] z wiersza 65;
- wiersz 42: poprawność wprowadzonych danych jest sprawdzana przez atrybut obliczeniowy [salaireValide] z wierszy 82–85;
- wiersz 45: zapewnia wyświetlenie komunikatu o błędzie, jeśli wprowadzone dane są nieprawidłowe;
- wiersze 48–50: przycisk typu [submit]. Po kliknięciu tego przycisku lub zatwierdzeniu wpisu za pomocą przycisku [Entrée] wykonywana jest metoda [calculerImpot] (wiersz 94);
- wiersz 49: stan przycisku (aktywny/nieaktywny) jest kontrolowany przez atrybut obliczeniowy [formInvalide] z wierszy 71–80;
- wiersze 71–80: formularz jest poprawny, jeśli:
- liczba dzieci jest prawidłowa;
- wynagrodzenie jest prawidłowe;
- Aplikacja pobrała z serwera dane z urzędu skarbowego umożliwiające obliczenie podatku. Przypominamy, że dane te są zapisane we właściwości [$métier.taxAdminData]. Widok [FormCalculImpot] może zostać wyświetlony, zanim dane te zostaną pobrane, ponieważ są one żądane asynchronicznie w momencie wyświetlania widoku. W tym miejscu zapewniamy, że użytkownik nie może kliknąć przycisku [Valider], dopóki dane nie zostaną pobrane;
- wiersze 94–109: metoda obliczania podatku:
- wiersze 96–100: obliczenia te wykonuje warstwa [métier]. Jest to obliczenie synchroniczne. Po uzyskaniu danych [taxAdminData] klient [Vue] nie musi już komunikować się z serwerem. Wszystko odbywa się lokalnie. Otrzymujemy obiekt [résultat] z właściwościami [impôt, décôte, surcôte, réduction, taux];
- wiersze 104–106: do wyniku dodaje się właściwości [marié, enfants, salaire];
- wiersz 108: wynik jest przekazywany do widoku nadrzędnego [CalculImpot] za pośrednictwem zdarzenia [resultatObtenu]. Widok ten odpowiada za wyświetlenie wyniku;
18.4.7. Widok [ListeSimulations]
Widok [ListeSimulations] wyświetla listę symulacji przeprowadzonych przez użytkownika:

Kod widoku jest następujący:
<!-- definicja widoku HTML -->
<template>
<div>
<!-- układ strony -->
<Layout :left="true" :right="true">
<!-- symulacje w prawej kolumnie -->
<template slot="right">
<template v-if="simulations.length==0">
<!-- brak symulacji -->
<b-alert show variant="primary">
<h4>Votre liste de simulations est vide</h4>
</b-alert>
</template>
<template v-if="simulations.length!=0">
<!-- są symulacje -->
<b-alert show variant="primary">
<h4>Liste de vos simulations</h4>
</b-alert>
<!-- tabela symulacji -->
<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 nawigacyjne w lewej kolumnie -->
<Menu slot="left" :options="options" />
</Layout>
</div>
</template>
<script>
// importy
import Layout from "./Layout";
import Menu from "./Menu";
export default {
// komponenty
components: {
Layout,
Menu
},
// stan wewnętrzny
data() {
return {
// opcje menu nawigacyjnego
options: [
{
text: "Calcul de l'impôt",
path: "/calcul-impot"
},
{
text: "Fin de session",
path: "/fin-session"
}
],
// parametry tabeli 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" }
]
};
},
// obliczony stan wewnętrzny
computed: {
// lista symulacji pobranych z magazynu Vuex
simulations() {
return this.$store.state.simulations;
}
},
// metody
methods: {
supprimerSimulation(index) {
// eslint-disable-next-line
console.log("supprimerSimulation", index);
// usunięcie symulacji nr [index]
this.$store.commit("deleteSimulation", index);
}
}
};
</script>
Uwagi
- wiersz 5: widok zajmuje dwie kolumny w układzie [Layout] widoków przekierowanych;
- wiersze 7–26: symulacje znajdują się w prawej kolumnie;
- wiersz 28: menu nawigacyjne znajduje się w lewej kolumnie;
- wiersze 8, 14, 20, 75: symulacje pochodzą z magazynu [Vuex] [$this.store];
- wiersze 8–13: wyświetlany komunikat ostrzegawczy, gdy lista symulacji jest pusta;
- wiersze 14–25: tabela HTML wyświetlana, gdy lista symulacji nie jest pusta;
- wiersze 20–24: tabela HTML jest generowana przez tag <b-table>;
- wiersz 20: tabela symulacji jest dostarczana przez atrybut obliczeniowy [simulations] z wierszy 74–76;
- wiersz 20: konfiguracja tabeli HTML jest ustalana przez atrybut obliczeniowy [fields] z wierszy 58–69. Wiersz 67: kolumna klucza [action] jest ostatnią kolumną tabeli HTML;
- wiersze 21–23: szablon ostatniej kolumny tabeli HTML;
- wiersz 22: umieszczamy tam przycisk typu link. Po kliknięciu na niego wywoływana jest metoda [supprimerSimulation(data.index)], gdzie [data] oznacza bieżący wiersz (wiersz 21). [data.index] oznacza numer tego wiersza na liście wyświetlanych wierszy;
- wiersz 28: generowanie menu nawigacyjnego. Jego opcje są określone przez atrybut [options] w wierszach 47–56;
- wiersze 80–85: metoda reagująca na kliknięcie linku [Supprimer] na stronie HTML;
- wiersz 84: wywoływana jest mutacja [deleteSimulation] z magazynu [Vuex] (patrz akapit |vuejs-15|);
18.5. Realizacja projektu

Należy również uruchomić serwer [Laragon] (patrz dokument |https://tahe.developpez.com/tutoriels-cours/php7|), aby serwer obliczający podatek był dostępny online.
18.6. Wdrożenie aplikacji na serwerze lokalnym
Obecnie nasz klient [Vue] jest wdrożony na serwerze testowym o numerze URL [http://localhost:8080]. Zamierzamy wdrożyć ją na serwerze [Laragon] w środowisku URL [http://localhost:80]. Aby to osiągnąć, należy wykonać kilka kroków.
Krok 1
Na początek zadbamy o to, aby klient [Vue] został wdrożony na serwerze testowym o nazwie URL [http://localhost:8080/client-vuejs-impot/].
Tworzymy plik o nazwie [vue.config.js] w katalogu głównym naszego obecnego projektu [VSCode]:

Plik [vue.config.js] [1] będzie miał następującą zawartość:
// vue.config.js
module.exports = {
// URL – usługa klienta [vuejs] serwera obliczającego podatek
publicPath: '/client-vuejs-impot/'
}
Musimy również zmodyfikować plik routingu [router.js] [2]:
// importy
import Vue from 'vue'
import VueRouter from 'vue-router'
// widoki
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'
// wtyczka routingu
Vue.use(VueRouter)
// trasy aplikacji
const routes = [
// uwierzytelnianie
{
path: '/', name: 'authentification', component: Authentification
},
// obliczanie podatku
{
path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
},
// lista symulacji
{
path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations
},
// zakończenie sesji
{
path: '/fin-session', name: 'finSession', component: Authentification
}
]
// router
const router = new VueRouter({
// trasy
routes,
// sposób wyświetlania tras w przeglądarce
mode: 'history',
// podstawowa aplikacja URL
base: '/client-vuejs-impot/'
})
// eksport routera
export default router
- wiersz 39: informujemy router, że ścieżki tras zdefiniowanych w wierszach 13–30 odnoszą się do ścieżki zdefiniowanej w wierszu 39. Na przykład ścieżka z wiersza 20 [/calcul-impot] zmieni się na [/client-vuejs-impot/calcul-impot];
Można następnie ponownie przetestować projekt [vuejs-20], aby sprawdzić zmianę ścieżek aplikacji:

Krok 2
Teraz kompilujemy wersję produkcyjną projektu [vuejs-20]:

- w [1-2] konfigurujemy zadanie [build] [2] w pliku [package.json] [1];
- w [3-5] uruchamiamy to zadanie. To właśnie ono utworzy wersję produkcyjną projektu [vuejs-20];
Wykonanie zadania [build] odbywa się w terminalu [VSCode]:


- w [3-6] pojawiają się ostrzeżenia, że wygenerowany kod jest zbyt duży i należy go podzielić na [8]. Jest to kwestia optymalizacji architektury kodu, której nie będziemy tutaj omawiać;
- w [7] informuje się nas, że folder [dist] zawiera wygenerowaną wersję produkcyjną:

- w pliku [3] plik [index.html] jest plikiem, który zostanie użyty po wywołaniu URL [https://localhost:80/client-vue-js-impot/];
Mamy tutaj statyczną stronę internetową, którą można wdrożyć na dowolnym serwerze. Wdrożymy ją na lokalnym serwerze Laragon (patrz dokument |https://tahe.developpez.com/tutoriels-cours/php7|). Folder [dist] [2] jest kopiowany do folderu [<laragon>/www] [4], gdzie <laragon> to folder instalacyjny serwera Laragon. Zmieniamy nazwę tego folderu na [client-vuejs-impot] [5], ponieważ skonfigurowaliśmy wersję produkcyjną do działania w folderze URL [/client-vuejs-impot/].
Krok 3
Do właśnie utworzonego folderu [client-vuejs-impot] dodajemy następujący plik [.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>

Ten plik jest plikiem konfiguracyjnym serwera WWW Apache. Jeśli nie umieścimy go i spróbujemy bezpośrednio wywołać adres URL [https://localhost/client-vuejs-impot/calcul-impot], bez przejścia najpierw przez URL [https://localhost/client-vuejs-impot/], otrzymamy błąd 404. Dzięki temu plikowi otrzymujemy prawidłowy widok [CalculImpot].
Po wykonaniu tych czynności uruchamiamy serwer Laragon, jeśli jeszcze tego nie zrobiliśmy, i wysyłamy żądanie do URL [https://localhost/client-vuejs-impot/]:

Zachęcamy czytelnika do przetestowania produkcyjnej wersji naszej aplikacji.
Możemy zmodyfikować serwer obliczający podatek w jednym aspekcie: nagłówki, które systematycznie wysyła on do swoich klientów. Było to konieczne w przypadku wersji klienta uruchamianej z domeny [localhost:8080]. Teraz, gdy zarówno klient, jak i serwer działają w domenie [localhost:80], nagłówki CORS stają się zbędne.
Modyfikujemy plik [config.json] w wersji 14 serwera:

- na [4], wskazując, że od tej pory żądania CORS są odrzucane;
Zapiszmy tę zmianę i ponownie wyślijmy żądania URL oraz [https://localhost/client-vuejs-impot/]. Powinno nadal działać.
18.7. Zarządzanie ręcznymi żądaniami URL
Zamiast korzystać z linków w menu nawigacyjnym, użytkownik może chcieć ręcznie wpisać adresy aplikacji URL w pasku adresu przeglądarki. Spróbujmy na przykład wywołać adres URL [https://client-vuejs-impot/calcul-impot] bez przechodzenia przez etap uwierzytelniania. Haker z pewnością by tego spróbował. Otrzymujemy następujący widok:

Rzeczywiście wyświetla się ekran obliczania podatku. Teraz spróbujmy wypełnić pola wprowadzania danych i je zatwierdzić:

Okazuje się wówczas, że przycisk [1] [Valider] pozostaje nadal nieaktywny, nawet jeśli dane wprowadzono poprawnie. Przyjrzyjmy się kodowi widoku [FormCalculImpot]:
<b-col cols="5">
<b-button type="submit" variant="primary" :disabled="formInvalide">Valider</b-button>
</b-col>
W wierszu 2 widać, że jego stan aktywny/nieaktywny zależy od właściwości [formInvalide]. Jest to następująca właściwość obliczeniowa:
formInvalide() {
return (
// nieprawidłowe wynagrodzenie
!this.salaireValide ||
// lub nieprawidłowe dane dotyczące dzieci
!this.enfantsValide ||
// lub brak danych podatkowych
!this.$métier.taxAdminData
);
},
W wierszu 8 widać, że aby formularz był prawidłowy, konieczne jest uzyskanie danych podatkowych. Dane te są jednak pobierane podczas walidacji widoku [Authentification], który użytkownik „pominął”. Nie będzie więc mógł zatwierdzić formularza. Gdyby mógł to zrobić, otrzymałby komunikat o błędzie z serwera informujący, że nie został uwierzytelniony. Sprawdzanie poprawności danych musi zawsze odbywać się po stronie serwera. Sprawdzanie po stronie przeglądarki zawsze można obejść. Wystarczy użyć klienta typu [Postman], który będzie wysyłał surowe żądania do serwera.
Teraz wywołajmy URL i [https://localhost/client-vuejs-impot/liste-des-simulations]. Otrzymujemy następujący widok:

Teraz URL i [https://localhost/client-vuejs-impot/fin-session]. Otrzymujemy następujący widok:

Teraz widok, który nie istnieje: [https://localhost/client-vuejs-impot/abcd]:

Nasza aplikacja dość dobrze radzi sobie z ręcznie wpisanymi adresami typu URL. Gdy są one wywoływane, router aplikacji o tym wie. Możliwe jest zatem podjęcie działań, zanim widok zostanie ostatecznie wyświetlony. Przyjrzyjmy się tej kwestii na przykładzie projektu [vuejs-21].
Kolejna kwestia, na którą warto zwrócić uwagę, jest następująca. Wyobraźmy sobie, że użytkownik przeprowadził kilka symulacji zgodnie z zasadami:

Teraz odświeżmy stronę za pomocą F5:

Zrobiliśmy coś, czego nie zaleca się: wpisaliśmy kod URL ręcznie (wpisanie F5 sprowadza się do tego samego). W ten sposób utraciliśmy nasze symulacje.
Kolejny projekt o nazwie [vuejs-21] ma na celu wprowadzenie dwóch ulepszeń:
- sprawdzanie kodu URL wprowadzonego przez użytkownika;
- zachowanie pamięci aplikacji, nawet jeśli użytkownik wpisze URL. Powyżej widać, że utraciliśmy listę symulacji;