Skip to content

18. Cliente Vue.js do servidor de cálculo de impostos

18.1. Architecture

Vamos implementar uma aplicação cliente/servidor com a seguinte arquitetura:

Image

O servidor de cálculo de impostos será a versão 14 desenvolvida no documento |https://tahe.developpez.com/tutoriels-cours/php7|

18.2. As visualizações da aplicação

As visualizações da aplicação [vuejs-10] são as da versão 13 do documento |https://tahe.developpez.com/tutoriels-cours/php7| do servidor de cálculo de impostos quando este é utilizado no modo HTML. No entanto, na aplicação em questão, essas visualizações serão geradas pelo cliente JavaScript e não pelo servidor PHP.

A primeira visualização é a de autenticação:

Image

A segunda visualização é a de cálculo do imposto:

Image

A terceira visualização é aquela que exibe a lista de simulações feitas pelo usuário:

Image

A tela acima mostra que é possível excluir a simulação nº 1. Obtém-se, então, a seguinte tela:

Image

Se excluirmos agora a última simulação, obtemos a seguinte nova tela:

Image

18.3. Elementos do projeto [vuejs-20]

A estrutura do projeto [vuejs-20] é a seguinte:

Image

Os elementos do projeto são os seguintes:

  • [assets/logo.jpg]: o logotipo do projeto;
  • [couches]: as camadas [métier] e [dao] do aplicativo;
  • [plugins]: os plug-ins do aplicativo;
  • [views]: as visualizações do aplicativo;
  • [config.js]: configura o aplicativo;
  • [router.js]: define o roteamento do aplicativo;
  • [store.js]: a loja de [Vuex];
  • [main.js]: o script principal do aplicativo;

18.3.1. As camadas [métier] e [dao]

18.3.1.1. A camada [dao]

A camada [dao] é implementada pela classe [Dao] do parágrafo |vuejs-10|

18.3.1.2. A camada [métier]

A camada [métier] é implementada pela classe [Métier] do documento |https://tahe.developpez.com/tutoriels-cours/php7|. Foi adicionado o seguinte método [setTaxAdminData]:


// construtor
  constructor(taxAdmindata) {
    // this.taxAdminData: dados da administração fiscal
    this.taxAdminData = taxAdmindata;
  }

  // setter
  setTaxAdminData(taxAdmindata) {
    // this.taxAdminData: dados da administração fiscal
    this.taxAdminData = taxAdmindata;
}

O método [setTaxAdminData] realiza a mesma função que o construtor. Sua presença permite a seguinte sequência:

  1. instanciar a classe [Métier] com uma instrução [métier=new Métier()] quando se deseja instanciar a classe, mas ainda não se possui o dado [taxAdminData];
  2. em seguida, preencher posteriormente sua propriedade [taxAdminData] por meio de uma operação [métier.setTaxAdminData(taxAdmindata)];

18.3.2. O arquivo de configuração [config]

O arquivo [config.js] é o seguinte:


// uso da biblioteca [axios]
const axios = require('axios');
// tempo limite das solicitações HTTP
axios.defaults.timeout = 2000;
// a base de dados do servidor de cálculo de impostos URL
// o esquema [https] causa problemas no Firefox porque o servidor de cálculo
// de cálculo de impostos envia um certificado autoassinado. Funciona bem com o Chrome e o Edge. O Safari não foi testado.
axios.defaults.baseURL = 'https://localhost/php7/scripts-web/impots/version-14';
// vamos usar cookies
axios.defaults.withCredentials = true;

// exportação da configuração
export default {
  axios: axios
}

Essa configuração é a da biblioteca [axios] que a camada [dao] utiliza para realizar suas consultas HTTP. Observe-se, na linha 8, que o servidor opera na porta segura [https].

18.3.3. Os plug-ins

Os plug-ins [pluginDao, pluginMétier, pluginConfig] têm como objetivo criar três novas propriedades para a função/classe [Vue]:

  • [$dao]: terá como valor uma instância da classe [Dao];
  • [$métier]: terá como valor uma instância da classe [Métier];
  • [$config]: terá como valor o objeto exportado pelo arquivo de configuração [config];

[pluginDao]


export default {
  install(Vue, dao) {
    // adiciona uma propriedade [$dao] à classe Vue
    Object.defineProperty(Vue.prototype, '$dao', {
      // quando Vue.$dao é referenciado, retornamos o segundo parâmetro [dao]
      get: () => dao,
    })
  }
}

[pluginMétier]

export default {
  install(Vue, métier) {
    // adiciona uma propriedade [$métier] à classe Vue
    Object.defineProperty(Vue.prototype, '$métier', {
      // quando Vue.$métier é referenciado, o segundo parâmetro é definido como [métier]
      get: () => métier,
    })
  }
}

[pluginConfig]


export default {
  install(Vue, config) {
    // adiciona uma propriedade [$config] à classe Vue
    Object.defineProperty(Vue.prototype, '$config', {
      // quando Vue.$config é referenciado, o segundo parâmetro passa a ser [config]
      get: () => config,
    })
  }
}

18.3.4. A persiana [Vuex]

O store de [Vuex] é implementado pelo seguinte arquivo [store]:


// plug-in Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);

// store do Vuex
const store = new Vuex.Store({
  state: {
    // a tabela de simulações
    simulations: [],
    // o número da última simulação
    idSimulation: 0
  },
  mutations: {
    // exclusão da linha nº índice
    deleteSimulation(state, index) {
      // eslint-disable-next-line no-console
      console.log("mutation deleteSimulation");
      // a linha nº [index] é excluída
      state.simulations.splice(index, 1);
      // eslint-disable-next-line no-console
      console.log("store simulations", state.simulations);
    },
    // adição de uma simulação
    addSimulation(state, simulation) {
      // eslint-disable-next-line no-console
      console.log("mutation addSimulation");
      // nº da simulação
      state.idSimulation++;
      simulation.id = state.idSimulation;
      // adicionamos a simulação à tabela de simulações
      state.simulations.push(simulation);
    },
    // limpeza do state
    clear(state) {
      state.simulations = [];
      state.idSimulation = 1;
    }
  }
});
// exportação do objeto [store]
export default store;

