Skip to content

12. 项目 [vuejs-10][dao] 插件,异步 HTTP 请求

[vuejs-10] 项目的目录结构如下:

Image

[vuejs-10] 项目演示了一个组件向远程服务器发起 HTTP 请求。其采用的架构如下:

Image

一个 [Vue.js] 组件通过 [dao] 层与税费计算服务器进行通信。

12.1. 安装依赖项

[vuejs-10] 应用程序使用 [axios] 库向税费计算服务器发起异步请求。我们需要安装此依赖项:

Image

  • [4-5] 中,安装 [axios][1-3] 后,向 [package.json] 文件中添加的行;

12.2. [Dao]

[Dao] 类是在 [Dao 类] 一节中开发的。我们在此将其包含进来以供参考:


'use strict';
 
// imports
import qs from 'qs'
 
// dao] class
class Dao {
 
  // manufacturer
  constructor(axios) {
    this.axios = axios;
    // session cookie
    this.sessionCookieName = "PHPSESSID";
    this.sessionCookie = '';
  }

  // init session
  async  initSession() {
    // query options HHTP [get /main.php?action=init-session&type=json]
    const options = {
      method: "GET",
      // URL parameters
      params: {
        action: 'init-session',
        type: 'json'
      }
    };
    // execute query HTTP
    return await this.getRemoteData(options);
  }
 
  async  authentifierUtilisateur(user, password) {
    // query options HHTP [post /main.php?action=authenticate-user]
    const options = {
      method: "POST",
      headers: {
        'Content-type': 'application/x-www-form-urlencoded',
      },
      // body of POST
      data: qs.stringify({
        user: user,
        password: password
      }),
      // URL parameters
      params: {
        action: 'authentifier-utilisateur'
      }
    };
    // execute query HTTP
    return await this.getRemoteData(options);
  }
 
  async getAdminData() {
    // query options HHTP [get /main.php?action=get-admindata]
    const options = {
      method: "GET",
      // URL parameters
      params: {
        action: 'get-admindata'
      }
    };
    // execute query HTTP
    const data = await this.getRemoteData(options);
    // result
    return data;
  }
 
  async  getRemoteData(options) {
    // for the session cookie
    if (!options.headers) {
      options.headers = {};
    }
    options.headers.Cookie = this.sessionCookie;
    // execute query HTTP
    let response;
    try {
      // asynchronous request
      response = await this.axios.request('main.php', options);
    } catch (error) {
      // the [error] parameter is an exception instance - it can take various forms
      if (error.response) {
        // the server response is in [error.response]
        response = error.response;
      } else {
        // error restart
        throw error;
      }
    }
    // response is the entire HTTP response from the server (HTTP headers + response itself)
    // retrieve the session cookie if it exists
    const setCookie = response.headers['set-cookie'];
    if (setCookie) {
      // setCookie is an array
      // look for the session cookie in this table
      let trouvé = false;
      let i = 0;
      while (!trouvé && i < setCookie.length) {
        // look for the session cookie
        const results = RegExp('^(' + this.sessionCookieName + '.+?);').exec(setCookie[i]);
        if (results) {
          // the session cookie is stored
          // eslint-disable-next-line require-atomic-updates
          this.sessionCookie = results[1];
          // we found
          trouvé = true;
        } else {
          // next item
          i++;
        }
      }
    }
    // the server response is in [response.data]
    return response.data;
  }
}
 
// class export
export default Dao;

[vuejs-10] 项目仅在第 18–30 行使用了异步的 [initSession] 方法。请注意,第 10 行通过 [axios] 参数实例化了 [Dao] 类,该参数由调用代码初始化。在此情况下,调用代码是 [./main.js] 脚本。

12.3. [pluginDao] 插件

[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,
    })
  }
}

