Skip to content

18. Vue.js-Client des Steuerberechnungsservers

18.1. Architektur

Wir werden eine Client/Server-Anwendung mit der folgenden Architektur implementieren:

Image

Der Steuerberechnungsserver wird in Version 14 vorliegen, die im Dokument |https://tahe.developpez.com/tutoriels-cours/php7| entwickelt wurde

18.2. Anwendungsansichten

Die Anwendungsansichten [vuejs-10] entsprechen denen aus Version 13 des Dokuments |https://tahe.developpez.com/tutoriels-cours/php7| des Steuerberechnungsservers bei Verwendung im HTML-Modus. In dieser Anwendung werden diese Ansichten jedoch vom JavaScript-Client statt vom PHP-Server generiert.

Die erste Ansicht ist die Authentifizierungsansicht:

Image

Die zweite Ansicht ist die Steuerberechnungsansicht:

Image

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

Image

Der obige Bildschirm zeigt, dass Sie Simulation Nr. 1 löschen können. Dies führt zur folgenden Ansicht:

Image

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

Image

18.3. Projektelemente [vuejs-20]

Die Projektstruktur [vuejs-20] sieht wie folgt aus:

Image

Das Projekt umfasst folgende Elemente:

  • [assets/logo.jpg]: das Projektlogo;
  • [layers]: die [Business]- und [DAO]-Schichten der Anwendung;
  • [plugins]: die Plugins der Anwendung;
  • [views]: die Ansichten der Anwendung;
  • [config.js]: konfiguriert die Anwendung;
  • [router.js]: definiert das Routing der Anwendung;
  • [store.js]: der [Vuex]-Store;
  • [main.js]: das Hauptskript der Anwendung;

18.3.1. Die [Business]- und [DAO]-Schichten

18.3.1.1. Die [DAO]-Schicht

Die [DAO]-Schicht wird durch die [Dao]-Klasse in Abschnitt |vuejs-10| implementiert

18.3.1.2. Die [business]-Schicht

Die [business]-Schicht wird durch die Klasse [Business] im Dokument |https://tahe.developpez.com/tutoriels-cours/php7| implementiert. Die folgende Methode [setTaxAdminData] wurde hinzugefügt:


// constructeur
  constructor(taxAdmindata) {
    // this.taxAdminData : données de l'administration fiscale
    this.taxAdminData = taxAdmindata;
  }
 
  // setter
  setTaxAdminData(taxAdmindata) {
    // this.taxAdminData : données de l'administration fiscale
    this.taxAdminData = taxAdmindata;
}

Die Methode [setTaxAdminData] hat dieselbe Funktion wie der Konstruktor. Ihre Existenz ermöglicht die folgende Abfolge:

  1. Instanziieren Sie die Klasse [Métier] mit der Anweisung [métier=new Métier()], wenn Sie die Klasse instanziieren möchten, aber noch nicht über die Daten [taxAdminData] verfügen;
  2. Füllen Sie anschließend die Eigenschaft [taxAdminData] mit der Operation [métier.setTaxAdminData(taxAdmindata)];

18.3.2. Die Konfigurationsdatei [config]

Die Datei [config.js] sieht wie folgt aus:


// utilisation de la bibliothèque [axios]
const axios = require('axios');
// timeout des requêtes HTTP
axios.defaults.timeout = 2000;
// la base des URL du serveur de calcul de l'impôt
// le schéma [https] pose des problèmes à Firefox parce que le serveur de calcul
// de l'impôt envoie un certificat autosigné. ok avec Chrome et Edge. Safari pas testé.
axios.defaults.baseURL = 'https://localhost/php7/scripts-web/impots/version-14';
// on va utiliser des cookies
axios.defaults.withCredentials = true;
 
// export de la configuration
export default {
  axios: axios
}

Diese Konfiguration gilt für die [axios]-Bibliothek, die die [dao]-Schicht für ihre HTTP-Anfragen verwendet. Beachten Sie in Zeile 8, dass der Server über einen sicheren Port [https] läuft.

18.3.3. Die Plugins

Die Plugins [pluginDao, pluginMétier, pluginConfig] dienen dazu, drei neue Eigenschaften für die [Vue]-Funktion/Klasse zu erstellen:

  • [$dao]: hat den Wert einer Instanz der [Dao]-Klasse;
  • [$métier]: hat den Wert einer Instanz der Klasse [Métier];
  • [$config]: wird auf das vom Konfigurationsdatei [config] exportierte Objekt gesetzt;

[pluginDao]


export default {
  install(Vue, dao) {
    // ajoute une propriété [$dao] à la classe Vue
    Object.defineProperty(Vue.prototype, '$dao', {
      // lorsque Vue.$dao est référencé, on rend le 2ième paramètre [dao]
      get: () => dao,
    })
  }
}
 
[pluginMétier]
 
export default {
  install(Vue, métier) {
    // ajoute une propriété [$métier] à la classe Vue
    Object.defineProperty(Vue.prototype, '$métier', {
      // lorsque Vue.$métier est référencé, on rend le 2ième paramètre [métier]
      get: () => métier,
    })
  }
}

[pluginConfig]


export default {
  install(Vue, config) {
    // ajoute une propriété [$config] à la classe vue
    Object.defineProperty(Vue.prototype, '$config', {
      // lorsque Vue.$config est référencé, on rend le 2ième paramètre [config]
      get: () => config,
    })
  }
}

18.3.4. Der [Vuex]-Store

Der [Vuex]-Store wird durch die folgende [store]-Datei implementiert:


// plugin Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
 
// store Vuex
const store = new Vuex.Store({
  state: {
    // le tableau des simulations
    simulations: [],
    // le n° de la dernière simulation
    idSimulation: 0
  },
  mutations: {
    // suppression ligne n° index
    deleteSimulation(state, index) {
      // eslint-disable-next-line no-console
      console.log("mutation deleteSimulation");
      // on supprime la ligne n° [index]
      state.simulations.splice(index, 1);
      // eslint-disable-next-line no-console
      console.log("store simulations", state.simulations);
    },
    // ajout d'une simulation
    addSimulation(state, simulation) {
      // eslint-disable-next-line no-console
      console.log("mutation addSimulation");
      // n° de la simulation
      state.idSimulation++;
      simulation.id = state.idSimulation;
      // on ajoute la simulation au tableau des simulations
      state.simulations.push(simulation);
    },
    // nettoyage state
    clear(state) {
      state.simulations = [];
      state.idSimulation = 1;
    }
  }
});
// export de l'objet [store]
export default store;

Kommentare

  • Zeilen 2–4: Das [Vuex]-Plugin wird in das [Vue]-Framework integriert;
  • Zeilen 8–13: Wir fügen die folgenden Elemente in den [Vuex]-Store ein:
    • [simulations]: die Liste der vom Benutzer durchgeführten Simulationen;
    • [idSimulation]: die ID der zuletzt vom Benutzer durchgeführten Simulation;