Comentários

  • linhas 2-4: o plugin [Vuex] está integrado ao framework [Vue];
  • linhas 8-13: inserimos no armazenamento do [Vuex] os seguintes elementos:
    • [simulations]: a lista das simulações realizadas pelo usuário;
    • [idSimulation]: o número da última simulação realizada pelo usuário;

Vale lembrar que o store será compartilhado entre as visualizações e que seu conteúdo é reativo: quando é alterado, as visualizações que o utilizam são atualizadas automaticamente. Em nosso aplicativo, apenas o elemento [simulations] precisa ser reativo, e não o elemento [idSimulation]. Deixamos esse elemento no store por conveniência;

  • linhas 14-40: as alterações permitidas no objeto [state] das linhas 8-13. Vale lembrar que essas sempre recebem o objeto [state] das linhas 8-13 como primeiro parâmetro;
    • linha 16: a modificação [deleteSimulation] permite excluir uma simulação cujo número é [index];
    • linha 25: a alteração [addSimulation] permite adicionar uma nova simulação à tabela de simulações;
    • linha 35: a modificação [clear] permite reinicializar o objeto [state] das linhas 8 a 13;

18.3.5. O arquivo de roteamento [router]

O arquivo de roteamento é o seguinte:


// importações
import Vue from 'vue'
import VueRouter from 'vue-router'
// as visualizações
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'

// plug-in de roteamento
Vue.use(VueRouter)

// rotas do aplicativo
const routes = [
  // autenticação
  {
    path: '/', name: 'authentification', component: Authentification
  },
  // cálculo de impostos
  {
    path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
  },
  // lista de simulações
  {
    path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations
  },
  // fim da sessão
  {
    path: '/fin-session', name: 'finSession', component: Authentification
  }
]

// o roteador
const router = new VueRouter({
  // as rotas
  routes,
  // modo de exibição das rotas no navegador
  mode: 'history',
})

// exportação do roteador
export default router

Comentários

  • linha 16: ao iniciar o aplicativo, é exibida a visualização [Authentification], pois seu URL é a raiz [/];
  • linha 20: a visualização [CalculImpot] é exibida quando a visualização URL [/calcul-impot] é solicitada;
  • linha 24: a visualização [ListeSimulations] é exibida quando a visualização URL [/liste-des-simualtions] é solicitada;
  • linha 28: a vista [Authentification] é exibida quando a vista URL [/fin-session] é solicitada;
  • linhas 33-38: um objeto [router] é criado com essas rotas (linha 35) e o modo [history] (linha 37) de gerenciamento do URL;
  • linha 41: esse roteador é exportado;

18.3.6. O script principal [main.js]

O script [main.js] é o seguinte:


// importações
import Vue from 'vue'

// tela principal
import Main from './views/Main.vue'

// plug-in [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'

// roteador
import router from './router'

// plug-in [config]
import config from './config';
import pluginConfig from './plugins/pluginConfig'
Vue.use(pluginConfig, config)

// instanciação da camada [dao]
import Dao from './couches/Dao';
const dao = new Dao(config.axios);

// plug-in [dao]
import pluginDao from './plugins/pluginDao'
Vue.use(pluginDao, dao)

// instanciação da camada [métier]
import Métier from './couches/Métier';
const métier = new Métier();

// plug-in [métier]
import pluginMétier from './plugins/pluginMétier'
Vue.use(pluginMétier, métier)

// store Vuex
import store from './store'

// inicialização do UI
new Vue({
  el: '#aplicativo',
  // o roteador
  router: router,
  // a persiana Vuex
  store: store,
  // a tela principal
  render: h => h(Main),
})

Observe os seguintes pontos:

  • linhas 18-21: o objeto exportado pelo script [./config] estará disponível no atributo [Vue.$config] e, portanto, acessível a todas as visualizações do aplicativo. Isso era desnecessário neste caso, pois o objeto [config] é utilizado apenas pelo script [main] (linha 25). No entanto, é comum que a configuração seja necessária para várias visualizações. Por isso, optamos aqui por manter o princípio de disponibilizá-la em um atributo da visualização;
  • linhas 24-25: instanciação da camada [dao]. A classe [Dao] é importada na linha 24 e, em seguida, instanciada na linha 25. Seu construtor aceita como único parâmetro o objeto [axios], uma propriedade de configuração;
  • linhas 27-29: a camada [dao] é disponibilizada no atributo [$dao] de todas as visualizações;
  • linhas 31-37: repete-se a mesma sequência para a camada [métier]. O construtor da classe [Métier] tem como parâmetro [taxAdminData], que representa os dados da administração fiscal. Ainda não dispomos desses dados. O objeto [métier] da linha 33 deverá, portanto, ser preenchido posteriormente;
  • linha 40: importa-se o store [Vuex];
  • linhas 43-51: instanciamos a visão principal [Main] (linhas 5 e 50), passando-lhe dois parâmetros:
    • linha 46: o roteador [router] definido na linha 16;
    • linha 48: o store [Vuex] [store] definido na linha 40;
    • em ambos os casos, o nome da propriedade está à esquerda e seu valor à direita. Os nomes das propriedades [router, store] são definidos pelos frameworks [vue-router] e [vuex]. Os valores associados, por sua vez, podem ser quaisquer;

18.4. As visualizações do aplicativo

18.4.1. A vista principal [Main]

O código da vista principal [Main] é o seguinte:


<!-- definição da tela 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>
      <!-- erro na solicitação HTTP -->
      <b-alert
        show
        variant="danger"
        v-if="showError"
      >L'erreur suivante s'est produite : {{error.message}}</b-alert>
      <!-- visualização atual -->
      <router-view v-if="showView" @loading="mShowLoading" @error="mShowError" />
      <!-- carregando -->
      <b-alert show v-if="showLoading" variant="light">
        <strong>Requête au serveur de calcul d'impôt en cours...</strong>
        <div class="spinner-border ml-auto" role="status" aria-hidden="true"></div>
      </b-alert>
    </b-card>
  </div>
</template>