如果回顾 [event-bus] 插件的说明,我们会发现 [pluginDao] 插件会在 [Vue] 类/函数中创建一个名为 [$dao] 的新属性。该属性的值(如我们将展示的)将是 [./Dao] 脚本导出的对象,即前面的 [Dao] 类。

12.4. 主脚本 [main.js]

主脚本 [main.js] 的代码如下:


// imports
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios';
 
// plugins
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue);
 
// bootstrap
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
 
// couche [dao]
import Dao from './Dao';
// configuration axios
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';
axios.defaults.withCredentials = true;
// instanciation couche [dao]
const dao = new Dao(axios);
 
// plugin [dao]
import pluginDao from './plugins/dao'
Vue.use(pluginDao, dao)
 
// configuration
Vue.config.productionTip = false
 
// instanciation projet [App]
new Vue({
  render: h => h(App),
}).$mount('#app')

[main.js] 脚本:

  • 在第 14–21 行实例化 [dao] 层;
  • 在第 24–25 行引入 [pluginDao] 插件;
  • 第 15 行:导入 [Dao] 类;
  • 第 17–18 行:配置了用于处理 HTTP 请求的 [axios] 对象。该对象在第 4 行被导入;
    • 第 17 行:定义了 2 秒的 [timeout]
    • 第 18 行:税费计算服务器的 URL;
    • 第 19 行:启用与服务器的 Cookie 交换;
  • 第 24–25 行:使用 [pluginDao] 插件
    • 第 24 行:导入插件;
    • 第 25 行:集成该插件。我们可以看到,[Vue.use] 方法的第二个参数是第 21 行定义的 [dao] 层的引用。这就是为什么 [Vue.$dao] 属性在 [Vue] 类/函数的所有实例中(即所有 [Vue.js] 组件中)都会指向 [dao] 层;

12.5. 主视图 [App.vue]

主视图 [App] 的代码如下:


<template>
  <div class="container">
    <b-card>
      <!-- message -->
      <b-alert show variant="success" align="center">
        <h4>[vuejs-10] : plugin [dao], requêtes HTTP asynchrones</h4>
      </b-alert>
      <!-- composant faisant une requête asynchrone au serveur de calcul d'impôt-->
      <Component1 @error="doSomethingWithError" @endWaiting="endWaiting" @beginWaiting="beginWaiting" />
      <!-- affichage d'une éventuelle erreur -->
      <b-alert show
               variant="danger"
               v-if="showError">Evénement [error] intercepté par [App]. Valeur reçue = {{error}}</b-alert>
      <!-- message d'attente avec un spinner -->
      <b-alert show v-if="showWaiting" variant="light">
        <strong>Requête au serveur de calcul d'impôt en cours...</strong>
        <b-spinner variant="primary" label="Spinning"></b-spinner>
      </b-alert>
    </b-card>
  </div>
</template>
 
<script>
  import Component1 from "./components/Component1";
  export default {
    name: "app",
    // état du composant
    data() {
      return {
        // contrôle le spinner d'attente
        showWaiting: false,
        // contrôle l'affichage de l'erreur
        showError: false,
        // l'erreur interceptée
        error: {}
      };
    },
    // composants utilisés
    components: {
      Component1
    },
    // méthodes de gestion des évts
    methods: {
      // début attente
      beginWaiting() {
        // on affiche l'attente
        this.showWaiting = true;
        // on cache le msg d'erreur
        this.showError = false;
      },
      // fin attente
      endWaiting() {
        // on cache l'attente
        this.showWaiting = false;
      },
      // gestion d'erreur
      doSomethingWithError(error) {
        // on note qu'il y a eu erreur
        this.error = error;
        // on affiche le msg d'erreur
        this.showError = true;
      }
    }
  };
</script>