Beachten Sie, dass der Store von den Ansichten gemeinsam genutzt wird und sein Inhalt reaktiv ist: Wenn er geändert wird, werden die Ansichten, die ihn verwenden, automatisch aktualisiert. In unserer Anwendung muss nur das Element [simulations] reaktiv sein, nicht das Element [simulationId]. Wir haben dieses Element der Einfachheit halber im Store belassen;

  • Zeilen 14–40: die Mutationen, die für das [state]-Objekt aus den Zeilen 8–13 zulässig sind. Beachten Sie, dass diese immer das [state]-Objekt aus den Zeilen 8–13 als ersten Parameter erhalten;
    • Zeile 16: Die Mutation [deleteSimulation] ermöglicht es Ihnen, eine Simulation zu löschen, indem Sie deren [index]-Nummer angeben;
    • Zeile 25: Die Mutation [addSimulation] ermöglicht es Ihnen, eine neue Simulation zum Simulationsarray hinzuzufügen;
    • Zeile 35: Die Operation [clear] setzt das [state]-Objekt aus den Zeilen 8–13 zurück;

18.3.5. Die Routing-Datei [router]

Die Routing-Datei lautet wie folgt:


// imports
import Vue from 'vue'
import VueRouter from 'vue-router'
// les vues
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'
 
// plugin de routage
Vue.use(VueRouter)
 
// les routes de l'application
const routes = [
  // authentification
  {
    path: '/', name: 'authentification', component: Authentification
  },
  // calcul de l'impôt
  {
    path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
  },
  // liste des simulations
  {
    path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations
  },
  // fin de session
  {
    path: '/fin-session', name: 'finSession', component: Authentification
  }
]
 
// le routeur
const router = new VueRouter({
  // les routes
  routes,
  // le mode d'affichage des routes dans le navigateur
  mode: 'history',
})
 
// export du router
export default router

Kommentare

  • Zeile 16: Beim Start der Anwendung wird die Ansicht [Authentication] angezeigt, da ihre URL die Stamm-URL [/] ist;
  • Zeile 20: Die Ansicht [TaxCalculation] wird angezeigt, wenn die URL [/tax-calculation] angefordert wird;
  • Zeile 24: Die Ansicht [SimulationList] wird angezeigt, wenn die URL [/simulation-list] angefordert wird;
  • Zeile 28: Die Ansicht [Authentication] wird angezeigt, wenn die URL [/end-session] angefordert wird;
  • Zeilen 33–38: Es wird ein [router]-Objekt mit diesen Routen (Zeile 35) und dem [history]-Modus (Zeile 37) für die URL-Verwaltung erstellt;
  • Zeile 41: Dieser Router wird exportiert;

18.3.6. Das Hauptskript [main.js]

Das Skript [main.js] lautet wie folgt:


// imports
import Vue from 'vue'
 
// vue principale
import Main from './views/Main.vue'
 
// plugin [bootstrap-vue]
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue);
 
// CSS bootstrap
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
 
// routeur
import router from './router'
 
// plugin [config]
import config from './config';
import pluginConfig from './plugins/pluginConfig'
Vue.use(pluginConfig, config)
 
// instanciation couche [dao]
import Dao from './couches/Dao';
const dao = new Dao(config.axios);
 
// plugin [dao]
import pluginDao from './plugins/pluginDao'
Vue.use(pluginDao, dao)
 
// instanciation couche [métier]
import Métier from './couches/Métier';
const métier = new Métier();
 
// plugin [métier]
import pluginMétier from './plugins/pluginMétier'
Vue.use(pluginMétier, métier)
 
// store Vuex
import store from './store'
 
// démarrage de l'UI
new Vue({
  el: '#app',
  // le routeur
  router: router,
  // le store Vuex
  store: store,
  // la vue principale
  render: h => h(Main),
})

Beachten Sie folgende Punkte:

  • Zeilen 18–21: Das vom Skript [./config] exportierte Objekt ist in der Eigenschaft [Vue.$config] verfügbar und steht somit allen Ansichten in der Anwendung zur Verfügung. Dies war hier nicht erforderlich, da das Objekt [config] nur vom Skript [main] (Zeile 25) verwendet wird. Es ist jedoch üblich, dass die Konfiguration von mehreren Ansichten benötigt wird. Wir wollten daher das Prinzip beibehalten, sie in einem View-Attribut verfügbar zu machen;
  • Zeilen 24–25: Instanziierung der [dao]-Schicht. Die Klasse [Dao] wird in Zeile 24 importiert und dann in Zeile 25 instanziiert. Ihr Konstruktor nimmt das [axios]-Objekt – eine Konfigurationseigenschaft – als einzigen Parameter entgegen;
  • Zeilen 27–29: Die [dao]-Schicht wird im Attribut [$dao] aller Ansichten verfügbar gemacht;
  • Zeilen 31–37: Wir wiederholen denselben Ablauf für die [business]-Schicht. Der Konstruktor der [Business]-Klasse nimmt [taxAdminData] als Parameter entgegen, das Steuerverwaltungsdaten repräsentiert. Diese Daten liegen uns noch nicht vor. Das [business]-Objekt in Zeile 33 muss daher später gefüllt werden;
  • Zeile 40: Wir importieren den [Vuex]-Store;
  • Zeilen 43–51: Wir instanziieren die Hauptansicht [Main] (Zeilen 5 und 50) und übergeben ihr zwei Parameter:
    • Zeile 46: den in Zeile 16 definierten Router [router];
    • Zeile 48: den in Zeile 40 definierten [Vuex]-Store [store];
    • In beiden Fällen steht der Name der Eigenschaft links und ihr Wert rechts. Die Eigenschaftsnamen [router, store] werden von den Frameworks [vue-router] und [vuex] festgelegt. Die zugehörigen Werte können beliebig sein;

18.4. Die Ansichten der Anwendung

18.4.1. Die Hauptansicht [Main]

Der Code für die Hauptansicht [Main] lautet wie folgt:


<!-- définition HTML de la vue -->
<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>
      <!-- erreur requête HTTP -->
      <b-alert
        show
        variant="danger"
        v-if="showError"
      >L'erreur suivante s'est produite : {{error.message}}</b-alert>
      <!-- vue courante -->
      <router-view v-if="showView" @loading="mShowLoading" @error="mShowError" />
      <!-- loading -->
      <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 {
  // name
  name: "app",
  // inner state
  data() {
    return {
      // controls waiting alert
      showLoading: false,
      // controls error alert
      showError: false,
      // controls the display of the current routing view
      showView: true,
      // an error message
      error: ""
    };
  },
  // event managers
  methods: {
    // asynchronous request error
    mShowError(error) {
      // eslint-disable-next-line
      console.log("Main evt error");
      // error msg is displayed
      this.error = error;
      this.showError = true;
      // hide the routed view
      this.showView = false;
      // we hide the waiting message
      this.showLoading = false;
    },
    // whether or not to display a waiting icon
    mShowLoading(value) {
      // eslint-disable-next-line
      console.log("Main evt showLoading");
      // whether or not to display the waiting alert
      this.showLoading = value;
    }
  }
};
</script>