<script>
export default {
  // nome
  name: "app",
  // estado interno
  data() {
    return {
      // verifica o alerta de espera
      showLoading: false,
      // controla o alerta de erro
      showError: false,
      // controla a exibição da visão de roteamento atual
      showView: true,
      // uma mensagem de erro
      error: ""
    };
  },
  // gerenciadores de eventos
  methods: {
    // erro na solicitação assíncrona
    mShowError(error) {
      // eslint-disable-next-line
      console.log("Main evt error");
      // exibe a mensagem de erro
      this.error = error;
      this.showError = true;
      // ocultamos a visualização roteada
      this.showView = false;
      // ocultar a mensagem de espera
      this.showLoading = false;
    },
    // exibir ou não um ícone de espera
    mShowLoading(value) {
      // eslint-disable-next-line
      console.log("Main evt showLoading");
      // exibir ou não o alerta de espera
      this.showLoading = value;
    }
  }
};
</script>

Comentários

  • A visualização [Main] garante o layout da visualização encaminhada e exibida na linha 23:

Image

  • as linhas 5 a 15 exibem a área 1;
  • a linha 23 exibe a visão encaminhada [2];
  • linhas 16 a 19: um alerta exibido apenas em caso de erro de comunicação com o servidor de cálculo de impostos;
  • linhas 25 a 28: uma mensagem de espera exibida a cada solicitação HTTP enviada ao servidor;
  • todas as visualizações serão exibidas com esse layout, uma vez que cada visualização roteada é exibida pelas linhas 20-24. A visualização [Main] serve para abstrair o que pode ser compartilhado pelas diferentes visualizações;
  • linha 23: cada visualização encaminhada pode emitir três eventos:
    • [loading]: uma consulta HTTP foi iniciada. É necessário exibir a mensagem de espera pela resposta;
    • [error]: a solicitação HTTP terminou com um erro. É preciso exibir a mensagem de erro e ocultar a visualização roteada;
  • linhas 38-49: o estado da visualização:
    • linha 41: [showLoading] controla a exibição da mensagem de espera pelo término de uma solicitação HTTP (linha 25);
    • linha 43: [showError] controla a exibição da mensagem de erro de uma consulta HTTP (linhas 17-21);
    • linha 45: [showView] controla a exibição da visualização encaminhada (linha 23);
  • linhas 53-63: o método [mShowError] gerencia o evento [error] emitido pela visualização roteada (linha 23);
  • linhas 65-70: o método [mShowLoading] gerencia o evento [loading] emitido pela vista roteada (linha 23);
  • linha 23: devemos prestar atenção aos eventos [error] e [loading]. Eles só são interceptados se a visualização roteada estiver exibida ([showView=true]). É por isso que a visualização roteada é exibida inicialmente (linha 45). Ela só é ocultada em caso de erro (linha 60). Para evitar esse problema, poderia-se ter usado a diretiva [v-show] em vez de [v-if]. A diferença entre essas duas diretivas é a seguinte:
    • [v-if=’false’] oculta o bloco controlado, removendo-o do código global HTML. Os eventos da visualização roteada não podem mais ser interceptados;
    • [v-show=’false’] oculta o bloco controlado ao alterar seu CSS, mas o código do bloco permanece presente no HTML global e, assim, pode interceptar os eventos da visualização roteada;

18.4.2. A visualização de layout [Layout]

O código da visualização [Layout] é o seguinte:


<!-- definição HTML do layout da visualização direcionada -->
<template>
  <!-- linha -->
  <div>
    <b-row>
      <!-- área de três colunas à esquerda -->
      <b-col cols="3" v-if="left">
        <slot name="left" />
      </b-col>
      <!-- área de nove colunas à direita -->
      <b-col cols="9" v-if="right">
        <slot name="right" />
      </b-col>
    </b-row>
  </div>
</template>

<script>
  export default {
    // parâmetros da visualização
    props: {
      // controla a coluna da esquerda
      left: {
        type: Boolean
      },
      // controla a coluna à direita
      right: {
        type: Boolean
      }
    }
  };
</script>

Comentários

  • A visualização [Layout] permite dividir a visualização roteada em duas áreas:
    • uma área com 3 colunas Bootstrap à esquerda (linhas 7-9). Essa área abrigará o menu de navegação, quando houver um;
    • uma área de 9 colunas à direita (linhas 11-13). Essa área abrigará as informações fornecidas pela vista roteada;

18.4.3. A visualização [Authentification]

A visualização de autenticação é a seguinte:

Image

Essa visualização é obtida a partir da [Layout], removendo a coluna da esquerda para exibir apenas a coluna da direita.

Seu código é o seguinte:


<!-- definição HTML da visualização -->
<template>
  <Layout :left="false" :right="true">
    <template slot="right">
      <!-- formulário HTML — os valores são enviados por meio da ação [authentifier-utilisateur] -->
      <b-form @submit.prevent="login">
        <!-- título -->
        <b-alert show variant="primary">
          <h4>Bienvenue. Veuillez vous authentifier pour vous connecter</h4>
        </b-alert>
        <!-- 1ª linha -->
        <b-form-group label="Nom d'utilisateur" label-for="user" label-cols="3">
          <!-- campo de entrada do usuário -->
          <b-col cols="6">
            <b-form-input type="text" id="user" placeholder="Nom d'utilisateur" v-model="user" />
          </b-col>
        </b-form-group>
        <!-- 2ª linha -->
        <b-form-group label="Mot de passe" label-for="password" label-cols="3">
          <!-- campo de digitação de senha -->
          <b-col cols="6">
            <b-input type="password" id="password" placeholder="Mot de passe" v-model="password" />
          </b-col>
        </b-form-group>
        <!-- 3ª linha -->
        <b-alert
          show
          variant="danger"
          v-if="showError"
          class="mt-3"
        >L'erreur suivante s'est produite : {{message}}</b-alert>
        <!-- botão do tipo [submit] na terceira linha -->
        <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>

