Skip to content

18. Klient Vue.js serwera obliczającego podatek

18.1. Architecture

Zaimplementujemy aplikację klient-serwer o następującej architekturze:

Image

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:

Image

Drugi widok to widok obliczania podatku:

Image

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

Image

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

Image

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

Image

18.3. Elementy projektu [vuejs-20]

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

Image

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:

  1. utworzenie instancji klasy [Métier] za pomocą instrukcji [métier=new Métier()], gdy chcemy utworzyć instancję klasy, ale nie dysponujemy jeszcze danymi [taxAdminData];
  2. 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:

Image

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

Image

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:

Image

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

Image

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

Image

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

Image

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:

Image

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:

Image

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

Image

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

Image

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:

Image

Krok 2

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

Image

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

Image

Image

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

Image

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

Image

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

Image

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:

Image

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

Image

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

Image

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:

Image

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

Image

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

Image

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:

Image

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

Image

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;