Kommentare

  • Die Ansicht [Main] ist für das Layout der gerouteten und angezeigten Ansicht zuständig. Zeile 23:

Image

  • Die Zeilen 5–15 zeigen Zone 1 an;
  • Zeile 23 zeigt die Routenansicht [2] an;
  • Zeilen 16–19: eine Warnmeldung, die nur im Falle eines Kommunikationsfehlers mit dem Steuerberechnungsserver angezeigt wird;
  • Zeilen 25–28: eine Lademeldung, die bei jeder HTTP-Anfrage an den Server angezeigt wird;
  • Alle Ansichten werden in diesem Layout angezeigt, da jede weitergeleitete Ansicht in den Zeilen 20–24 dargestellt wird. Die Ansicht [Main] dient dazu, die Elemente herauszufiltern, die von den verschiedenen Ansichten gemeinsam genutzt werden können;
  • Zeile 23: Jede weitergeleitete Ansicht kann drei Ereignisse auslösen:
    • [loading]: Eine HTTP-Anfrage wurde gesendet. Die Lademeldung muss angezeigt werden;
    • [error]: Die HTTP-Anfrage ist mit einem Fehler beendet. Die Fehlermeldung muss angezeigt und die weitergeleitete Ansicht ausgeblendet werden;
  • Zeilen 38–49: Der Status der Ansicht:
    • Zeile 41: [showLoading] steuert die Anzeige der Meldung, die darauf hinweist, dass eine HTTP-Anfrage läuft (Zeile 25);
    • Zeile 43: [showError] steuert die Anzeige der Fehlermeldung für eine HTTP-Anfrage (Zeilen 17–21);
    • Zeile 45: [showView] steuert die Anzeige der weitergeleiteten Ansicht (Zeile 23);
  • Zeilen 53–63: Die Methode [mShowError] verarbeitet das von der weitergeleiteten Ansicht ausgelöste [error]-Ereignis (Zeile 23);
  • Zeilen 65–70: Die Methode [mShowLoading] verarbeitet das von der weitergeleiteten Ansicht ausgelöste [loading]-Ereignis (Zeile 23);
  • Zeile 23: Wir konzentrieren uns auf die Ereignisse [error] und [loading]. Sie werden nur abgefangen, wenn die weitergeleitete Ansicht angezeigt wird [showView=true]. Aus diesem Grund wird die weitergeleitete Ansicht zunächst angezeigt (Zeile 45). Sie wird nur im Falle eines Fehlers ausgeblendet (Zeile 60). Um dieses Problem zu vermeiden, hätten wir anstelle von [v-if] die Direktive [v-show] verwenden können. Der Unterschied zwischen diesen beiden Direktiven ist folgender:
    • [v-if=’false’] blendet den kontrollierten Block aus, indem er aus dem globalen HTML entfernt wird. Ereignisse aus der gerouteten Ansicht können dann nicht mehr abgefangen werden;
    • [v-show=’false’] blendet den kontrollierten Block aus, indem es dessen CSS manipuliert, aber der Code des Blocks bleibt im globalen HTML vorhanden und kann somit Ereignisse aus der gerouteten Ansicht abfangen;

18.4.2. Die [Layout]-Ansicht

Der Code der [Layout]-Ansicht lautet wie folgt:


<!-- definition HTML of the routed view layout -->
<template>
  <!-- line -->
  <div>
    <b-row>
      <!-- three-column zone on the left -->
      <b-col cols="3" v-if="left">
        <slot name="left" />
      </b-col>
      <!-- nine-column zone on the right -->
      <b-col cols="9" v-if="right">
        <slot name="right" />
      </b-col>
    </b-row>
  </div>
</template>
 
<script>
  export default {
    // paramètres de la vue
    props: {
      // contrôle la colonne de gauche
      left: {
        type: Boolean
      },
      // contrôle la colonne de droite
      right: {
        type: Boolean
      }
    }
  };
</script>

Kommentare

  • Die Ansicht [Layout] ermöglicht es Ihnen, die geroutete Ansicht in zwei Bereiche aufzuteilen:
    • einen dreispaltigen Bootstrap-Bereich auf der linken Seite (Zeilen 7–9). Dieser Bereich enthält das Navigationsmenü, sofern vorhanden;
    • einen 9-spaltigen Bereich auf der rechten Seite (Zeilen 11–13). In diesem Bereich werden die von der gerouteten Ansicht bereitgestellten Informationen angezeigt;

18.4.3. Die Ansicht [Authentication]

Die Authentifizierungsansicht sieht wie folgt aus:

Image

Diese Ansicht leitet sich vom [Layout] ab, indem die linke Spalte entfernt wird, um nur die rechte Spalte anzuzeigen.

Der Code lautet wie folgt:


<!-- définition HTML de la vue -->
<template>
  <Layout :left="false" :right="true">
    <template slot="right">
      <!-- formulaire HTML - on poste ses valeurs avec l'action [authentifier-utilisateur] -->
      <b-form @submit.prevent="login">
        <!-- titre -->
        <b-alert show variant="primary">
          <h4>Bienvenue. Veuillez vous authentifier pour vous connecter</h4>
        </b-alert>
        <!-- 1ère ligne -->
        <b-form-group label="Nom d'utilisateur" label-for="user" label-cols="3">
          <!-- zone de saisie user -->
          <b-col cols="6">
            <b-form-input type="text" id="user" placeholder="Nom d'utilisateur" v-model="user" />
          </b-col>
        </b-form-group>
        <!-- 2ième ligne -->
        <b-form-group label="Mot de passe" label-for="password" label-cols="3">
          <!-- zone de saisie password -->
          <b-col cols="6">
            <b-input type="password" id="password" placeholder="Mot de passe" v-model="password" />
          </b-col>
        </b-form-group>
        <!-- 3ième ligne -->
        <b-alert
          show
          variant="danger"
          v-if="showError"
          class="mt-3"
        >L'erreur suivante s'est produite : {{message}}</b-alert>
        <!-- bouton de type [submit] sur une 3ième ligne -->
        <b-row>
          <b-col cols="2">
            <b-button variant="primary" type="submit" :disabled="!valid">Valider</b-button>
          </b-col>
        </b-row>
      </b-form>
    </template>
  </Layout>
</template>
 
