序章

前のチュートリアルでは、請求書発行アプリケーション用のバックエンドサーバーを構築しました。 このチュートリアルでは、ユーザーインターフェイスと呼ばれる、ユーザーが操作するアプリケーションの部分を構築します。

注:これは3部構成のシリーズのパート2です。 最初のチュートリアルは、ノードを使用して軽量の請求書作成アプリを構築する方法:データベースとAPIです。 3番目のチュートリアルは、 Vueとノードを使用して軽量の請求書作成アプリを構築する方法:JWT認証と請求書の送信です。

このチュートリアルのユーザーインターフェイスはVueで構築され、ユーザーがログインして請求書を表示および作成できるようにします。

前提条件

このチュートリアルを完了するには、次のものが必要です。

このチュートリアルは、Node v16.1.0、npm v7.12.1、Vue v2.6.11、Vue Router v3.2.0、axios v0.21.1、およびBootstrapv5.0.1で検証されました。

ステップ1—プロジェクトの設定

@ vue / cli を使用して、新しいVue.jsプロジェクトを作成できます。

注:この新しいプロジェクトディレクトリは、前のチュートリアルで作成したinvoicing-appディレクトリの隣に配置できるはずです。 これにより、serverclientを分離する一般的な方法が導入されます。

ターミナルウィンドウで、次のコマンドを使用します。

  1. npx @vue/cli create --inlinePreset='{ "useConfigFiles": false, "plugins": { "@vue/cli-plugin-babel": {}, "@vue/cli-plugin-eslint": { "config": "base", "lintOn": ["save"] } }, "router": true, "routerHistoryMode": true }' invoicing-app-frontend

これは、 VueRouterでVue.jsプロジェクトを作成するためのインラインプリセット構成を使用します。

新しく作成されたプロジェクトディレクトリに移動します。

  1. cd invoicing-app-frontend

プロジェクトを開始して、エラーがないことを確認します。

  1. npm run serve

Webブラウザでローカルアプリ(通常はlocalhost:8080)にアクセスすると、"Welcome to Your Vue.js App"メッセージが表示されます。

これにより、この記事で構築するサンプルVueプロジェクトが作成されます。

この請求書発行アプリケーションのフロントエンドでは、バックエンドサーバーに対して多くの要求が行われます。

これを実現するために、axiosを使用します。 axiosをインストールするには、プロジェクトディレクトリで次のコマンドを実行します。

  1. npm install axios@0.21.1

アプリケーションでデフォルトのスタイルを使用できるようにするには、Bootstrapを使用します。

まず、コードエディタでpublic/index.htmlファイルを開きます。

Bootstrap用のCDNでホストされているCSSファイルをドキュメントのheadに追加します。

public / index.html
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">

PopperおよびBootstrap用のCDNでホストされているJavaScriptファイルをドキュメントのheadに追加します。

public / index.html
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT" crossorigin="anonymous"></script>

App.vueの内容を次のコード行に置き換えることができます。

src / App.vue
<template>
  <div id="app">
    <router-view/>
  </div>
</template>

また、自動生成されたsrc/views/Home.vuesrc/views/About.vuesrc/components/HelloWorld.vueファイルは無視または削除できます。

この時点で、AxiosとBootstrapを使用した新しいVueプロジェクトがあります。

ステップ2—Vueルーターの設定

このアプリケーションでは、2つの主要なルートがあります。

  • /ログインページをレンダリングします
  • /dashboardユーザーダッシュボードをレンダリングします

これらのルートを構成するには、src/router/index.jsを開き、次のコード行で更新します。

src / router / index.js
import Vue from 'vue'
import VueRouter from 'vue-router'

import SignUp from '@/components/SignUp'
import Dashboard from '@/components/Dashboard'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'SignUp',
    component: SignUp
  },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: Dashboard
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

これは、ユーザーがアプリケーションにアクセスしたときにユーザーに表示する必要のあるコンポーネントを指定します。

ステップ3—コンポーネントの作成

コンポーネントを使用すると、アプリケーションのフロントエンドをよりモジュール化して再利用できるようになります。 このアプリケーションには、次のコンポーネントが含まれます。

  • ヘッダ
  • ナビゲーション
  • サインアップ(およびサインイン)
  • ダッシュボード
  • 請求書を作成する
  • 請求書を表示する