<!-- dinâmica da visualização -->
<script>
import Layout from "./Layout";
export default {
  // estado do componente
  data() {
    return {
      // usuário
      user: "",
      // sua senha
      password: "",
      // controla a exibição de uma mensagem de erro
      showError: false,
      // a mensagem de erro
      message: "",
      // sessão iniciada
      sessionStarted: false
    };
  },

  // componentes utilizados
  components: {
    Layout
  },

  // propriedades calculadas
  computed: {
    // entradas válidas
    valid() {
      return this.user && this.password && this.sessionStarted;
    }
  },

  // gerenciadores de eventos
  methods: {
    // ----------- autenticação
    async login() {
      try {
        // início da espera
        this.$emit("loading", true);
        // autenticação bloqueante no servidor
        const response = await this.$dao.authentifierUtilisateur(
          this.user,
          this.password
        );
        // fim do carregamento
        this.$emit("loading", false);
        // análise da resposta
        if (response.état != 200) {
          // exibição do erro
          this.message = response.réponse;
          this.showError = true;
          return;
        }
        // sem erro
        this.showError = false;
        // --------- agora são solicitados os dados da administração fiscal
        // início da espera
        this.$emit("loading", true);
        // solicitação em espera no servidor
        const response2 = await this.$dao.getAdminData();
        // fim do carregamento
        this.$emit("loading", false);
        // análise da resposta
        if (response2.état != 1000) {
          // exibição do erro
          this.message = response2.réponse;
          this.showError = true;
          return;
        }
        // sem erro
        this.showError = false;
        // os dados recebidos são armazenados na camada [métier]
        this.$métier.setTaxAdminData(response2.réponse);
        // passa-se para a tela de cálculo do imposto
        this.$router.push({ name: "calculImpot" });
      } catch (error) {
        // o erro é repassado ao componente principal
        this.$emit("error", error);
      }
    }
  },
  // ciclo de vida: o componente acaba de ser criado
  created() {
    // eslint-disable-next-line
    console.log("authentification", "created");
    // iniciando uma sessão jSON com o servidor
    // início da espera
    this.$emit("loading", true);
    // inicializando a sessão com o servidor — solicitação assíncrona
    // utiliza-se a promessa retornada pelos métodos da camada [dao]
    this.$dao
      // inicializando uma sessão jSON
      .initSession()
      // a resposta foi obtida
      .then(response => {
        // fim da espera
        this.$emit("loading", false);
        // análise da resposta
        if (response.état != 700) {
          // exibindo o erro
          this.message = response.réponse;
          this.showError = true;
          return;
        }
        // a sessão foi iniciada
        this.sessionStarted = true;
      })
      // em caso de erro
      .catch(error => {
        // o erro é reportado à tela [Main]
        this.$emit("error", error);
      });
  }
};
</script>

Comentários

  • linha 3: a visualização [Authentification] utiliza apenas a coluna da direita do [Layout] (linhas 3 e 4);
  • linhas 6-38: o formulário Bootstrap que gera a área 1 da captura de tela acima;
  • linha 6: o evento [@submit] ocorre quando o usuário clica no botão do tipo [submit] da linha 35. O modificador [prevent] determina que a página não seja recarregada durante o [submit]. Também seria possível escrever:
    • uma tag <b-form> sem gerenciamento do evento [submit];
    • uma tag <b-button> com o evento [@click=’login’] e sem o atributo [type=’submit’];

Isso também funciona. A vantagem da solução escolhida é que o envio ocorre não apenas com um clique no botão [Valider], mas também ao pressionar a tecla de validação ([Entrée]) nos campos de preenchimento. Portanto, foi por uma questão de conveniência para o usuário que a solução [<b-form @submit.prevent="login">] foi escolhida aqui;

  • linhas 33-37: um alerta que aparece quando o servidor rejeita as credenciais inseridas pelo usuário:

Image

  • linha 35: o botão [Valider] nem sempre está ativo. Seu estado depende do atributo calculado [valid] das linhas 71-73. O atributo [valid] é verdadeiro se:
    • houver algum valor nos campos [user, password] do formulário;
    • a sessão jSON tiver sido iniciada. Inicialmente, essa sessão não está iniciada (linha 59) e, portanto, o botão [Valider] está inativo.
  • linhas 49-60: o estado da visualização;
    • [user] representa a entrada do usuário no campo [user] (linhas 12-17) do formulário. A diretiva [v-model] da linha 15 estabelece uma ligação bidirecional entre a entrada do usuário e o atributo [user] da visualização;
    • [password] representa a entrada do usuário no campo [password] (linhas 19-24) do formulário. A diretiva [v-model] da linha 22 estabelece uma ligação bidirecional entre a entrada do usuário e o atributo [password] da visualização;
    • [showError] controla (linha 29) a exibição do alerta das linhas 26 a 31;
    • [message] é a mensagem de erro (linha 31) a ser exibida no alerta das linhas 26 a 31;
    • [sessionStarted] indica se a sessão jSON com o servidor foi iniciada ou não. Inicialmente, esse atributo tem o valor [false] (linha 59). A sessão jSON com o servidor é inicializada no evento [created] do ciclo de vida da visualização, linhas 126-156. Se o servidor responder positivamente, o atributo [sessionStarted] é alterado para [true] (linha 149);
  • linhas 126-156: a função [created] é executada quando a visualização [Authentification] foi criada (não necessariamente exibida ainda). Em segundo plano, inicializa-se então uma sessão jSON com o servidor. Sabe-se que essa é a primeira ação a ser realizada com o servidor de cálculo de impostos. Para isso, utiliza-se a camada [dao] do aplicativo (linha 134). Todos os métodos dessa camada são assíncronos. Aqui, utiliza-se a promessa (Promise) retornada pelo método [$dao.initSession], que inicializa a sessão jSON com o servidor.
  • linhas 138-150: o código executado quando o servidor retorna sua resposta sem erros;
  • linha 142: verifica-se a propriedade [état] da resposta. Ela deve ter o valor [700] para que a operação seja bem-sucedida. Caso contrário, ocorreu um erro cuja causa é indicada na propriedade [response.réponse] (linha 144). Em seguida, exibe-se a mensagem de erro da visualização (linha 145);
  • linha 149: observa-se que a sessão jSON foi iniciada;
  • linhas 152-155: o código executado em caso de erro. Esse erro é reportado à visualização pai [Main], que
    • exibirá o erro;
    • ocultará a mensagem de espera;
    • ocultará a visualização encaminhada, a visualização [Autentification];
  • linhas 79-124: o método [login] processa o clique no botão [Valider];
  • linha 79: o método foi prefixado com a palavra-chave [async] para permitir o uso da palavra-chave [await], linhas 84 e 103;
  • linhas 84-87: chamada bloqueante ao método [$dao.authentifierUtilisateur(user, password)]. Teríamos podido usar uma promessa [Promise], como foi feito na função [created]. Queríamos variar os estilos. Não há risco de bloquear o usuário, pois colocamos um [timeout] de 2 segundos em todas as solicitações HTTP. Ele não vai esperar muito tempo. Além disso, ele não pode fazer nada enquanto o servidor não enviar sua resposta, pois, nesse caso, o botão [Valider] permanece inativo;
  • linha 91: o servidor de cálculo de impostos envia respostas jSON, todas com a estrutura [{‘action’:action, ‘état’:val, ‘réponse’:réponse}]. A autenticação foi bem-sucedida se [état==200]. Caso contrário, é exibida uma mensagem de erro, linhas 93-94;
  • linha 98: oculta-se uma eventual mensagem de erro de uma operação anterior;
  • linhas 99-116: agora são solicitados ao servidor os dados da administração fiscal que permitem o cálculo do imposto. Em [this.$métier], temos uma instância da classe [Métier] que, por enquanto, não pode realizar nenhuma ação, pois não possui esses dados;
  • linha 103: os dados da administração fiscal são solicitados ao servidor por meio de uma operação bloqueante;
  • linhas 107-112: a resposta do servidor é analisada. Ela deve ter um valor de estado igual a 1000; caso contrário, significa que ocorreu um erro. Nesse último caso, exibe-se a mensagem de erro (linhas 109-110);
  • linhas 113-118: caso a operação seja bem-sucedida, nós:
    • ocultamos a mensagem de erro, linha 114;
    • transmitimos os dados da administração fiscal para a camada [métier] (linha 116);
    • exibe-se a visualização [CalculImpot], linha 118. Vale lembrar que [this.$router] designa o roteador da aplicação. O método [push] permite definir a próxima visualização roteada. Aqui, ela é identificada por seu atributo [name]. Também seria possível identificá-la por seu atributo [path]. Essas informações estão no arquivo de roteamento:

// cálculo do imposto
  {
    path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
  },
  • linhas 119-122: o [catch] é acionado quando uma das duas solicitações HTTP falha (servidor indisponível, tempo limite excedido, etc.). O erro é então reportado à visualização pai [Main], que o exibirá, ocultará a mensagem de espera e a visualização [Authentification];

18.4.4. A visualização [CalculImpot]

A visualização [CalculImpot] é a seguinte:

Image

  • [1]: um menu de navegação ocupa a coluna esquerda da visualização direcionada;
  • [2]: o formulário de cálculo do imposto ocupa a coluna direita da visualização direcionada;

O código da visualização [CalculImpot] é o seguinte:


<!-- definição HTML da visualização -->
<template>
  <div>
    <Layout :left="true" :right="true">
      <!-- formulário de cálculo do imposto à direita -->
      <FormCalculImpot slot="right" @resultatObtenu="handleResultatObtenu" />
      <!-- menu de navegação à esquerda -->
      <Menu slot="left" :options="options" />
    </Layout>
    <!-- área de exibição dos resultados do cálculo do imposto abaixo do formulário -->
    <b-row v-if="résultatObtenu" class="mt-3">
      <!-- área vazia com três colunas -->
      <b-col cols="3" />
      <!-- área com nove colunas -->
      <b-col cols="9">
        <b-alert show variant="success">
          <span v-html="résultat"></span>
        </b-alert>
      </b-col>
    </b-row>
  </div>
</template>

<script>
// importações
import FormCalculImpot from "./FormCalculImpot";
import Menu from "./Menu";
import Layout from "./Layout";

export default {
  // relatório interno
  data() {
    return {
      // opções do menu
      options: [
        {
          text: "Liste des simulations",
          path: "/liste-des-simulations"
        },
        {
          text: "Fin de session",
          path: "/fin-session"
        }
      ],
      // resultado do cálculo do imposto
      résultat: "",
      résultatObtenu: false
    };
  },
  // componentes utilizados
  components: {
    Layout,
    FormCalculImpot,
    Menu
  },
  // métodos de gerenciamento de eventos
  methods: {
    // resultado do cálculo do imposto
    handleResultatObtenu(résultat) {
      // o resultado é construído em cadeia 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;
      // exibição do resultado
      this.résultatObtenu = true;
      // ---- atualização do store [Vuex]
      // uma simulação de +
      this.$store.commit("addSimulation", résultat);
    }
  }
};
</script>

Comentários

  • linha 4: as duas colunas do [Layout] estão presentes aqui;
  • linha 6: o formulário de cálculo do imposto ocupa a coluna da direita. Ele emite o evento [resultatObtenu] quando o resultado do cálculo do imposto é obtido. Observe-se que os nomes dos eventos e dos métodos que os gerenciam não podem conter caracteres acentuados;
  • linha 8: o menu de navegação ocupa a coluna da esquerda;
  • linhas 11-20: o resultado do cálculo do imposto é exibido abaixo do formulário:

Image

  • linha 11: o resultado só é exibido se o atributo [résultatObtenu] (linha 47) tiver o valor [true];
  • linhas 34-48: o estado da visualização:
    • [options]: a lista de opções do menu de navegação. Essa tabela é passada como parâmetro para o componente [Menu], linha 8;
    • [résultat]: o resultado do cálculo do imposto. Esse resultado é uma string HTML. Por isso, utilizou-se a diretiva [v-html] na linha 17 para exibi-lo;
    • [résultatObtenu]: o valor booleano que controla a exibição do resultado, linha 11;
  • linhas 59-81: o método [handleResultatObtenu] exibe o resultado do cálculo do imposto que lhe foi enviado pela vista filha [FormCalculImpot], linha 6. Esse resultado é um objeto com as propriedades [impot, décôte, réduction, surcôte, taux, marié, enfants, salaire];
  • linhas 61-75: insere-se o objeto [impot, décôte, réduction, surcôte, taux] em um texto HTML, que é exibido pela linha 17 do modelo;
  • linha 77: exibe-se esse resultado;
  • linha 80: chama-se a mutação [addSimulation] do store Vuex, que irá adicionar [résultat] às simulações já presentes no store;