<!-- dynamique de la vue -->
<script>
import Layout from "./Layout";
export default {
  // component status
  data() {
    return {
      // user
      user: "",
      // password
      password: "",
      // controls the display of an error msg
      showError: false,
      // the error message
      message: "",
      // session started
      sessionStarted: false
    };
  },
 
  // components used
  components: {
    Layout
  },
 
  // calculated properties
  computed: {
    // valid entries
    valid() {
      return this.user && this.password && this.sessionStarted;
    }
  },
 
  // event managers
  methods: {
    // ----------- authentication
    async login() {
      try {
        // start waiting
        this.$emit("loading", true);
        // blocking server authentication
        const response = await this.$dao.authentifierUtilisateur(
          this.user,
          this.password
        );
        // end of loading
        this.$emit("loading", false);
        // response analysis
        if (response.état != 200) {
          // error is displayed
          this.message = response.réponse;
          this.showError = true;
          return;
        }
        // no error
        this.showError = false;
        // --------- we now request data from the tax authorities
        // start waiting
        this.$emit("loading", true);
        // blocking request to the server
        const response2 = await this.$dao.getAdminData();
        // end of loading
        this.$emit("loading", false);
        // response analysis
        if (response2.état != 1000) {
          // error is displayed
          this.message = response2.réponse;
          this.showError = true;
          return;
        }
        // no error
        this.showError = false;
        // the received data is stored in the [business] layer
        this.$métier.setTaxAdminData(response2.réponse);
        // we move on to the tax calculation view
        this.$router.push({ name: "calculImpot" });
      } catch (error) {
        // the error is traced back to the main component
        this.$emit("error", error);
      }
    }
  },
  // life cycle: the component has just been created
  created() {
    // eslint-disable-next-line
    console.log("authentification", "created");
    // start a jSON session with the server
    // start waiting
    this.$emit("loading", true);
    // initialize the session with the server - asynchronous request
    // we use the promise rendered by the [dao] layer methods
    this.$dao
      // initialize a jSON session
      .initSession()
      // we got the answer
      .then(response => {
        // end waiting
        this.$emit("loading", false);
        // response analysis
        if (response.état != 700) {
          // error is displayed
          this.message = response.réponse;
          this.showError = true;
          return;
        }
        // the session has started
        this.sessionStarted = true;
      })
      // in case of error
      .catch(error => {
        // the error is traced back to the [Main] view
        this.$emit("error", error);
      });
  }
};
</script>

Kommentare

  • Zeile 3: Die Ansicht [Authentication] verwendet nur die rechte Spalte des [Layout] (Zeilen 3 und 4);
  • Zeilen 6–38: Das Bootstrap-Formular, das Bereich 1 im obigen Screenshot generiert;
  • Zeile 6: Das [@submit]-Ereignis tritt ein, wenn der Benutzer auf die Schaltfläche [submit] in Zeile 35 klickt. Der Modifikator [prevent] stellt sicher, dass die Seite bei [submit] nicht neu geladen wird. Wir hätten auch schreiben können:
    • ein <b-form>-Tag ohne Behandlung des [submit]-Ereignisses;
    • ein <b-button>-Tag mit dem Ereignis [@click='login'] und ohne das Attribut [type='submit'];

Das funktioniert ebenfalls. Der Vorteil der gewählten Lösung besteht darin, dass das Formular nicht nur durch Klicken auf die Schaltfläche [Submit], sondern auch durch Drücken der [Enter]-Taste in den Eingabefeldern übermittelt wird. Die Lösung [<b-form @submit.prevent="login">] wurde daher hier aus Gründen der Benutzerfreundlichkeit gewählt;

  • Zeilen 33–37: eine Warnmeldung, die erscheint, wenn der Server die vom Benutzer eingegebenen Anmeldedaten abgelehnt hat:

Image

  • Zeile 35: Die Schaltfläche [Submit] ist nicht immer aktiv. Ihr Status hängt vom berechneten Attribut [valid] in den Zeilen 71–73 ab. Das Attribut [valid] ist wahr, wenn:
    • die Felder [user, password] des Formulars ausgefüllt sind;
    • die JSON-Sitzung gestartet wurde. Zu Beginn ist diese Sitzung noch nicht gestartet (Zeile 59) und daher ist die Schaltfläche [Validate] inaktiv.
  • Zeilen 49–60: der Status der Ansicht;
    • [user] steht für die Eingabe des Benutzers im Feld [user] (Zeilen 12–17) des Formulars. Die [v-model]-Direktive in Zeile 15 stellt eine bidirektionale Bindung zwischen der Benutzereingabe und dem Attribut [user] der Ansicht her;
    • [password] steht für die Eingabe des Benutzers im Feld [password] (Zeilen 19–24) des Formulars. Die [v-model]-Anweisung in Zeile 22 stellt eine bidirektionale Bindung zwischen der Eingabe des Benutzers und dem Attribut [password] der Ansicht her;
    • [showError] steuert (Zeile 29) die Anzeige der Warnmeldung in den Zeilen 26–31;
    • [message] ist die Fehlermeldung (Zeile 31), die in der Warnmeldung in den Zeilen 26–31 angezeigt werden soll;
    • [sessionStarted] gibt an, ob die JSON-Sitzung mit dem Server gestartet wurde oder nicht. Zu Beginn hat dieses Attribut den Wert [false] (Zeile 59). Die JSON-Sitzung mit dem Server wird im [created]-Ereignis des Lebenszyklus der Ansicht initialisiert (Zeilen 126–156). Wenn der Server positiv antwortet, wird das Attribut [sessionStarted] auf [true] gesetzt (Zeile 149);
  • Zeilen 126–156: Die Funktion [created] wird ausgeführt, wenn die Ansicht [Authentication] erstellt wurde (auch wenn sie noch nicht unbedingt angezeigt wird). Im Hintergrund wird dann eine JSON-Sitzung mit dem Server initialisiert. Wir wissen, dass dies die erste Aktion ist, die mit dem Steuerberechnungsserver durchgeführt werden muss. Dazu verwenden wir die [dao]-Schicht der Anwendung (Zeile 134). Alle Methoden in dieser Schicht sind asynchron. Hier verwenden wir das von der Methode [$dao.initSession] zurückgegebene Promise, das die JSON-Sitzung mit dem Server initialisiert.
  • Zeilen 138–150: Der Code, der ausgeführt wird, wenn der Server seine Antwort ohne Fehler zurückgibt;
  • Zeile 142: Wir überprüfen die Eigenschaft [status] der Antwort. Sie muss den Wert [700] haben, damit der Vorgang erfolgreich war. Andernfalls ist ein Fehler aufgetreten, dessen Ursache in der Eigenschaft [response.response] angegeben ist (Zeile 144). Anschließend zeigen wir die Fehlermeldung in der Ansicht an (Zeile 145);
  • Zeile 149: Wir vermerken, dass die JSON-Sitzung gestartet wurde;
  • Zeilen 152–155: Der Code, der im Falle eines Fehlers ausgeführt wird. Dieser Fehler wird an die übergeordnete Ansicht [Main] weitergeleitet, die
    • den Fehler anzeigt;
    • die Wartemeldung ausblendet;
    • die weitergeleitete Ansicht, die Ansicht [Authentication], ausblendet;
  • Zeilen 79–124: Die Methode [login] verarbeitet den Klick auf die Schaltfläche [Validate];
  • Zeile 79: Der Methode wurde das Schlüsselwort [async] vorangestellt, um die Verwendung des Schlüsselworts [await] in den Zeilen 84 und 103 zu ermöglichen;
  • Zeilen 84–87: blockierender Aufruf der Methode [$dao.authenticateUser(user, password)]. Wir hätten ein [Promise] verwenden können, wie es in der Funktion [created] geschehen ist. Wir wollten die Stile variieren. Es besteht keine Gefahr, den Benutzer zu blockieren, da wir für alle HTTP-Anfragen ein [timeout] von 2 Sekunden festgelegt haben. Sie müssen nicht lange warten. Außerdem können sie nichts tun, bis der Server seine Antwort zurückgegeben hat, da die Schaltfläche [Validate] bis dahin inaktiv bleibt;
  • Zeile 91: Der Steuerberechnungsserver sendet JSON-Antworten, die alle die Struktur [{‘action’:action, ‘status’:val, ‘response’:response}] aufweisen. Die Authentifizierung war erfolgreich, wenn [status==200]. Ist dies nicht der Fall, wird eine Fehlermeldung angezeigt (Zeilen 93–94);
  • Zeile 98: Alle Fehlermeldungen aus einem vorherigen Vorgang werden ausgeblendet;
  • Zeilen 99–116: Wir fordern nun die für die Steuerberechnung erforderlichen Daten der Steuerbehörde vom Server an. In [this.$métier] haben wir eine Instanz der Klasse [Métier], die zu diesem Zeitpunkt noch nichts tun kann, da ihr diese Daten fehlen;
  • Zeile 103: Die Daten der Steuerbehörde werden über einen blockierenden Vorgang vom Server angefordert;
  • Zeilen 107–112: Die Antwort des Servers wird analysiert. Sie muss einen Statuswert von 1000 haben; andernfalls ist ein Fehler aufgetreten. Im letzteren Fall wird eine Fehlermeldung angezeigt (Zeilen 109–110);
  • Zeilen 113–118: Wenn der Vorgang erfolgreich ist, werden:
    • die Fehlermeldung ausblenden (Zeile 114);
    • leiten die Steuerbehörden-Daten an die [business]-Schicht weiter (Zeile 116);
    • die Ansicht [CalculImpot] anzeigen, Zeile 118. Zur Erinnerung: [this.$router] bezieht sich auf den Anwendungsrouter. Die Methode [push] wird verwendet, um die nächste geroutete Ansicht festzulegen. Hier beziehen wir uns auf sie über ihr Attribut [name]. Wir hätten uns auch über ihr Attribut [path] darauf beziehen können. Diese Informationen befinden sich in der Routing-Datei:

// calcul de l'impôt
  {
    path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
  },
  • Zeilen 119–122: Der [catch]-Block wird ausgelöst, wenn eine der beiden HTTP-Anfragen fehlschlägt (Server nicht gefunden, Zeitüberschreitung usw.). Der Fehler wird dann an die übergeordnete Ansicht [Main] gemeldet, die ihn anzeigt, die Lademeldung ausblendet und die Ansicht [Authentication] ausblendet;

18.4.4. Die Ansicht [CalculImpot]

Die Ansicht [CalculImpot] sieht wie folgt aus:

Image

  • [1]: Ein Navigationsmenü nimmt die linke Spalte der gerouteten Ansicht ein;
  • [2]: Das Formular zur Steuerberechnung nimmt die rechte Spalte der gerouteten Ansicht ein;

Der Code für die Ansicht [TaxCalculation] sieht wie folgt aus:


<!-- definition HTML of the view -->
<template>
  <div>
    <Layout :left="true" :right="true">
      <!-- tax calculation form on the right -->
      <FormCalculImpot slot="right" @resultatObtenu="handleResultatObtenu" />
      <!-- left-hand navigation menu -->
      <Menu slot="left" :options="options" />
    </Layout>
    <!-- display area for tax calculation results under the form -->
    <b-row v-if="résultatObtenu" class="mt-3">
      <!-- empty three-column zone -->
      <b-col cols="3" />
      <!-- nine-column zone -->
      <b-col cols="9">
        <b-alert show variant="success">
          <span v-html="résultat"></span>
        </b-alert>
      </b-col>
    </b-row>
  </div>
</template>
 
<script>
// imports
import FormCalculImpot from "./FormCalculImpot";
import Menu from "./Menu";
import Layout from "./Layout";
 
export default {
  // état interne
  data() {
    return {
      // options du menu
      options: [
        {
          text: "Liste des simulations",
          path: "/liste-des-simulations"
        },
        {
          text: "Fin de session",
          path: "/fin-session"
        }
      ],
      // résultat du calcul de l'impôt
      résultat: "",
      résultatObtenu: false
    };
  },
  // composants utilisés
  components: {
    Layout,
    FormCalculImpot,
    Menu
  },
  // méthodes de gestion des évts
  methods: {
    // résultat du calcul de l'impôt
    handleResultatObtenu(résultat) {
      // on construit le résultat en chaîne HTML
      const impôt = "Montant de l'impôt : " + résultat.impôt + " euro(s)";
      const décôte = "Décôte : " + résultat.décôte + " euro(s)";
      const réduction = "Réduction : " + résultat.réduction + " euro(s)";
      const surcôte = "Surcôte : " + résultat.surcôte + " euro(s)";
      const taux = "Taux d'imposition : " + résultat.taux;
      this.résultat =
        impôt +
        "<br/>" +
        décôte +
        "<br/>" +
        réduction +
        "<br/>" +
        surcôte +
        "<br/>" +
        taux;
      // affichage du résultat
      this.résultatObtenu = true;
      // ---- maj du store [Vuex]
      // une simulation de +
      this.$store.commit("addSimulation", résultat);
    }
  }
};
</script>

Kommentare

  • Zeile 4: Hier sind die beiden Spalten des [Layout] vorhanden;
  • Zeile 6: Das Formular zur Steuerberechnung nimmt die rechte Spalte ein. Es löst das Ereignis [resultatObtenu] aus, sobald das Ergebnis der Steuerberechnung vorliegt. Beachten Sie, dass Ereignisnamen und die Namen der Methoden, die diese verarbeiten, keine Akzentzeichen enthalten dürfen;
  • Zeile 8: Das Navigationsmenü nimmt die linke Spalte ein;
  • Zeilen 11–20: Das Ergebnis der Steuerberechnung wird unterhalb des Formulars angezeigt:

Image

  • Zeile 11: Das Ergebnis wird nur angezeigt, wenn das Attribut [resultObtained] (Zeile 47) den Wert [true] hat;
  • Zeilen 34–48: der Ansichtsstatus:
    • [options]: die Liste der Optionen des Navigationsmenüs. Dieses Array wird in Zeile 8 als Parameter an die [Menu]-Komponente übergeben;
    • [result]: das Ergebnis der Steuerberechnung. Dieses Ergebnis ist eine HTML-Zeichenkette. Aus diesem Grund wurde in Zeile 17 die Direktive [v-html] verwendet, um es anzuzeigen;
    • [resultObtained]: der boolesche Wert, der die Anzeige des Ergebnisses steuert, Zeile 11;
  • Zeilen 59–81: Die Methode [handleResultatObtenu] zeigt das Ergebnis der Steuerberechnung an, das ihr von der untergeordneten Ansicht [FormCalculImpot] in Zeile 6 übermittelt wurde. Dieses Ergebnis ist ein Objekt mit den Eigenschaften [tax, discount, reduction, surcharge, rate, married, children, salary];
  • Zeilen 61–75: Das Objekt [tax, discount, reduction, surcharge, rate] wird in HTML-Text eingefügt, der durch Zeile 17 der Vorlage gerendert wird;
  • Zeile 77: Dieses Ergebnis wird angezeigt;
  • Zeile 80: Ruft die Methode [addSimulation] des Vuex-Stores auf, die [result] zu den bereits im Store vorhandenen Simulationen hinzufügt;