ヘッダーコンポーネントの作成

Headerコンポーネントは、アプリケーションの名前を表示し、ユーザーがサインインしている場合はNavigationを表示します。

Header.vueファイルをsrc/componentsディレクトリに作成します。 コンポーネントファイルには、次のコード行があります。

src / components / Header.vue
<template>
  <nav class="navbar navbar-light bg-light">
    <div class="navbar-brand m-0 p-3 h1 align-self-start">{{title}}</div>
    <template v-if="user != null">
      <Navigation v-bind:name="user.name" v-bind:company="user.company_name"/>
    </template>
  </nav>
</template>

<script>
import Navigation from './Navigation'

export default {
  name: "Header",
  props : ["user"],
  components: {
    Navigation
  },
  data() {
    return {
      title: "Invoicing App",
    };
  }
};
</script>

ヘッダーコンポーネントには、userと呼ばれる単一のpropがあります。 このpropは、ヘッダーコンポーネントを使用するすべてのコンポーネントによって渡されます。 ヘッダーのテンプレートでは、Navigationコンポーネントがインポートされ、条件付きレンダリングを使用して、Navigationを表示するかどうかが決定されます。

ナビゲーションコンポーネントの作成

Navigationコンポーネントは、さまざまなアクションのリンクを格納するサイドバーです。

/src/componentsディレクトリに新しいNavigation.vueコンポーネントを作成します。 コンポーネントには次のテンプレートがあります。

src / components / Navigation.vue
<template>
  <div class="flex-grow-1">
    <div class="navbar navbar-expand-lg">
      <ul class="navbar-nav flex-grow-1 flex-row">
        <li class="nav-item">
          <a class="nav-link" v-on:click="setActive('create')">Create Invoice</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" v-on:click="setActive('view')">View Invoices</a>
        </li>
      </ul>
    </div>
    <div class="navbar-text"><em>Company: {{ company }}</em></div>
    <div class="navbar-text h3">Welcome, {{ name }}</div>
  </div>
</template>

...

次に、コードエディタでNavigation.vueファイルを開き、次のコード行を追加します。

src / components / Navigation.vue
...

<script>
export default {
  name: "Navigation",
  props: ["name", "company"],
  methods: {
   setActive(option) {
      this.$parent.$parent.isactive = option;
    },
  }
};
</script>

コンポーネントは、ユーザー名と会社名の2つのpropsで作成されます。 setActiveメソッドは、ユーザーがナビゲーションリンクをクリックすると、Navigationコンポーネントの親(この場合はDashboard)を呼び出すコンポーネントを更新します。

サインアップコンポーネントの作成

SignUpコンポーネントには、サインアップおよびサインインフォームが格納されています。 /src/componentsディレクトリに新しいファイルを作成します。

まず、コンポーネントを作成します。

src / components / SignUp.vue
<template>
  <div class="container">
    <Header/>

    <ul class="nav nav-tabs" role="tablist">
      <li class="nav-item" role="presentation">
        <button class="nav-link active" id="login-tab" data-bs-toggle="tab" data-bs-target="#login" type="button" role="tab" aria-controls="login" aria-selected="true">Login</button>
      </li>
      <li class="nav-item" role="presentation">
        <button class="nav-link" id="register-tab" data-bs-toggle="tab" data-bs-target="#register" type="button" role="tab" aria-controls="register" aria-selected="false">Register</button>
      </li>
    </ul>

    <div class="tab-content p-3">
      ...
    </div>
  </div>
</template>

<script>
import axios from "axios"

import Header from "./Header"

export default {
  name: "SignUp",
  components: {
    Header
  },
  data() {
    return {
      model: {
        name: "",
        email: "",
        password: "",
        c_password: "",
        company_name: ""
      },
      loading: "",
      status: ""
    };
  },
  methods: {
    ...
  }
}
</script>

Headerコンポーネントがインポートされ、コンポーネントのデータプロパティも指定されます。

次に、データが送信されたときに何が起こるかを処理するメソッドを作成します。

src / components / SignUp.vue
...

  methods: {
    validate() {
      // checks to ensure passwords match
      if (this.model.password != this.model.c_password) {
        return false;
      }
      return true;
    },

    ...
  }

...

validate()メソッドは、ユーザーから送信されたデータが要件を満たしていることを確認するためのチェックを実行します。