18.4.5. O menu de navegação [Menu]

O menu de navegação é exibido na coluna esquerda das visualizações direcionadas:

Image

O código da visualização [Menu] é o seguinte:


<!-- definição HTML da visualização -->
<template>
  <!-- menu Bootstrap vertical -->
  <b-nav vertical>
    <!-- opções do 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 {
  // parâmetros da visualização
  props: {
    options: {
      type: Array
    }
  }
};
</script>

Comentários

  • as opções do menu são fornecidas pelo parâmetro [options] (linhas 7, 20-22);
  • cada elemento da tabela [options] possui uma propriedade [text] (linha 12), que é o texto do link, e uma propriedade [path] (linha 9), que será o caminho da visualização de destino do link;

18.4.6. A visualização [FormCalculImpot]

Essa visualização fornece o formulário de cálculo do imposto:

Image

Seu código é o seguinte:


  <!-- definição HTML da visualização -->
  <template>
  <!-- formulário HTML -->
  <b-form @submit.prevent="calculerImpot" class="mb-3">
    <!-- mensagem em 12 colunas com fundo azul -->
    <b-alert show variant="primary">
      <h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
    </b-alert>
    <!-- elementos do formulário -->
    <!-- primeira linha -->
    <b-form-group label="Etes-vous marié(e) ou pacsé(e) ?" label-cols="4">
      <!-- botões de opção em 5 colunas-->
      <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>
    <!-- segunda linha -->
    <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"
      />
      <!-- possível mensagem de erro -->
      <b-form-invalid-feedback :state="enfantsValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
    </b-form-group>
    <!-- terceira linha -->
    <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"
      />
      <!-- possível mensagem de erro -->
      <b-form-invalid-feedback :state="salaireValide">Vous devez saisir un nombre positif ou nul</b-form-invalid-feedback>
    </b-form-group>
    <!-- quarta linha, botão [submit] em 5 colunas -->
    <b-col cols="5">
      <b-button type="submit" variant="primary" :disabled="formInvalide">Valider</b-button>
    </b-col>
  </b-form>
</template>

<!-- script -->
<script>
export default {
  // estado interno
  data() {
    return {
      // casado ou solteiro
      marié: "non",
      // número de filhos
      enfants: "",
      // salário anual
      salaire: ""
    };
  },
  // status interno calculado
  computed: {
    // validação do formulário
    formInvalide() {
      return (
        // salário inválido
        !this.salaireValide ||
        // ou número de filhos inválido
        !this.enfantsValide ||
        // ou dados fiscais não obtidos
        !this.$métier.taxAdminData
      );
    },
    // validação do salário
    salaireValide() {
      // deve ser um número >=0
      return Boolean(this.salaire.match(/^\s*\d+\s*$/));
    },
    // validação de filhos
    enfantsValide() {
      // deve ser um valor numérico >=0
      return Boolean(this.enfants.match(/^\s*\d+\s*$/));
    }
  },
  // gerenciador de eventos
  methods: {
    calculerImpot() {
      // o imposto é calculado com a ajuda da camada [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);
      // completa-se o resultado
      résultat.marié = this.marié;
      résultat.enfants = this.enfants;
      résultat.salaire = this.salaire;
      // emite-se o evento [resultatObtenu]
      this.$emit("resultatObtenu", résultat);
    }
  }
};
</script>

Comentários

  • linhas 4-51: o formulário Bootstrap;
  • linhas 11-17: um grupo de botões de opção com seus rótulos;
  • linhas 14-15: a tag <b-form-radio> garante a exibição de um botão de opção:
    • linha 14: a diretiva [v-model] garante que, ao clicar no botão, o atributo [marié] da linha 61 receberá o valor [oui] (atributo [value="oui"]);
    • linha 15: a diretiva [v-model] garante que, ao clicar no botão, o atributo [marié] da linha 61 receberá o valor [non] (atributo [value="non"]);
  • linhas 19-29: a parte relativa à inserção do número de filhos:
    • linha 24: a entrada do número de filhos está vinculada ao atributo [enfants] da linha 63;
    • linha 25: a validade da entrada é verificada pelo atributo calculado [enfantsValide] das linhas 87-89;
    • linha 28: garante a exibição de uma mensagem de erro se a entrada for inválida;
  • linhas 31 a 45: a parte de entrada do salário anual:
    • linha 35: exibe uma mensagem de ajuda logo abaixo do campo de entrada;
    • linha 41: a entrada do salário está vinculada ao atributo [salaire] da linha 65;
    • linha 42: a validade da entrada é verificada pelo atributo calculado [salaireValide] das linhas 82-85;
    • linha 45: garante a exibição de uma mensagem de erro caso a entrada seja inválida;
  • linhas 48-50: um botão do tipo [submit]. Ao clicar nesse botão ou ao confirmar uma entrada com a tecla [Entrée], o método [calculerImpot] é executado (linha 94);
    • linha 49: o estado do botão (ativo/inativo) é controlado pelo atributo calculado [formInvalide] das linhas 71-80;
  • linhas 71 a 80: o formulário é válido se:
    • o número de filhos for válido;
    • o salário for válido;
    • o aplicativo obteve do servidor os dados da administração fiscal que permitem o cálculo do imposto. Vale lembrar que esses dados são registrados na propriedade [$métier.taxAdminData]. A visualização [FormCalculImpot] pode ser exibida antes que esses dados tenham sido obtidos, pois eles são solicitados de forma assíncrona ao mesmo tempo em que ocorre a exibição da visualização. Aqui, garantimos que o usuário não possa clicar no botão [Valider] enquanto os dados não forem obtidos;
  • linhas 94-109: o método de cálculo do imposto:
    • linhas 96-100: é a camada [métier] que realiza esse cálculo. Trata-se de um cálculo síncrono. Uma vez obtidos os dados [taxAdminData], o cliente [Vue] não precisa mais se comunicar com o servidor. Tudo é feito localmente. Obtém-se um objeto [résultat] com as propriedades [impôt, décôte, surcôte, réduction, taux];
    • linhas 104-106: adicionam-se as propriedades [marié, enfants, salaire] ao resultado;
    • linha 108: o resultado é passado para a visualização pai [CalculImpot] por meio do evento [resultatObtenu]. Essa visualização é responsável por exibir o resultado;