18.4.5. Das Navigationsmenü [Menu]

Das Navigationsmenü wird in der linken Spalte der gerouteten Ansichten angezeigt:

Image

Der Code für die Ansicht [Menu] lautet wie folgt:


<!-- definition HTML of the view -->
<template>
  <!-- bootstrap vertical menu -->
  <b-nav vertical>
    <!-- menu options -->
    <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 {
  // paramètres de la vue
  props: {
    options: {
      type: Array
    }
  }
};
</script>

Kommentare

  • Die Menüoptionen werden durch den Parameter [options] bereitgestellt (Zeilen 7, 20–22);
  • jedes Element des Arrays [options] verfügt über eine Eigenschaft [text] (Zeile 12), die den Linktext angibt, sowie eine Eigenschaft [path] (Zeile 9), die den Pfad zur Zielansicht des Links angibt;

18.4.6. Die Ansicht [FormCalculImpot]

Diese Ansicht stellt das Formular zur Steuerberechnung bereit:

Image

Der Code lautet wie folgt:


  <!-- définition HTML de la vue -->
  <template>
  <!-- formulaire HTML -->
  <b-form @submit.prevent="calculerImpot" class="mb-3">
    <!-- message sur 12 colonnes sur fond bleu -->
    <b-alert show variant="primary">
      <h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
    </b-alert>
    <!-- éléments du formulaire -->
    <!-- première ligne -->
    <b-form-group label="Etes-vous marié(e) ou pacsé(e) ?" label-cols="4">
      <!-- boutons radio sur 5 colonnes-->
      <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>
    <!-- deuxième ligne -->
    <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"
      />
      <!-- message d'erreur éventuel -->
      <b-form-invalid-feedback :state="enfantsValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
    </b-form-group>
    <!-- troisème ligne -->
    <b-form-group
      label="Salaire annuel"
      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"
      />
      <!-- message d'erreur éventuel -->
      <b-form-invalid-feedback :state="salaireValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
    </b-form-group>
    <!-- quatrième ligne, bouton [submit] sur 5 colonnes -->
    <b-col cols="5">
      <b-button type="submit" variant="primary" :disabled="formInvalide">Valider</b-button>
    </b-col>
  </b-form>
</template>
 
<!-- script -->
<script>
export default {
  // inner state
  data() {
    return {
      // married or not
      marié: "non",
      // number of children
      enfants: "",
      // annual salary
      salaire: ""
    };
  },
  // calculated internal state
  computed: {
    // form validation
    formInvalide() {
      return (
        // disabled salary
        !this.salaireValide ||
        // or disabled children
        !this.enfantsValide ||
        // or tax data not obtained
        !this.$métier.taxAdminData
      );
    },
    // salary validation
    salaireValide() {
      // must be numeric >=0
      return Boolean(this.salaire.match(/^\s*\d+\s*$/));
    },
    // child validation
    enfantsValide() {
      // must be numeric >=0
      return Boolean(this.enfants.match(/^\s*\d+\s*$/));
    }
  },
  // event manager
  methods: {
    calculerImpot() {
      // tax is calculated using the [business] layer
      const résultat = this.$métier.calculerImpot(
        this.marié,
        this.enfants,
        this.salaire
      );
      // eslint-disable-next-line
      console.log("résultat=", résultat);
      // complete the result
      résultat.marié = this.marié;
      résultat.enfants = this.enfants;
      résultat.salaire = this.salaire;
      // the [resultatObtenu] event is issued
      this.$emit("resultatObtenu", résultat);
    }
  }
};
</script>

Kommentare

  • Zeilen 4–51: das Bootstrap-Formular;
  • Zeilen 11–17: eine Gruppe von Optionsfeldern mit ihren Beschriftungen;
  • Zeilen 14–15: Das <b-form-radio>-Tag zeigt ein Optionsfeld an:
    • Zeile 14: Die [v-model]-Direktive stellt sicher, dass beim Klicken auf die Schaltfläche das Attribut [married] in Zeile 61 auf [yes] gesetzt wird (Attribut [value="yes"]);
    • Zeile 15: Die [v-model]-Direktive sorgt dafür, dass beim Klicken auf die Schaltfläche das Attribut [married] in Zeile 61 auf [no] gesetzt wird (Attribut [value="no"]);
  • Zeilen 19–29: der Abschnitt zur Eingabe der Anzahl der Kinder:
    • Zeile 24: Die Eingabe für die Anzahl der Kinder ist mit dem Attribut [children] in Zeile 63 verknüpft;
    • Zeile 25: Die Gültigkeit der Eingabe wird durch das berechnete Attribut [validChildren] in den Zeilen 87–89 überprüft;
    • Zeile 28: stellt sicher, dass eine Fehlermeldung angezeigt wird, wenn die Eingabe ungültig ist;
  • Zeilen 31–45: der Abschnitt zur Eingabe des Jahresgehalts:
    • Zeile 35: Zeigt direkt unter dem Eingabefeld eine Hilfemeldung an;
    • Zeile 41: Die Gehaltseingabe ist mit dem Attribut [salary] in Zeile 65 verknüpft;
    • Zeile 42: Die Gültigkeit der Eingabe wird durch das berechnete Attribut [validSalary] in den Zeilen 82–85 überprüft;
    • Zeile 45: Zeigt eine Fehlermeldung an, wenn die Eingabe ungültig ist;
  • Zeilen 48–50: eine Schaltfläche [submit]. Wenn diese Schaltfläche angeklickt wird oder wenn eine Eingabe mit der [Enter]-Taste bestätigt wird, wird die Methode [calculateTax] ausgeführt (Zeile 94);
    • Zeile 49: Der aktive/inaktive Status der Schaltfläche wird durch das berechnete Attribut [formInvalid] in den Zeilen 71–80 gesteuert;
  • Zeilen 71–80: Das Formular ist gültig, wenn:
    • die Anzahl der Kinder gültig ist;
    • das Gehalt gültig ist;
    • die Anwendung die Steuerverwaltungsdaten vom Server abgerufen hat, um die Steuer zu berechnen. Beachten Sie, dass diese Daten in der Eigenschaft [$métier.taxAdminData] gespeichert sind. Die Ansicht [FormCalculImpot] kann angezeigt werden, bevor diese Daten abgerufen wurden, da sie asynchron angefordert werden, während die Ansicht angezeigt wird. Hier stellen wir sicher, dass der Benutzer nicht auf die Schaltfläche [Validate] klicken kann, bis die Daten abgerufen wurden;
  • Zeilen 94–109: die Methode zur Steuerberechnung:
    • Zeilen 96–100: Die [business]-Schicht führt diese Berechnung durch. Dies ist eine synchrone Berechnung. Sobald die [taxAdminData] abgerufen wurde, muss die Client-[View] nicht mehr mit dem Server kommunizieren. Alles wird lokal erledigt. Wir erhalten ein Objekt [result] mit den Eigenschaften [tax, discount, surcharge, reduction, rate];
    • Zeilen 104–106: Die Eigenschaften [married, children, salary] werden dem Ergebnis hinzugefügt;
    • Zeile 108: Das Ergebnis wird über das Ereignis [resultatObtenu] an die übergeordnete Ansicht [CalculImpot] übergeben. Diese Ansicht ist für die Anzeige des Ergebnisses zuständig;