src / components / SignUp.vue
  ...

  methods: {
    ...

    register() {
      const formData = new FormData();
      let valid = this.validate();

      if (valid) {
        formData.append("name", this.model.name);
        formData.append("email", this.model.email);
        formData.append("company_name", this.model.company_name);
        formData.append("password", this.model.password);

        this.loading = "Registering you, please wait";

        // Post to server
        axios.post("http://localhost:3128/register", formData).then(res => {
          // Post a status message
          this.loading = "";

          if (res.data.status == true) {
            // now send the user to the next route
            this.$router.push({
              name: "Dashboard",
              params: { user: res.data.user }
            });
          } else {
            this.status = res.data.message;
          }
        });
      } else {
        alert("Passwords do not match");
      }
    },

    ...
  }

...

コンポーネントのregisterメソッドは、ユーザーが新しいアカウントを登録しようとしたときのアクションを処理します。 まず、validateメソッドを使用してデータを検証します。 次に、すべての基準が満たされると、formDataを使用してデータを送信できるように準備されます。

また、コンポーネントのloadingプロパティを定義して、フォームが処理されていることをユーザーに通知します。 最後に、POSTリクエストがaxiosを使用してバックエンドサーバーに送信されます。 サーバーからステータスがtrueの応答を受信すると、ユーザーはダッシュボードに移動します。 それ以外の場合は、エラーメッセージがユーザーに表示されます。

src / components / SignUp.vue
  ...

  methods: {
    ...

    login() {
      const formData = new FormData();

      formData.append("email", this.model.email);
      formData.append("password", this.model.password);
      this.loading = "Logging In";

      // Post to server
      axios.post("http://localhost:3128/login", formData).then(res => {
        // Post a status message
        this.loading = "";
    
        if (res.data.status == true) {
          // now send the user to the next route
          this.$router.push({
            name: "Dashboard",
            params: { user: res.data.user }
          });
        } else {
          this.status = res.data.message;
        }
      });
    }
  }

...

loginメソッドは、registerメソッドに似ています。 データは準備され、ユーザーを認証するためにバックエンドサーバーに送信されます。 ユーザーが存在し、詳細が一致する場合、ユーザーはダッシュボードに移動します。

次に、登録用のテンプレートを見てください。

src / components / SignUp.vue
<template>
  <div class="container">
    ...

    <div class="tab-content p-3">
      <div id="login" class="tab-pane fade show active" role="tabpanel" aria-labelledby="login-tab">
        <div class="row">
          <div class="col-md-12">
            <form @submit.prevent="login">
              <div class="form-group mb-3">
                <label for="login-email" class="label-form">Email:</label>
                <input id="login-email" type="email" required class="form-control" placeholder="[email protected]" v-model="model.email">
              </div>

              <div class="form-group mb-3">
                <label for="login-password" class="label-form">Password:</label>
                <input id="login-password" type="password" required class="form-control" placeholder="Password" v-model="model.password">
              </div>

              <div class="form-group">
                <button class="btn btn-primary">Log In</button>
                {{ loading }}
                {{ status }}
              </div>
            </form>
          </div>
        </div>
      </div>

      ...
    </div>
  </div>
</template>

ログインフォームは上に表示されており、入力フィールドは、コンポーネントの作成時に指定されたそれぞれのデータプロパティにリンクされています。 フォームの送信ボタンをクリックすると、コンポーネントのloginメソッドが呼び出されます。

通常、フォームの送信ボタンをクリックすると、フォームはGETまたはPOSTリクエストを介して送信されます。 これを使用する代わりに、フォームの作成時に<form @submit.prevent="login">を追加して、デフォルトの動作をオーバーライドし、ログイン関数を呼び出すように指定しました。

登録フォームも次のようになります。