评论

  • 第 9 行:[Component1] 是发起异步 HTTP 请求的组件。它可以触发三个事件:
    • [beginWaiting]:请求即将发出。必须向用户显示一条等待提示;
    • [endWaiting]:请求已完成。必须停止等待;
    • [error]:请求失败。必须显示错误消息;
  • 第 10–13 行:用于显示错误消息的提示框。它由第 33 行的布尔变量 [showError] 控制。该提示框在第 35 行显示错误信息;
  • 第 14–18 行:显示带旋转图标的等待提示的 Alert 控件。由第 47 行的布尔变量 [showWaiting] 控制;
  • 第 45–50 行:[beginWaiting] 是接收到 [beginWaiting] 事件时执行的方法。它会显示等待提示(第 47 行),并隐藏错误提示(第 49 行),以防该提示在前一次操作后仍可见;
  • 第 52–55 行:[endWaiting] 是接收到 [endWaiting] 事件时执行的方法。它隐藏等待消息(第 54 行);
  • 第 57–62 行:[doSomethingWithError] 是接收到 [error] 事件时执行的方法。它记录接收到的错误(第 59 行)并显示错误消息(第 61 行);

12.6. [Component1] 组件

[Component1] 组件的代码如下:


<template>
  <b-row>
    <b-col>
      <b-alert show
               variant="warning"
               v-if="showMsg">Valeur reçue du serveur = {{data}}</b-alert>
    </b-col>
  </b-row>
</template>
 
<script>
  export default {
    name: "component1",
    // component status
    data() {
      return {
        showMsg: false
      };
    },
    // event management methods
    methods: {
      // processing data received from the server
      doSomethingWithData(data) {
        // record the data received
        this.data = data;
        // we display it
        this.showMsg = true;
      }
    },
    // the component has just been created
    created() {
      // initialize the session with the server - asynchronous request
      // we use the promise rendered by the [dao] layer methods
      // we signal the start of the operation
      this.$emit("beginWaiting");
      // asynchronous operation is launched
      this.$dao
        // initialize a jSON session with the tax calculation server
        .initSession()
        // method that processes the data received in the event of success
        .then(data => {
          // we process the data received
          this.doSomethingWithData(data);
        })
        // error handling method in case of error
        .catch(error => {
          // the error is traced back to the parent component
          this.$emit("error", error.message);
        }).finally(() => {
          // end of wait
          this.$emit("endWaiting");
        })
    }
  };
</script>

评论

  • 第 4–6 行:该组件包含一个警报,用于显示税费计算服务器返回的值,但仅在 HTTP 请求成功时显示。该警报由第 17 行的布尔变量 [showMsg] 控制;
  • 第 31–53 行:组件创建后立即发出 HTTP 请求。因此,相关代码被放置在第 31 行的 [created] 方法中;
  • 第 35 行:通知父组件异步请求即将开始;
  • 第 37–39 行:执行 [this.$dao.initSession] 方法。该方法用于与税费计算服务器初始化一个 JSON 会话。此方法的直接返回结果是一个 [Promise]
  • 第 41–44 行:当服务器返回结果且未发生错误时,此代码将执行。服务器的结果存储在 [data] 中。在第 43 行,我们调用 [doSomethingWithData] 方法来处理该结果;
  • 第 46–49 行:若请求执行过程中发生错误,则执行此代码。第 48 行通知父组件已发生错误,并将错误消息 [error.message] 传递给它;
  • 第 49–52 行:此代码在所有情况下都会执行。我们通知父组件 HTTP 请求已完成;
  • 第 23–28 行:[doSomethingWithData] 方法负责处理服务器发送的数据 [data]。第 25 行将该数据存储起来,第 27 行将其显示出来;

12.7. 运行项目

Image

如果项目启动时税费计算服务器未运行,将得到以下结果:

Image

让我们启动 [Laragon] 服务器(参见 https://tahe.developpez.com/tutoriels-cours/php7),并重新加载上述页面。此时结果如下:

Image

注意:我们使用的是 https://tahe.developpez.com/tutoriels-cours/php7 中定义的第 14 版税费计算服务器。