18.4.7. Die Ansicht [SimulationList]

Die Ansicht [SimulationList] zeigt die Liste der vom Benutzer durchgeführten Simulationen an:

Image

Der Code der Ansicht lautet wie folgt:


<!-- définition HTML de la vue -->
<template>
  <div>
    <!-- mise en page -->
    <Layout :left="true" :right="true">
      <!-- simulations dans colonne de droite -->
      <template slot="right">
        <template v-if="simulations.length==0">
          <!-- pas de simulations -->
          <b-alert show variant="primary">
            <h4>Votre liste de simulations est vide</h4>
          </b-alert>
        </template>
        <template v-if="simulations.length!=0">
          <!-- il y a des simulations -->
          <b-alert show variant="primary">
            <h4>Liste de vos simulations</h4>
          </b-alert>
          <!-- tableau des simulations -->
          <b-table 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 de navigation dans colonne de gauche -->
      <Menu slot="left" :options="options" />
    </Layout>
  </div>
</template>
 
<script>
  // imports
  import Layout from "./Layout";
  import Menu from "./Menu";
  export default {
    // composants
    components: {
      Layout,
      Menu
    },
    // état interne
    data() {
      return {
        // options du menu de navigation
        options: [
          {
            text: "Calcul de l'impôt",
            path: "/calcul-impot"
          },
          {
            text: "Fin de session",
            path: "/fin-session"
          }
        ],
        // paramètres de la table HTML
        fields: [
          { label: "#", key: "id" },
          { label: "Marié", key: "marié" },
          { label: "Nombre d'enfants", key: "enfants" },
          { label: "Salaire", key: "salaire" },
          { label: "Impôt", key: "impôt" },
          { label: "Décôte", key: "décôte" },
          { label: "Réduction", key: "réduction" },
          { label: "Surcôte", key: "surcôte" },
          { label: "", key: "action" }
        ]
      };
    },
    // état interne calculé
    computed: {
      // liste des simulations prise dans le store Vuex
      simulations() {
        return this.$store.state.simulations;
      }
    },
    // méthodes
    methods: {
      supprimerSimulation(index) {
        // eslint-disable-next-line
        console.log("supprimerSimulation", index);
        // suppression de la simulation n° [index]
        this.$store.commit("deleteSimulation", index);
      }
    }
  };
</script>

Kommentare

  • Zeile 5: Die Ansicht belegt bei gerouteten Ansichten beide Spalten des [Layout];
  • Zeilen 7–26: Die Simulationen werden in der rechten Spalte angezeigt;
  • Zeile 28: Das Navigationsmenü wird in der linken Spalte angezeigt;
  • Zeilen 8, 14, 20, 75: Die Simulationen stammen aus dem Store [Vuex] [$this.store];
  • Zeilen 8–13: Es wird eine Warnmeldung angezeigt, wenn die Liste der Simulationen leer ist;
  • Zeilen 14–25: Die HTML-Tabelle wird angezeigt, wenn die Liste der Simulationen nicht leer ist;
  • Zeilen 20–24: Die HTML-Tabelle wird durch ein <b-table>-Tag generiert;
    • Zeile 20: Die Simulationstabelle wird durch das berechnete Attribut [simulations] aus den Zeilen 74–76 bereitgestellt;
    • Zeile 20: Die HTML-Tabelle wird durch das berechnete Attribut [fields] in den Zeilen 58–69 konfiguriert. Zeile 67: Die Spalte mit dem Schlüssel [action] ist die letzte Spalte der HTML-Tabelle;
    • Zeilen 21–23: Vorlage für die letzte Spalte der HTML-Tabelle;
    • Zeile 22: Hier wird eine Schaltfläche platziert. Beim Klicken wird die Methode [deleteSimulation(data.index)] aufgerufen, wobei [data] die aktuelle Zeile darstellt (Zeile 21). [data.index] steht für die Nummer dieser Zeile in der Liste der angezeigten Zeilen;
  • Zeile 28: Erstellung des Navigationsmenüs. Seine Optionen werden durch das Attribut [options] in den Zeilen 47–56 bereitgestellt;
  • Zeilen 80–85: Die Methode, die auf einen Klick auf den Link [Delete] auf der HTML-Seite reagiert;
    • Zeile 84: Die Methode [deleteSimulation] des [Vuex]-Speichers wird aufgerufen (siehe Abschnitt |vuejs-15|);

18.5. Ausführen des Projekts

Image