src / components / SignUp.vue
<template>
  <div class="container">
    ...

    <div class="tab-content p-3">
      ...

      <div id="register" class="tab-pane fade" role="tabpanel" aria-labelledby="register-tab">
        <div class="row">
          <div class="col-md-12">
            <form @submit.prevent="register">
              <div class="form-group mb-3">
                <label for="register-name" class="label-form">Name:</label>
                <input id="register-name" type="text" required class="form-control" placeholder="Full Name" v-model="model.name">
              </div>

              <div class="form-group mb-3">
                <label for="register-email" class="label-form">Email:</label>
                <input id="register-email" type="email" required class="form-control" placeholder="[email protected]" v-model="model.email">
              </div>

              <div class="form-group mb-3">
                <label for="register-company" class="label-form">Company Name:</label>
                <input id="register-company" type="text" required class="form-control" placeholder="Company Name" v-model="model.company_name">
              </div>

              <div class="form-group mb-3">
                <label for="register-password" class="label-form">Password:</label>
                <input id="register-password" type="password" required class="form-control" placeholder="Password" v-model="model.password">
              </div>

              <div class="form-group mb-3">
                <label for="register-confirm" class="label-form">Confirm Password:</label>
                <input id="register-confirm" type="password" required class="form-control" placeholder="Confirm Password" v-model="model.c_password">
              </div>

              <div class="form-group mb-3">
                <button class="btn btn-primary">Register</button>
                {{ loading }}
                {{ status }}
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

@submit.preventは、送信ボタンがクリックされたときにregisterメソッドを呼び出すためにもここで使用されます。

次に、次のコマンドを使用して開発サーバーを実行します。

  1. npm run serve

ブラウザでlocalhost:8080にアクセスして、新しく作成されたログインおよび登録ページを確認してください。

注:ユーザーインターフェイスを試すときは、invoicing-appサーバーを実行している必要があります。 さらに、Access-Control-Allow-Originヘッダーを設定することで対処する必要があるCORS(クロスオリジンリソースシェアリング)エラーが発生する場合があります。

ログインして新しいユーザーを登録してみてください。

ダッシュボードコンポーネントの作成

ユーザーが/dashboardルートにルーティングされると、ダッシュボードコンポーネントが表示されます。 デフォルトでは、HeaderおよびCreateInvoiceコンポーネントが表示されます。

Dashboard.vueファイルをsrc/componentsディレクトリに作成します。 コンポーネントには次のコード行があります。

src / component / Dashboard.vue
<template>
  <div class="container">
    <Header v-bind:user="user"/>
    <template v-if="this.isactive == 'create'">
      <CreateInvoice />
    </template>
    <template v-else>
      <ViewInvoices />
    </template>
  </div>
</template>

...

テンプレートの下に、次のコード行を追加します。

src / component / Dashboard.vue
...

<script>
import Header from "./Header";
import CreateInvoice from "./CreateInvoice";
import ViewInvoices from "./ViewInvoices";

export default {
  name: "Dashboard",
  components: {
    Header,
    CreateInvoice,
    ViewInvoices,
  },
  data() {
    return {
      isactive: 'create',
      title: "Invoicing App",
      user : (this.$route.params.user) ? this.$route.params.user : null
    };
  }
};
</script>

CreateInvoiceコンポーネントの作成

CreateInvoiceコンポーネントには、新しい請求書を作成するために必要なフォームが含まれています。 src/componentsディレクトリに新しいファイルを作成します。

CreateInvoiceコンポーネントを次のように編集します。

src / components / CreateInvoice.vue
<template>
  <div class="container">
    <div class="tab-pane p-3 fade show active">
      <div class="row">
        <div class="col-md-12">
          <h3>Enter details below to create invoice</h3>
          <form @submit.prevent="onSubmit">
            <div class="form-group mb-3">
              <label for="create-invoice-name" class="form-label">Invoice Name:</label>
              <input id="create-invoice-name" type="text" required class="form-control" placeholder="Invoice Name" v-model="invoice.name">
            </div>

            <div class="form-group mb-3">
              Invoice Price: <span>${{ invoice.total_price }}</span>
            </div>

            ...
          </form>
        </div>
      </div>
    </div>
  </div>
</template>

これにより、請求書の名前を受け入れ、請求書の合計金額を表示するフォームが作成されます。 合計金額は、請求書の個々のトランザクションの価格を合計することによって得られます。

トランザクションが請求書に追加される方法を見てみましょう。

