如何使用 Node.NET 构建轻量级发票应用程序?用户界面

简介

在上一教程中,您构建了发票应用程序的后台服务器。在本教程中,您将构建用户将与之交互的应用程序部分,即用户界面。

<$>[注] 注: 这是三部曲系列的第二部分。第一篇教程是 如何使用 Node 构建轻量级发票应用程序:数据库和 API。第三篇教程是【如何使用 Vue 和 Node 构建轻量级发票应用程序:JWT 身份验证和发送发票](https://andsky.com/tech/tutorials/how-to-build-a-lightweight-invoicing-app-with-vue-and-node-jwt-authentication-and-sending-invoices)。 <$>

本教程中的用户界面将使用 Vue构建,允许用户登录查看和创建发票。

先决条件

要完成本教程,您需要

本教程使用 Node v16.1.0、npm v7.12.1、Vue v2.6.11、Vue Router v3.2.0、axios v0.21.1 和 Bootstrap v5.0.1 进行了验证。

步骤 1 - 设置项目

您可以使用 @vue/cli创建一个新的 Vue.js 项目。

<$>[注] 注意: 您应该可以将这个新项目目录放在上一个教程中创建的 invoicing-app 目录旁边。这引入了将 serverclient 分开的常见做法。 <$>

在终端窗口中,使用以下命令:

1npx @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

这将使用内联预设配置,通过 Vue Router 创建 Vue.js 项目。

导航至新建的项目目录:

1cd invoicing-app-frontend

启动项目,验证是否存在错误。

1npm run serve

如果您在网络浏览器中访问本地应用程序(通常位于 localhost:8080),您将看到一条"欢迎访问您的 Vue.js 应用程序"的消息。

这将创建一个示例 "Vue "项目,我们将在本文中以此为基础进行构建。

对于该发票应用程序的前端,将向后端服务器发出大量请求。

为此,我们将使用 axios。要安装 axios,请在项目目录下运行该命令:

1npm install [email protected]

为了允许在应用程序中使用一些默认样式,您将使用 Bootstrap

首先,在代码编辑器中打开 "public/index.html "文件。

将 CDN 托管的 Bootstrap CSS 文件添加到文档的 "head "部分:

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

将 CDN 托管的 Popper 和 Bootstrap JavaScript 文件添加到文档的 "head "部分:

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

您可以用以下代码行替换 App.vue 中的内容:

1[label src/App.vue]
2<template>
3  <div id="app">
4    <router-view/>
5  </div>
6</template>

你可以忽略或删除自动生成的 src/views/Home.vuesrc/views/About.vuesrc/components/HelloWorld.vue 文件。

至此,您就有了一个包含 Axios 和 Bootstrap 的新 Vue 项目。

第 2 步 - 配置 Vue 路由器

在此应用中,您将有两条主要路线:

  • / 呈现登录页面
  • /dashboard 用于显示用户仪表板

要配置这些路由,请打开 src/router/index.js,并用以下代码行更新:

 1[label src/router/index.js]
 2import Vue from 'vue'
 3import VueRouter from 'vue-router'
 4
 5import SignUp from '@/components/SignUp'
 6import Dashboard from '@/components/Dashboard'
 7
 8Vue.use(VueRouter)
 9
10const routes = [
11  {
12    path: '/',
13    name: 'SignUp',
14    component: SignUp
15  },
16  {
17    path: '/dashboard',
18    name: 'Dashboard',
19    component: Dashboard
20  }
21]
22
23const router = new VueRouter({
24  mode: 'history',
25  base: process.env.BASE_URL,
26  routes
27})
28
29export default router

这指定了用户访问应用程序时应向其显示的组件。

第 3 步 - 创建组件

组件使应用程序的前端更加模块化和可重用。本应用程序将包含以下组件:

  • 标题
  • 导航
  • 注册(和登录)
  • 仪表板
  • 创建发票
  • 查看发票

创建页眉组件

如果用户已登录,"页眉 "组件会显示应用程序名称和 "导航"。

src/components 目录中创建一个 Header.vue 文件。该组件文件包含以下代码行:

 1[label src/components/Header.vue]
 2<template>
 3  <nav class="navbar navbar-light bg-light">
 4    <div class="navbar-brand m-0 p-3 h1 align-self-start">{{title}}</div>
 5    <template v-if="user != null">
 6      <Navigation v-bind:name="user.name" v-bind:company="user.company_name"/>
 7    </template>
 8  </nav>
 9</template>
10
11<script>
12import Navigation from './Navigation'
13
14export default {
15  name: "Header",
16  props : ["user"],
17  components: {
18    Navigation
19  },
20  data() {
21    return {
22      title: "Invoicing App",
23    };
24  }
25};
26</script>

页眉组件有一个名为 "用户 "的 "属性"。任何将使用页眉组件的组件都将传递该prop。在页眉的模板中,会导入导航组件,并使用条件渲染来确定是否显示导航

创建导航组件

导航 "组件是侧边栏,用于放置不同操作的链接。

/src/components 目录中创建一个新的 Navigation.vue 组件。该组件的模板如下:

 1[label src/components/Navigation.vue]
 2<template>
 3  <div class="flex-grow-1">
 4    <div class="navbar navbar-expand-lg">
 5      <ul class="navbar-nav flex-grow-1 flex-row">
 6        <li class="nav-item">
 7          <a class="nav-link" v-on:click="setActive('create')">Create Invoice</a>
 8        </li>
 9        <li class="nav-item">
10          <a class="nav-link" v-on:click="setActive('view')">View Invoices</a>
11        </li>
12      </ul>
13    </div>
14    <div class="navbar-text"><em>Company: {{ company }}</em></div>
15    <div class="navbar-text h3">Welcome, {{ name }}</div>
16  </div>
17</template>
18
19...

接下来,在代码编辑器中打开 Navigation.vue 文件,添加以下代码行:

 1[label src/components/Navigation.vue]
 2...
 3
 4<script>
 5export default {
 6  name: "Navigation",
 7  props: ["name", "company"],
 8  methods: {
 9   setActive(option) {
10      this.$parent.$parent.isactive = option;
11    },
12  }
13};
14</script>

创建该组件时包含两个 "道具":用户名和公司名。当用户点击导航链接时,"setActive "方法将更新调用 "导航 "组件父组件(在本例中为 "仪表盘")的组件。

创建注册组件

SignUp "组件包含注册和登录表单。在 /src/components 目录中创建一个新文件。

首先,创建组件:

 1[label src/components/SignUp.vue]
 2<template>
 3  <div class="container">
 4    <Header/>
 5
 6    <ul class="nav nav-tabs" role="tablist">
 7      <li class="nav-item" role="presentation">
 8        <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>
 9      </li>
10      <li class="nav-item" role="presentation">
11        <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>
12      </li>
13    </ul>
14
15    <div class="tab-content p-3">
16      ...
17    </div>
18  </div>
19</template>
20
21<script>
22import axios from "axios"
23
24import Header from "./Header"
25
26export default {
27  name: "SignUp",
28  components: {
29    Header
30  },
31  data() {
32    return {
33      model: {
34        name: "",
35        email: "",
36        password: "",
37        c_password: "",
38        company_name: ""
39      },
40      loading: "",
41      status: ""
42    };
43  },
44  methods: {
45    ...
46  }
47}
48</script>

导入 "标题 "组件,并指定组件的数据属性。

接下来,创建方法来处理数据提交时发生的情况:

 1[label src/components/SignUp.vue]
 2...
 3
 4  methods: {
 5    validate() {
 6      // checks to ensure passwords match
 7      if (this.model.password != this.model.c_password) {
 8        return false;
 9      }
10      return true;
11    },
12
13    ...
14  }
15
16...

验证() "方法执行检查,以确保用户发送的数据符合我们的要求。

 1[label src/components/SignUp.vue]
 2  ...
 3
 4  methods: {
 5    ...
 6
 7    register() {
 8      const formData = new FormData();
 9      let valid = this.validate();
10
11      if (valid) {
12        formData.append("name", this.model.name);
13        formData.append("email", this.model.email);
14        formData.append("company_name", this.model.company_name);
15        formData.append("password", this.model.password);
16
17        this.loading = "Registering you, please wait";
18
19        // Post to server
20        axios.post("http://localhost:3128/register", formData).then(res => {
21          // Post a status message
22          this.loading = "";
23
24          if (res.data.status == true) {
25            // now send the user to the next route
26            this.$router.push({
27              name: "Dashboard",
28              params: { user: res.data.user }
29            });
30          } else {
31            this.status = res.data.message;
32          }
33        });
34      } else {
35        alert("Passwords do not match");
36      }
37    },
38
39    ...
40  }
41
42...

当用户尝试注册新账户时,组件的 register 方法会处理相关操作。首先,使用 validate 方法验证数据。然后,如果符合所有条件,就使用 formData 方法准备提交数据。

我们还定义了组件的 loading 属性,以便让用户知道他们的表单何时正在处理。最后,我们使用 axios 向后台服务器发送一个 POST 请求。当服务器收到状态为 "true "的响应时,用户将被引导至仪表板。否则,将向用户显示错误信息。

 1[label src/components/SignUp.vue]
 2  ...
 3
 4  methods: {
 5    ...
 6
 7    login() {
 8      const formData = new FormData();
 9
10      formData.append("email", this.model.email);
11      formData.append("password", this.model.password);
12      this.loading = "Logging In";
13
14      // Post to server
15      axios.post("http://localhost:3128/login", formData).then(res => {
16        // Post a status message
17        this.loading = "";
18
19        if (res.data.status == true) {
20          // now send the user to the next route
21          this.$router.push({
22            name: "Dashboard",
23            params: { user: res.data.user }
24          });
25        } else {
26          this.status = res.data.message;
27        }
28      });
29    }
30  }
31
32...

登录 "方法与 "注册 "方法类似。数据会被准备好并发送到后端服务器以验证用户身份。如果用户存在且详细信息匹配,用户将被引导至其仪表板。

现在来看看注册模板:

 1[label src/components/SignUp.vue]
 2<template>
 3  <div class="container">
 4    ...
 5
 6    <div class="tab-content p-3">
 7      <div id="login" class="tab-pane fade show active" role="tabpanel" aria-labelledby="login-tab">
 8        <div class="row">
 9          <div class="col-md-12">
10            <form @submit.prevent="login">
11              <div class="form-group mb-3">
12                <label for="login-email" class="label-form">Email:</label>
13                <input id="login-email" type="email" required class="form-control" placeholder="[email protected]" v-model="model.email">
14              </div>
15
16              <div class="form-group mb-3">
17                <label for="login-password" class="label-form">Password:</label>
18                <input id="login-password" type="password" required class="form-control" placeholder="Password" v-model="model.password">
19              </div>
20
21              <div class="form-group">
22                <button class="btn btn-primary">Log In</button>
23                {{ loading }}
24                {{ status }}
25              </div>
26            </form>
27          </div>
28        </div>
29      </div>
30
31      ...
32    </div>
33  </div>
34</template>

登录表单如上图所示,输入字段与创建组件时指定的相应数据属性相关联。点击表单的提交按钮后,组件的 "登录 "方法将被调用。

通常,当点击表单的提交按钮时,表单会通过 GETPOST 请求提交。我们在创建表格时添加了 <form @submit.prevent="login"> 来覆盖默认行为,并指定应调用登录函数,而不是使用这种方式。

注册表也是这样的:

 1[label src/components/SignUp.vue]
 2<template>
 3  <div class="container">
 4    ...
 5
 6    <div class="tab-content p-3">
 7      ...
 8
 9      <div id="register" class="tab-pane fade" role="tabpanel" aria-labelledby="register-tab">
10        <div class="row">
11          <div class="col-md-12">
12            <form @submit.prevent="register">
13              <div class="form-group mb-3">
14                <label for="register-name" class="label-form">Name:</label>
15                <input id="register-name" type="text" required class="form-control" placeholder="Full Name" v-model="model.name">
16              </div>
17
18              <div class="form-group mb-3">
19                <label for="register-email" class="label-form">Email:</label>
20                <input id="register-email" type="email" required class="form-control" placeholder="[email protected]" v-model="model.email">
21              </div>
22
23              <div class="form-group mb-3">
24                <label for="register-company" class="label-form">Company Name:</label>
25                <input id="register-company" type="text" required class="form-control" placeholder="Company Name" v-model="model.company_name">
26              </div>
27
28              <div class="form-group mb-3">
29                <label for="register-password" class="label-form">Password:</label>
30                <input id="register-password" type="password" required class="form-control" placeholder="Password" v-model="model.password">
31              </div>
32
33              <div class="form-group mb-3">
34                <label for="register-confirm" class="label-form">Confirm Password:</label>
35                <input id="register-confirm" type="password" required class="form-control" placeholder="Confirm Password" v-model="model.c_password">
36              </div>
37
38              <div class="form-group mb-3">
39                <button class="btn btn-primary">Register</button>
40                {{ loading }}
41                {{ status }}
42              </div>
43            </form>
44          </div>
45        </div>
46      </div>
47    </div>
48  </div>
49</template>

这里还使用了 @submit.prevent 在点击提交按钮时调用 register 方法。

现在,使用此命令运行开发服务器:

1npm run serve

访问浏览器中的 localhost:8080 查看新创建的登录和注册页面。

<$>[注] 注意: 在尝试使用用户界面时,您需要运行 invoicing-app 服务器。此外,您可能会遇到 CORS(跨源资源共享)错误,可能需要通过设置 Access-Control-Allow-Origin 标头来解决。 <$>

尝试登录和注册新用户。

创建仪表板组件

当用户转到 /dashboard 路由时,将显示仪表盘组件。默认情况下,它会显示 "标题 "和 "创建发票 "组件。

src/components 目录中创建 Dashboard.vue 文件。该组件包含以下代码行:

 1[label src/component/Dashboard.vue]
 2<template>
 3  <div class="container">
 4    <Header v-bind:user="user"/>
 5    <template v-if="this.isactive == 'create'">
 6      <CreateInvoice />
 7    </template>
 8    <template v-else>
 9      <ViewInvoices />
10    </template>
11  </div>
12</template>
13
14...

在模板下方添加以下代码行:

 1[label src/component/Dashboard.vue]
 2...
 3
 4<script>
 5import Header from "./Header";
 6import CreateInvoice from "./CreateInvoice";
 7import ViewInvoices from "./ViewInvoices";
 8
 9export default {
10  name: "Dashboard",
11  components: {
12    Header,
13    CreateInvoice,
14    ViewInvoices,
15  },
16  data() {
17    return {
18      isactive: 'create',
19      title: "Invoicing App",
20      user : (this.$route.params.user) ? this.$route.params.user : null
21    };
22  }
23};
24</script>

创建创建发票组件

创建发票 "组件包含创建新发票所需的表单。在 src/components 目录中创建一个新文件:

编辑 CreateInvoice 组件,使其看起来像这样:

 1[label src/components/CreateInvoice.vue]
 2<template>
 3  <div class="container">
 4    <div class="tab-pane p-3 fade show active">
 5      <div class="row">
 6        <div class="col-md-12">
 7          <h3>Enter details below to create invoice</h3>
 8          <form @submit.prevent="onSubmit">
 9            <div class="form-group mb-3">
10              <label for="create-invoice-name" class="form-label">Invoice Name:</label>
11              <input id="create-invoice-name" type="text" required class="form-control" placeholder="Invoice Name" v-model="invoice.name">
12            </div>
13
14            <div class="form-group mb-3">
15              Invoice Price: <span>${{ invoice.total_price }}</span>
16            </div>
17
18            ...
19          </form>
20        </div>
21      </div>
22    </div>
23  </div>
24</template>

这将创建一个接受发票名称并显示发票总价的表单。总价是将该发票各个交易的价格相加得出的。

让我们看看交易是如何添加到发票中的:

 1[label src/components/CreateInvoice.vue]
 2        ...
 3
 4          <form @submit.prevent="onSubmit">
 5            ...
 6
 7            <hr />
 8            <h3>Transactions </h3>
 9            <div class="form-group">
10              <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#transactionModal">Add Transaction</button>
11
12              <!-- Modal -->
13              <div class="modal fade" id="transactionModal" tabindex="-1" aria-labelledby="transactionModalLabel" aria-hidden="true">
14                <div class="modal-dialog" role="document">
15                  <div class="modal-content">
16                    <div class="modal-header">
17                      <h5 class="modal-title" id="exampleModalLabel">Add Transaction</h5>
18                      <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
19                    </div>
20                    <div class="modal-body">
21                      <div class="form-group mb-3">
22                        <label for="txn_name_modal" class="form-label">Transaction name:</label>
23                        <input id="txn_name_modal" type="text" class="form-control">
24                      </div>
25                      <div class="form-group mb-3">
26                        <label for="txn_price_modal" class="form-label">Price ($):</label>
27                        <input id="txn_price_modal" type="numeric" class="form-control">
28                      </div>
29                    </div>
30                    <div class="modal-footer">
31                      <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Discard Transaction</button>
32                      <button type="button" class="btn btn-primary" data-bs-dismiss="modal" v-on:click="saveTransaction()">Save Transaction</button>
33                    </div>
34                  </div>
35                </div>
36              </div>
37
38            </div>
39
40            ...
41          </form>
42
43        ...

系统会显示一个按钮,供用户添加新交易。点击 "添加交易 "按钮后,系统会显示一个模态窗口,让用户输入交易的详细信息。单击 "保存交易 "按钮后,一个方法会将该交易添加到现有交易中。

 1[label src/components/CreateInvoice.vue]
 2        ...
 3
 4          <form @submit.prevent="onSubmit">
 5            ...
 6
 7            <div class="col-md-12">
 8              <table class="table">
 9                <thead>
10                  <tr>
11                    <th scope="col">#</th>
12                    <th scope="col">Transaction Name</th>
13                    <th scope="col">Price ($)</th>
14                    <th scope="col"></th>
15                  </tr>
16                </thead>
17                <tbody>
18                  <template v-for="txn in transactions">
19                    <tr :key="txn.id">
20                      <th>{{ txn.id }}</th>
21                      <td>{{ txn.name }}</td>
22                      <td>{{ txn.price }} </td>
23                      <td><button type="button" class="btn btn-danger" v-on:click="deleteTransaction(txn.id)">Delete</button></td>
24                    </tr>
25                  </template>
26                </tbody>
27              </table>
28            </div>
29
30            <div class="form-group">
31              <button class="btn btn-primary">Create Invoice</button>
32              {{ loading }}
33              {{ status }}
34            </div>
35          </form>
36
37        ...

现有交易以表格形式显示。点击删除 按钮后,有关交易将从交易列表中删除,并重新计算 "发票价格"。最后,"创建发票 "按钮会触发一个函数,然后准备数据并将其发送到后台服务器,以便创建发票。

我们也来看看 "创建发票 "组件的组件结构:

 1[label src/components/CreateInvoice.vue]
 2...
 3
 4<script>
 5import axios from "axios";
 6
 7export default {
 8  name: "CreateInvoice",
 9  data() {
10    return {
11      invoice: {
12        name: "",
13        total_price: 0
14      },
15      transactions: [],
16      nextTxnId: 1,
17      loading: "",
18      status: ""
19    };
20  },
21  methods: {
22    ...
23  }
24};
25</script>

首先,您定义了组件的数据属性。该组件将有一个发票对象,其中包含发票的 "名称 "和 "总价"。它还将拥有一个包含 nextTxnId 索引的 transactions 数组。这将跟踪交易和变量,以便向用户发送状态更新。

 1[label src/components/CreateInvoice.vue]
 2  ...
 3
 4  methods: {
 5    saveTransaction() {
 6      // append data to the arrays
 7      let name = document.getElementById("txn_name_modal").value;
 8      let price = document.getElementById("txn_price_modal").value;
 9
10      if (name.length != 0 && price > 0) {
11        this.transactions.push({
12          id: this.nextTxnId,
13          name: name,
14          price: price
15        });
16
17        this.nextTxnId++;
18        this.calcTotal();
19
20        // clear their values
21        document.getElementById("txn_name_modal").value = "";
22        document.getElementById("txn_price_modal").value = "";
23      }
24    },
25
26    ...
27  }
28
29...

这里还定义了 CreateInvoice 组件的方法。saveTransaction() "方法获取事务表单模态中的值,然后将它们添加到事务列表中。deleteTransaction() "方法从事务列表中删除现有事务对象,而 "calcTotal() "方法则在添加或删除新事务时重新计算发票总价。

 1[label src/components/CreateInvoice.vue]
 2  ...
 3
 4  methods: {
 5    ...
 6
 7    deleteTransaction(id) {
 8      let newList = this.transactions.filter(function(el) {
 9        return el.id !== id;
10      });
11
12      this.nextTxnId--;
13      this.transactions = newList;
14      this.calcTotal();
15    },
16
17    calcTotal() {
18      let total = 0;
19
20      this.transactions.forEach(element => {
21        total += parseInt(element.price, 10);
22      });
23      this.invoice.total_price = total;
24    },
25
26    ...
27  }
28
29...

最后,onSubmit() 方法会将表单提交到后台服务器。在该方法中,formDataaxios 用于发送请求。包含事务对象的事务数组被分成两个不同的数组。一个数组包含交易名称,另一个包含交易价格。然后,服务器会尝试处理请求并向用户发回响应。

 1[label src/components/CreateInvoice.vue]
 2  ...
 3
 4  methods: {
 5    ...
 6
 7    onSubmit() {
 8      const formData = new FormData();
 9
10      this.transactions.forEach(element => {
11        formData.append("txn_names[]", element.name);
12        formData.append("txn_prices[]", element.price)
13      });
14
15      formData.append("name", this.invoice.name);
16      formData.append("user_id", this.$route.params.user.id);
17      this.loading = "Creating Invoice, please wait ...";
18
19      // Post to server
20      axios.post("http://localhost:3128/invoice", formData).then(res => {
21        // Post a status message
22        this.loading = "";
23
24        if (res.data.status == true) {
25          this.status = res.data.message;
26        } else {
27          this.status = res.data.message;
28        }
29      });
30    }
31  }
32
33...

当您回到 localhost:8080 上的应用程序并登录时,您将被重定向到一个仪表板。

创建 ViewInvoice 组件

现在您可以创建发票了,下一步就是创建发票及其状态的可视化图片。为此,请在应用程序的 src/components 目录中创建一个 ViewInvoices.vue 文件。

编辑文件,使其看起来像这样:

 1[label src/components/ViewInvoices.vue]
 2<template>
 3  <div>
 4    <div class="tab-pane p-3 fade show active">
 5      <div class="row">
 6        <div class="col-md-12">
 7          <h3>Here is a list of your invoices</h3>
 8          <table class="table">
 9            <thead>
10              <tr>
11                <th scope="col">Invoice #</th>
12                <th scope="col">Invoice Name</th>
13                <th scope="col">Status</th>
14                <th scope="col"></th>
15              </tr>
16            </thead>
17            <tbody>
18              <template v-for="invoice in invoices">
19                <tr :key="invoice.id">
20                  <th scope="row">{{ invoice.id }}</th>
21                  <td>{{ invoice.name }}</td>
22                  <td v-if="invoice.paid == 0">Unpaid</td>
23                  <td v-else>Paid</td>
24                  <td><a href="#" class="btn btn-success">To Invoice</a></td>
25                </tr>
26              </template>
27            </tbody>
28          </table>
29        </div>
30      </div>
31    </div>
32  </div>
33</template>
34
35...

上面的模板包含一个表格,显示用户创建的发票。它还包含一个按钮,当用户点击发票时,该按钮会将用户带入单张发票页面。

 1[label src/components/ViewInvoice.vue]
 2...
 3
 4<script>
 5import axios from "axios";
 6
 7export default {
 8  name: "ViewInvoices",
 9  data() {
10    return {
11      invoices: [],
12      user: this.$route.params.user
13    };
14  },
15  mounted() {
16    axios
17      .get(`http://localhost:3128/invoice/user/${this.user.id}`)
18      .then(res => {
19        if (res.data.status == true) {
20          this.invoices = res.data.invoices;
21        }
22      });
23  }
24};
25</script>

查看发票 "组件的数据属性是发票数组和用户详细信息。用户详细信息是从路由参数中获取的。当该组件被 "挂载 "时,会向后台服务器发出 "GET "请求,以获取用户创建的发票列表,然后使用前面显示的模板显示出来。

进入"/仪表板 "后,单击 "导航 "中的 "查看发票 "**选项,即可看到发票和付款状态列表。

结论

在本系列的这一部分中,您使用 Vue 的概念配置了发票应用程序的用户界面。

继续学习 如何使用 Vue 和 Node 构建轻量级发票应用程序:JWT 身份验证和发送发票

Published At
Categories with 技术
comments powered by Disqus