Sie müssen außerdem den [Laragon]-Server starten (siehe Dokument |https://tahe.developpez.com/tutoriels-cours/php7|), damit der Steuerberechnungsserver online ist.

18.6. Bereitstellung der Anwendung auf einem lokalen Server

Derzeit ist unser [Vue]-Client auf einem Testserver unter der URL [http://localhost:8080] bereitgestellt. Wir werden ihn auf dem [Laragon]-Server unter der URL [http://localhost:80] bereitstellen. Dazu sind mehrere Schritte erforderlich.

Schritt 1

Zunächst stellen wir sicher, dass der [Vue]-Client auf dem Testserver unter der URL [http://localhost:8080/client-vuejs-impot/] bereitgestellt ist.

Wir erstellen eine Datei [vue.config.js] im Stammverzeichnis unseres aktuellen [VSCode]-Projekts:

Image

Die Datei [vue.config.js] [1] enthält folgenden Inhalt:


// vue.config.js
module.exports = {
  // l'URL de service du client [vuejs] du serveur de calcul de l'impôt
  publicPath: '/client-vuejs-impot/'
}

Außerdem müssen wir die Routing-Datei [router.js] [2] ändern:


// imports
import Vue from 'vue'
import VueRouter from 'vue-router'
// les vues
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'
 
// plugin de routage
Vue.use(VueRouter)
 
// les routes de l'application
const routes = [
  // authentification
  {
    path: '/', name: 'authentification', component: Authentification
  },
  // calcul de l'impôt
  {
    path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
  },
  // liste des simulations
  {
    path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations
  },
  // fin de session
  {
    path: '/fin-session', name: 'finSession', component: Authentification
  }
]
 
// le routeur
const router = new VueRouter({
  // les routes
  routes,
  // le mode d'affichage des routes dans le navigateur
  mode: 'history',
  // l'URL de base de l'application
  base: '/client-vuejs-impot/'
})
 
// export du router
export default router
  • Zeile 39: Wir teilen dem Router mit, dass die Pfade der in den Zeilen 13–30 definierten Routen relativ zu dem in Zeile 39 definierten Pfad sind. So wird beispielsweise der Pfad in Zeile 20 [/calcul-impot] zu [/client-vuejs-impot/calcul-impot];

Anschließend können wir das Projekt [vuejs-20] erneut testen, um die Änderung der Pfade in der Anwendung zu überprüfen:

Image

Schritt 2

Wir erstellen nun die Produktionsversion des [vuejs-20]-Projekts:

Image

  • In [1-2] konfigurieren wir die [build]-Aufgabe [2] in der Datei [package.json] [1];
  • In [3-5] führen wir diese Aufgabe aus. Dadurch wird die Produktionsversion des [vuejs-20]-Projekts erstellt;

Die [build]-Aufgabe wird in einem [VSCode]-Terminal ausgeführt:

Image

Image

  • In [3-6] weisen Warnungen darauf hin, dass der generierte Code zu groß ist und aufgeteilt werden sollte [8]. Dies hängt mit der Optimierung der Code-Architektur zusammen, auf die wir hier nicht näher eingehen werden;
  • In [7] wird uns mitgeteilt, dass der Ordner [dist] die generierte Produktionsversion enthält:

Image

  • In [3] ist die Datei [index.html] diejenige, die verwendet wird, wenn die URL [https://localhost:80/client-vue-js-impot/] aufgerufen wird;

Hier haben wir eine statische Website, die auf jedem Server bereitgestellt werden kann. Wir werden sie auf dem lokalen Laragon-Server bereitstellen (siehe Dokument |https://tahe.developpez.com/tutoriels-cours/php7|). Der Ordner [dist] [2] wird in den Ordner [<laragon>/www] [4] kopiert, wobei <laragon> der Installationsordner des Laragon-Servers ist. Wir benennen diesen Ordner in [client-vuejs-import] [5] um, da wir die Produktionsversion so konfiguriert haben, dass sie unter der URL [/client-vuejs-import/] läuft.

Schritt 3

Wir fügen die folgende [.htaccess]-Datei zum soeben erstellten Ordner [client-vuejs-import] hinzu:


<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

Diese Datei ist eine Konfigurationsdatei für den Apache-Webserver. Wenn wir sie nicht einbinden und die URL [https://localhost/client-vuejs-impot/calcul-impot] direkt aufrufen, ohne zuvor die URL [https://localhost/client-vuejs-impot/] zu durchlaufen, erhalten wir einen 404-Fehler. Mit dieser Datei wird die Ansicht [CalculImpot] erfolgreich angezeigt.

Sobald das erledigt ist, starten Sie den Laragon-Server, falls Sie dies noch nicht getan haben, und navigieren Sie zur URL [https://localhost/client-vuejs-impot/]:

Image

Leser sind herzlich eingeladen, die Produktionsversion unserer Anwendung zu testen.

Wir können den Steuerberechnungsserver in einem Punkt ändern: die CORS-Header, die er systematisch an seine Clients sendet. Dies war für die Client-Version erforderlich, die unter der Domain [localhost:8080] lief. Da nun sowohl Client als auch Server unter der Domain [localhost:80] laufen, werden die CORS-Header nicht mehr benötigt.

Wir ändern die Datei [config.json] für Version 14 des Servers:

Image

  • In [4] legen wir fest, dass CORS-Anfragen nun abgelehnt werden;

Speichern wir diese Änderung und rufen wir die URL [https://localhost/client-vuejs-impot/] erneut auf. Sie sollte weiterhin funktionieren.

18.7. Umgang mit manuellen URLs

Anstatt die Links im Navigationsmenü zu verwenden, möchte der Benutzer die URLs der Anwendung möglicherweise manuell in die Adressleiste des Browsers eingeben. Rufen wir beispielsweise die URL [https://client-vuejs-impot/calcul-impot] auf, ohne die Authentifizierungsseite zu durchlaufen. Ein Hacker würde dies sicherlich versuchen. Wir erhalten die folgende Ansicht:

Image

Wir erhalten tatsächlich die Ansicht zur Steuerberechnung. Versuchen wir nun, die Eingabefelder auszufüllen und abzuschicken:

Image

Wir stellen dann fest, dass die Schaltfläche [1] [Absenden] auch dann deaktiviert bleibt, wenn die Eingaben korrekt sind. Sehen wir uns den Code für die Ansicht [FormCalculImpot] an:


<b-col cols="5">
      <b-button type="submit" variant="primary" :disabled="formInvalide">Valider</b-button>
</b-col>

In Zeile 2 sehen wir, dass der aktive/inaktive Status von der Eigenschaft [formInvalide] abhängt. Dies ist die folgende berechnete Eigenschaft:


formInvalide() {
      return (
        // salaire invalide
        !this.salaireValide ||
        // ou enfants invalide
        !this.enfantsValide ||
        // ou données fiscales pas obtenues
        !this.$métier.taxAdminData
      );
},

Zeile 8 zeigt, dass für die Gültigkeit des Formulars Steuerdaten abgerufen worden sein müssen. Diese Daten werden jedoch während der Validierung der Ansicht [Authentication] abgerufen, die der Benutzer „übersprungen“ hat. Daher kann er das Formular nicht absenden. Wäre dies möglich gewesen, hätte er eine Fehlermeldung vom Server erhalten, die darauf hinweist, dass er nicht authentifiziert ist. Die Validierung muss immer serverseitig erfolgen. Eine browserbasierte Validierung kann immer umgangen werden. Dazu reicht ein Client wie [Postman], der Rohanfragen an den Server sendet.

Rufen wir nun die URL [https://localhost/client-vuejs-impot/liste-des-simulations] auf. Wir erhalten die folgende Ansicht:

Image

Nun die URL [https://localhost/client-vuejs-impot/fin-session]. Wir erhalten folgende Ansicht:

Image

Nun eine Ansicht, die nicht existiert [https://localhost/client-vuejs-impot/abcd]:

Image

Unsere Anwendung kommt mit manuell eingegebenen URLs recht gut zurecht. Wenn diese aufgerufen werden, erkennt der Anwendungsrouter dies. Es ist daher möglich, einzugreifen, bevor die Ansicht endgültig angezeigt wird. Wir werden uns dies im Projekt [vuejs-21] ansehen.

Ein weiterer zu berücksichtigender Punkt ist der folgende. Stellen wir uns vor, der Benutzer hat einige Simulationen gemäß den Regeln durchgeführt:

Image

Aktualisieren wir nun die Seite durch Drücken der F5-Taste:

Image

Wir haben etwas getan, was nicht empfohlen wird: die URL manuell eingegeben (F5 zu drücken ist im Grunde dasselbe). Infolgedessen sind unsere Simulationen verloren gegangen.

Das folgende Projekt [vuejs-21] zielt darauf ab, zwei Verbesserungen einzuführen:

  • die vom Benutzer eingegebenen URLs zu validieren;
  • den Zustand der Anwendung beibehalten, auch wenn der Benutzer eine URL eingibt. Oben sehen wir, dass wir die Liste der Simulationen verloren haben;