src / components / CreateInvoice.vue
        ...

          <form @submit.prevent="onSubmit">
            ...

            <hr />
            <h3>Transactions </h3>
            <div class="form-group">
              <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#transactionModal">Add Transaction</button>

              <!-- Modal -->
              <div class="modal fade" id="transactionModal" tabindex="-1" aria-labelledby="transactionModalLabel" aria-hidden="true">
                <div class="modal-dialog" role="document">
                  <div class="modal-content">
                    <div class="modal-header">
                      <h5 class="modal-title" id="exampleModalLabel">Add Transaction</h5>
                      <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                    </div>
                    <div class="modal-body">
                      <div class="form-group mb-3">
                        <label for="txn_name_modal" class="form-label">Transaction name:</label>
                        <input id="txn_name_modal" type="text" class="form-control">
                      </div>
                      <div class="form-group mb-3">
                        <label for="txn_price_modal" class="form-label">Price ($):</label>
                        <input id="txn_price_modal" type="numeric" class="form-control">
                      </div>
                    </div>
                    <div class="modal-footer">
                      <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Discard Transaction</button>
                      <button type="button" class="btn btn-primary" data-bs-dismiss="modal" v-on:click="saveTransaction()">Save Transaction</button>
                    </div>
                  </div>
                </div>
              </div>

            </div>

            ...
          </form>

        ...

ユーザーが新しいトランザクションを追加するためのボタンが表示されます。 トランザクションの追加ボタンをクリックすると、トランザクションの詳細を入力するためのモーダルがユーザーに表示されます。 トランザクションの保存ボタンをクリックすると、メソッドが既存のトランザクションに追加します。

src / components / CreateInvoice.vue
        ...

          <form @submit.prevent="onSubmit">
            ...

            <div class="col-md-12">
              <table class="table">
                <thead>
                  <tr>
                    <th scope="col">#</th>
                    <th scope="col">Transaction Name</th>
                    <th scope="col">Price ($)</th>
                    <th scope="col"></th>
                  </tr>
                </thead>
                <tbody>
                  <template v-for="txn in transactions">
                    <tr :key="txn.id">
                      <th>{{ txn.id }}</th>
                      <td>{{ txn.name }}</td>
                      <td>{{ txn.price }} </td>
                      <td><button type="button" class="btn btn-danger" v-on:click="deleteTransaction(txn.id)">Delete</button></td>
                    </tr>
                  </template>
                </tbody>
              </table>
            </div>

            <div class="form-group">
              <button class="btn btn-primary">Create Invoice</button>
              {{ loading }}
              {{ status }}
            </div>
          </form>

        ...

既存のトランザクションは表形式で表示されます。 削除ボタンをクリックすると、対象のトランザクションがトランザクションリストから削除され、Invoice Priceが再計算されます。 最後に、Create Invoiceボタンは関数をトリガーし、データを準備して、請求書を作成するためにバックエンドサーバーに送信します。

Create Invoiceコンポーネントのコンポーネント構造も見てみましょう。

src / components / CreateInvoice.vue
...

<script>
import axios from "axios";

export default {
  name: "CreateInvoice",
  data() {
    return {
      invoice: {
        name: "",
        total_price: 0
      },
      transactions: [],
      nextTxnId: 1,
      loading: "",
      status: ""
    };
  },
  methods: {
    ...
  }
};
</script>

まず、コンポーネントのデータプロパティを定義しました。 コンポーネントには、請求書nameおよびtotal_priceを含む請求書オブジェクトが含まれます。 また、nextTxnIdインデックスを持つtransactionsの配列もあります。 これにより、トランザクションと変数が追跡され、ステータスの更新がユーザーに送信されます。

src / components / CreateInvoice.vue
  ...

  methods: {
    saveTransaction() {
      // append data to the arrays
      let name = document.getElementById("txn_name_modal").value;
      let price = document.getElementById("txn_price_modal").value;

      if (name.length != 0 && price > 0) {
        this.transactions.push({
          id: this.nextTxnId,
          name: name,
          price: price
        });

        this.nextTxnId++;
        this.calcTotal();

        // clear their values
        document.getElementById("txn_name_modal").value = "";
        document.getElementById("txn_price_modal").value = "";
      }
    },

    ...
  }

...

CreateInvoiceコンポーネントのメソッドもここで定義されています。 saveTransaction()メソッドは、トランザクションフォームモーダルの値を取得し、それらをトランザクションリストに追加します。 deleteTransaction()メソッドは、トランザクションのリストから既存のトランザクションオブジェクトを削除しますが、calcTotal()メソッドは、新しいトランザクションが追加または削除されたときに合計請求価格を再計算します。