18.4.7. A visualização [ListeSimulations]

A visualização [ListeSimulations] exibe a lista das simulações realizadas pelo usuário:

Image

O código da visualização é o seguinte:


<!-- definição HTML da visualização -->
<template>
  <div>
    <!-- formatação -->
    <Layout :left="true" :right="true">
      <!-- simulações na coluna da direita -->
      <template slot="right">
        <template v-if="simulations.length==0">
          <!-- sem simulações -->
          <b-alert show variant="primary">
            <h4>Votre liste de simulations est vide</h4>
          </b-alert>
        </template>
        <template v-if="simulations.length!=0">
          <!-- há simulações -->
          <b-alert show variant="primary">
            <h4>Liste de vos simulations</h4>
          </b-alert>
          <!-- tabela de simulações -->
          <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 navegação na coluna da esquerda -->
      <Menu slot="left" :options="options" />
    </Layout>
  </div>
</template>

<script>
  // importações
  import Layout from "./Layout";
  import Menu from "./Menu";
  export default {
    // componentes
    components: {
      Layout,
      Menu
    },
    // estado interno
    data() {
      return {
        // opções do menu de navegação
        options: [
          {
            text: "Calcul de l'impôt",
            path: "/calcul-impot"
          },
          {
            text: "Fin de session",
            path: "/fin-session"
          }
        ],
        // parâmetros da tabela 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" }
        ]
      };
    },
    // estado interno calculado
    computed: {
      // lista de simulações obtida no armazenamento Vuex
      simulations() {
        return this.$store.state.simulations;
      }
    },
    // métodos
    methods: {
      supprimerSimulation(index) {
        // eslint-disable-next-line
        console.log("supprimerSimulation", index);
        // remoção da simulação nº [index]
        this.$store.commit("deleteSimulation", index);
      }
    }
  };
</script>

Comentários

  • linha 5: a visualização ocupa as duas colunas do layout [Layout] das visualizações encaminhadas;
  • linhas 7-26: as simulações vão para a coluna da direita;
  • linha 28: o menu de navegação fica na coluna da esquerda;
  • linhas 8, 14, 20, 75: as simulações provêm do armazenamento [Vuex] [$this.store];
  • linhas 8-13: alerta exibido quando a lista de simulações está vazia;
  • linhas 14-25: a tabela HTML é exibida quando a lista de simulações não está vazia;
  • linhas 20-24: a tabela HTML é gerada por uma tag <b-table>;
    • linha 20: a tabela de simulações é fornecida pelo atributo calculado [simulations] das linhas 74-76;
    • linha 20: a configuração da tabela HTML é feita pelo atributo calculado [fields] das linhas 58 a 69. Linha 67: a coluna-chave [action] é a última coluna da tabela HTML;
    • linhas 21 a 23: modelo da última coluna da tabela HTML;
    • linha 22: insere-se um botão do tipo link. Ao clicar nele, o método [supprimerSimulation(data.index)] é chamado, onde [data] representa a linha atual (linha 21). [data.index] representa o número dessa linha na lista de linhas exibidas;
  • linha 28: geração do menu de navegação. As opções desse menu são fornecidas pelo atributo [options] das linhas 47 a 56;
  • linhas 80 a 85: o método que responde ao clique em um link [Supprimer] da página HTML;
    • linha 84: é chamada a mutação [deleteSimulation] do store [Vuex] (ver parágrafo |vuejs-15|);

18.5. Execução do projeto

Image