src / components / CreateInvoice.vue
  ...

  methods: {
    ...

    deleteTransaction(id) {
      let newList = this.transactions.filter(function(el) {
        return el.id !== id;
      });
  
      this.nextTxnId--;
      this.transactions = newList;
      this.calcTotal();
    },

    calcTotal() {
      let total = 0;

      this.transactions.forEach(element => {
        total += parseInt(element.price, 10);
      });
      this.invoice.total_price = total;
    },

    ...
  }

...

最後に、onSubmit()メソッドはフォームをバックエンドサーバーに送信します。 この方法では、formDataaxiosを使用してリクエストを送信します。 トランザクションオブジェクトを含むトランザクション配列は、2つの異なる配列に分割されます。 1つの配列はトランザクション名を保持し、もう1つの配列はトランザクション価格を保持します。 次に、サーバーは要求を処理し、ユーザーに応答を返送しようとします。

src / components / CreateInvoice.vue
  ...

  methods: {
    ...

    onSubmit() {
      const formData = new FormData();

      this.transactions.forEach(element => {
        formData.append("txn_names[]", element.name);
        formData.append("txn_prices[]", element.price)
      });

      formData.append("name", this.invoice.name);
      formData.append("user_id", this.$route.params.user.id);
      this.loading = "Creating Invoice, please wait ...";

      // Post to server
      axios.post("http://localhost:3128/invoice", formData).then(res => {
        // Post a status message
        this.loading = "";

        if (res.data.status == true) {
          this.status = res.data.message;
        } else {
          this.status = res.data.message;
        }
      });
    }
  }

...

localhost:8080でアプリケーションに戻ってサインインすると、ダッシュボードにリダイレクトされます。

ViewInvoiceコンポーネントの作成

請求書を作成できるようになったので、次のステップは、請求書とそのステータスの視覚的な画像を作成することです。 これを行うには、アプリケーションのsrc/componentsディレクトリにViewInvoices.vueファイルを作成します。

次のようにファイルを編集します。

src / components / ViewInvoices.vue
<template>
  <div>
    <div class="tab-pane p-3 fade show active">
      <div class="row">
        <div class="col-md-12">
          <h3>Here is a list of your invoices</h3>
          <table class="table">
            <thead>
              <tr>
                <th scope="col">Invoice #</th>
                <th scope="col">Invoice Name</th>
                <th scope="col">Status</th>
                <th scope="col"></th>
              </tr>
            </thead>
            <tbody>
              <template v-for="invoice in invoices">
                <tr :key="invoice.id">
                  <th scope="row">{{ invoice.id }}</th>
                  <td>{{ invoice.name }}</td>
                  <td v-if="invoice.paid == 0">Unpaid</td>
                  <td v-else>Paid</td>
                  <td><a href="#" class="btn btn-success">To Invoice</a></td>
                </tr>
              </template>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
</template>

...

上記のテンプレートには、ユーザーが作成した請求書を表示するテーブルが含まれています。 また、請求書がクリックされたときにユーザーを単一の請求書ページに移動させるボタンもあります。

src / components / ViewInvoice.vue
...

<script>
import axios from "axios";

export default {
  name: "ViewInvoices",
  data() {
    return {
      invoices: [],
      user: this.$route.params.user
    };
  },
  mounted() {
    axios
      .get(`http://localhost:3128/invoice/user/${this.user.id}`)
      .then(res => {
        if (res.data.status == true) {
          this.invoices = res.data.invoices;
        }
      });
  }
};
</script>

ViewInvoicesコンポーネントには、請求書とユーザーの詳細の配列としてのデータプロパティがあります。 ユーザーの詳細は、ルートパラメータから取得されます。 コンポーネントがmountedの場合、GETリクエストがバックエンドサーバーに対して行われ、ユーザーが作成した請求書のリストを取得します。このリストは、前に示したテンプレートを使用して表示されます。

/dashboardに移動したら、Navigation請求書の表示オプションをクリックして、請求書と支払い状況のリストを表示します。

結論

シリーズのこのパートでは、Vueの概念を使用して、請求アプリケーションのユーザーインターフェイスを構成しました。

Vueとノードを使用して軽量の請求書作成アプリを構築する方法:JWT認証と請求書の送信で学習を続けてください。