Também é necessário iniciar o servidor [Laragon] (consulte o documento |https://tahe.developpez.com/tutoriels-cours/php7|) para que o servidor de cálculo de impostos fique ativo.

18.6. Implantação do aplicativo em um servidor local

Atualmente, nosso cliente [Vue] está implantado em um servidor de teste no URL [http://localhost:8080]. Vamos implantá-lo no servidor [Laragon], no URL e no [http://localhost:80]. Há várias etapas a serem realizadas para chegar lá.

Etapa 1

Primeiramente, vamos garantir que o cliente [Vue] seja implantado no servidor de teste, passando de URL para [http://localhost:8080/client-vuejs-impot/].

Criamos um arquivo [vue.config.js] na raiz do nosso projeto atual [VSCode]:

Image

O arquivo [vue.config.js] [1] terá o seguinte conteúdo:


// vue.config.js
module.exports = {
  // o serviço URL do cliente [vuejs] do servidor de cálculo de impostos
  publicPath: '/client-vuejs-impot/'
}

Também precisamos modificar o arquivo de roteamento [router.js] [2]:


// importações
import Vue from 'vue'
import VueRouter from 'vue-router'
// as visualizações
import Authentification from './views/Authentification'
import CalculImpot from './views/CalculImpot'
import ListeSimulations from './views/ListeSimulations'

// plug-in de roteamento
Vue.use(VueRouter)

// rotas do aplicativo
const routes = [
  // autenticação
  {
    path: '/', name: 'authentification', component: Authentification
  },
  // cálculo de impostos
  {
    path: '/calcul-impot', name: 'calculImpot', component: CalculImpot
  },
  // lista de simulações
  {
    path: '/liste-des-simulations', name: 'listeSimulations', component: ListeSimulations
  },
  // fim da sessão
  {
    path: '/fin-session', name: 'finSession', component: Authentification
  }
]

// o roteador
const router = new VueRouter({
  // as rotas
  routes,
  // modo de exibição das rotas no navegador
  mode: 'history',
  // o URL básico do aplicativo
  base: '/client-vuejs-impot/'
})

// exportação do roteador
export default router
  • linha 39: indicamos ao roteador que os caminhos das rotas definidas nas linhas 13 a 30 são relativos ao caminho definido na linha 39. Por exemplo, o caminho da linha 20, [/calcul-impot], passará a ser [/client-vuejs-impot/calcul-impot];

É possível, então, testar novamente o projeto [vuejs-20] para verificar a alteração nos caminhos do aplicativo:

Image

Etapa 2

Agora, vamos compilar a versão de produção do projeto [vuejs-20]:

Image

  • em [1-2], configuramos a tarefa [build] [2] no arquivo [package.json] [1];
  • em [3-5], executamos essa tarefa. É ela que irá construir a versão de produção do projeto [vuejs-20];

A execução da tarefa [build] ocorre em um terminal de [VSCode]:

Image

Image

  • no [3-6], alguns avisos indicam que o código gerado é muito grande e que seria necessário dividi-lo em [8]. Isso diz respeito à otimização da arquitetura do código, assunto que não abordaremos aqui;
  • em [7], somos informados de que a pasta [dist] contém a versão de produção gerada:

Image

  • no [3], o arquivo [index.html] é o arquivo que será utilizado quando for solicitado o URL [https://localhost:80/client-vue-js-impot/];

Temos aqui um site estático que pode ser implantado em qualquer servidor. Vamos implantá-lo no servidor Laragon local (consulte o documento |https://tahe.developpez.com/tutoriels-cours/php7|). A pasta [dist] [2] é copiada para a pasta [<laragon>/www] [4], onde <laragon> é a pasta de instalação do servidor Laragon. Renomeamos essa pasta para [client-vuejs-impot] [5], pois configuramos a versão de produção para funcionar na pasta URL [/client-vuejs-impot/].

Etapa 3

Adicionamos à pasta [client-vuejs-impot], que acaba de ser criada, o seguinte arquivo [.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

Este arquivo é um arquivo de configuração do servidor web Apache. Se não o colocarmos e solicitarmos diretamente o URL [https://localhost/client-vuejs-impot/calcul-impot], sem passar primeiro pelo URL [https://localhost/client-vuejs-impot/], receberemos um erro 404. Com esse arquivo, conseguimos visualizar corretamente o [CalculImpot].

Feito isso, iniciamos o servidor Laragon, caso ainda não tenha sido feito, e acessamos o URL [https://localhost/client-vuejs-impot/]:

Image

Convidamos o leitor a testar a versão de produção do nosso aplicativo.

Podemos modificar o servidor de cálculo de impostos em um ponto: os cabeçalhos CORS que ele envia sistematicamente aos seus clientes. Isso havia sido necessário para a versão do cliente executada a partir do domínio [localhost:8080]. Agora que tanto o cliente quanto o servidor são executados no domínio [localhost:80], os cabeçalhos CORS tornam-se desnecessários.

Alteramos o arquivo [config.json] da versão 14 do servidor:

Image

  • para [4], indicando que, a partir de agora, as solicitações CORS serão recusadas;

Vamos salvar essa alteração e solicitar novamente o URL [https://localhost/client-vuejs-impot/]. Deve continuar funcionando.

18.7. Gerenciamento de URL manuais

Em vez de usar corretamente os links do menu de navegação, o usuário pode querer digitar manualmente os URL do aplicativo no campo de endereço do navegador. Vamos, por exemplo, acessar o URL [https://client-vuejs-impot/calcul-impot] sem passar pela página de autenticação. Um hacker certamente tentaria isso. Obtemos a seguinte visualização:

Image

De fato, obtemos a tela de cálculo do imposto. Agora, vamos tentar preencher os campos de entrada e validá-los:

Image

Descobrimos então que o botão [1] [Valider] permanece desativado mesmo que as entradas estejam corretas. Vamos examinar o código da tela [FormCalculImpot]:


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

Na linha 2, vemos que seu estado ativo/inativo depende da propriedade [formInvalide]. Trata-se da seguinte propriedade calculada:


formInvalide() {
      return (
        // salário inválido
        !this.salaireValide ||
        // ou filhos inválidos
        !this.enfantsValide ||
        // ou dados fiscais não obtidos
        !this.$métier.taxAdminData
      );
},

Na linha 8, percebe-se que, para que o formulário seja válido, é necessário ter obtido os dados fiscais. No entanto, esses dados são obtidos durante a validação da visualização [Authentification], que o usuário “pulou”. Portanto, ele não poderá validar o formulário. Se ele tivesse conseguido fazer isso, teria recebido uma mensagem de erro do servidor informando que não estava autenticado. As verificações devem sempre ser feitas no lado do servidor. As verificações no lado do navegador sempre podem ser contornadas. Basta usar um cliente do tipo [Postman], que enviará solicitações brutas ao servidor.

Agora, vamos solicitar o URL e o [https://localhost/client-vuejs-impot/liste-des-simulations]. Obtemos a seguinte visualização:

Image

Agora, os URL e [https://localhost/client-vuejs-impot/fin-session]. Obtemos a seguinte visualização:

Image

Agora, uma visualização que não existe: [https://localhost/client-vuejs-impot/abcd]:

Image

Nosso aplicativo resiste bastante bem às URL digitadas manualmente. Quando elas são chamadas, o roteador do aplicativo reconhece isso. Portanto, é possível intervir antes que a visualização seja finalmente exibida. Vamos examinar esse ponto no projeto [vuejs-21].

Outro ponto a ser observado é o seguinte. Imaginemos que o usuário tenha feito algumas simulações de acordo com as regras:

Image

Agora, vamos atualizar a página com um F5:

Image

Fizemos algo que não é recomendado: digitar o URL manualmente (fazer F5 equivale a isso). Com isso, perdemos nossas simulações.

O projeto seguinte, [vuejs-21], propõe duas melhorias:

  • verificar os URL digitados pelo usuário;
  • manter um histórico do aplicativo mesmo que o usuário digite um URL. Acima, vemos que perdemos a lista de simulações;