父子节点 构建
介绍 (Introduction)
In the first part of this series, you set up the backend server for the invoicing application. In this tutorial you will build the part of the application that users will interact with, known as the user interface.
在本系列的第一部分中,您将为发票应用程序设置后端服务器。 在本教程中,您将构建用户将与之交互的应用程序部分,即用户界面。
先决条件 (Prerequisites)
To follow through this article, you’ll need the following:
要继续阅读本文,您需要满足以下条件:
- Node installed on your machine 安装在计算机上的节点
- NPM installed on your machine NPM安装在您的计算机上
Have read through the first part of this series.
阅读了本系列的第一部分 。
第1步-安装Vue (Step 1 — Installing Vue)
To build the frontend of this application, you’ll be using Vue. Vue is a progressive JavaScript framework used for building useful and interactive user interfaces. To install Vue, run the following command:
要构建此应用程序的前端,您将使用Vue 。 Vue是一个渐进式JavaScript框架,用于构建有用的交互式用户界面。 要安装Vue,请运行以下命令:
- npm install -g vue-cli npm install -g vue-cli
To confirm your Vue installation, run this command:
要确认您的Vue安装,请运行以下命令:
vue --version
The version number will be returned if Vue is installed.
如果安装了Vue,将返回版本号。
第2步-创建项目 (Step 2 — Creating the Project)
To create a new project with Vue, run the following command and then follow the prompt:
要使用Vue创建新项目,请运行以下命令,然后按照提示进行操作:
- vue init webpack invoicing-app-frontend Vue初始化Webpack发票应用前端
This creates a sample Vue
project that we’ll build upon in this article.
这将创建一个示例Vue
项目,在本文中将以此为基础。
For the frontend of this invoicing application, a lot of requests are going to be made to the backend server. To do this, we’ll make use of axios. To install axios
, run the command in your project directory:
对于此开票应用程序的前端,将对后端服务器提出许多请求。 为此,我们将使用axios 。 要安装axios
,请在您的项目目录中运行命令:
- npm install axios --save npm install axios-保存
To allow some default styling in the application, you will make use of Bootstrap
. To add it to your application, add the following to the index.html
file in the project:
要在应用程序中允许某些默认样式,您将使用Bootstrap
。 要将其添加到您的应用程序中,请将以下内容添加到项目中的index.html
文件中:
...
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
...
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js" integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js" integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm"crossorigin="anonymous"></script>
...
步骤3 —配置Vue路由器 (Step 3 — Configuring Vue Router)
For this application, you are going to have two major routes:
对于此应用程序,您将有两条主要路线:
/
to render the login page/
呈现登录页面/dashboard
to render the user dashboard/dashboard
呈现用户仪表板
To configure these routes, open the src/router/index.js
and update it to look like this:
要配置这些路由,请打开src/router/index.js
并将其更新为如下所示:
import Vue from 'vue'
import Router from 'vue-router'
import SignUp from '@/components/SignUp'
import Dashboard from '@/components/Dashboard'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'SignUp',
component: SignUp
},
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard
},
]
})
This specifies the components that should be displayed to the user when they visit your application.
这指定了当用户访问您的应用程序时应向用户显示的组件。
第4步-创建组件 (Step 4 — Creating Components)
One of the major selling points of Vue is the component structure. Components allow the frontend of your application to be more modular and reusable. This application will have the following components:
Vue的主要卖点之一是组件结构。 组件使您的应用程序的前端更加模块化和可重用。 该应用程序将具有以下组件:
- Sign Up/Sign In 注册登录
- Header 标头
- Navigation 导航
- Dashboard 仪表板
- View Invoices 查看发票
- Create Invoice 创建发票
- Single Invoice 单张发票
The Navigation component is the sidebar that will house the links of different actions. Create a new component in the /src/components
directory:
导航组件是侧栏,将包含不同操作的链接。 在/src/components
目录中创建一个新组件:
- touch SideNav.vue 触摸SideNav.vue
Now, edit the SideNav.vue
like this:
现在,像这样编辑SideNav.vue
:
<script>
export default {
name: "SideNav",
props: ["name", "company"],
methods: {
setActive(option) {
this.$parent.$parent.isactive = option;
},
openNav() {
document.getElementById("leftsidenav").style.width = "20%";
},
closeNav() {
document.getElementById("leftsidenav").style.width = "0%";
}
}
};
</script>
...
The component is created with two props
: the name of the user and the name of the company. The two methods add the collapsible functionality to the sidebar. The setActive
method will update the component calling the parent of the SideNav
component, in this case Dashboard
, when a user clicks on a navigation link.
该组件使用两个props
创建:用户名和公司名。 这两种方法将可折叠功能添加到侧栏。 当用户单击导航链接时, setActive
方法将更新调用SideNav
组件父级的组件,在本例中为Dashboard
。
The component has the following template:
该组件具有以下模板:
...
<template>
<div>
<span style="font-size:30px;cursor:pointer" v-on:click="openNav">☰</span>
<div id="leftsidenav" class="sidenav">
<p style="font-size:12px;cursor:pointer" v-on:click="closeNav"><em>Close Nav</em></p>
<p><em>Company: {{ company }} </em></p>
<h3>Welcome, {{ name }}</h3>
<p class="clickable" v-on:click="setActive('create')">Create Invoice</p>
<p class="clickable" v-on:click="setActive('view')">View Invoices</p>
</div>
</div>
</template>
...
The Header
component displays the name of the application and side bar if a user is signed in. Create a Header.vue
file in the src/components
directory:
如果用户登录,则Header
组件将显示应用程序的名称和侧栏。在src/components
目录中创建Header.vue
文件:
- touch Header.vue 触摸Header.vue
The component file will look like this:
组件文件将如下所示:
<template>
<nav class="navbar navbar-light bg-light">
<template v-if="user != null">
<SideNav v-bind:name="user.name" v-bind:company="user.company_name"/>
</template>
<span class="navbar-brand mb-0 h1">{{title}}</span>
</nav>
</template>
<script>
import SideNav from './SideNav'
export default {
name: "Header",
props : ["user"],
components: {
SideNav
},
data() {
return {
title: "Invoicing App",
};
}
};
</script>
The header component has a single prop
called user
. This prop
will be passed by any component that will use the header component. In template for the header, the SideNav
component created earlier is imported and conditional rendering is used to determine if the SideNav
should be displayed or not.
标头组件具有一个称为user
prop
。 该prop
将由将使用标头组件的任何组件传递。 在标题模板中,将导入先前创建的SideNav
组件,并使用条件渲染来确定是否应显示SideNav
。
The SignIn
component houses the sign up and sign in form. Create a new file in /src/components
directory:
SignIn
组件包含注册和登录表单。 在/src/components
目录中创建一个新文件:
- touch SignIn.vue 触摸SignIn.vue
The component to register and login a user is a little more complex than the two earlier components. The app needs to have the login and register forms on the same page.
注册和登录用户的组件比前两个组件要复杂一些。 该应用程序需要在同一页面上具有登录和注册表单。
First, create the component:
首先,创建组件:
<script>
import Header from "./Header";
import axios from "axios";
export default {
name: "SignUp",
components: {
Header
},
data() {
return {
model: {
name: "",
email: "",
password: "",
c_password: "",
company_name: ""
},
loading: "",
status: ""
};
},
...
The Header
component is imported and the data properties of the components are also specified. Next, create the methods to handle what happens when data is submitted:
导入了Header
组件,并且还指定了组件的数据属性。 接下来,创建用于处理提交数据时发生的情况的方法:
...
methods: {
validate() {
// checks to ensure passwords match
if( this.model.password != this.model.c_password){
return false;
}
return true;
},
...
The validate()
method performs checks to make sure the data sent by the user meets our requirements.
validate()
方法执行检查以确保用户发送的数据符合我们的要求。
...
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");
}
},
...
The register
method of the component handles the action when a user tries to register a new account. First, the data is validated using the validate
method. Then if all criteria are met, the data is prepared for submission using the formData
.
当用户尝试注册新帐户时,组件的register
方法将处理该操作。 首先,使用validate
方法验证数据。 然后,如果满足所有条件,则准备使用formData
提交数据。
We’ve also defined the loading
property of the component to let the user know when their form is being processed. Finally, a POST
request is sent to the backend server using axios. When a response is received from the server with a status of true
, the user is directed to the dashboard. Otherwise, an error message is displayed to the user.
我们还定义了组件的loading
属性,以使用户知道何时处理其表单。 最后,使用axios将POST
请求发送到后端服务器。 当从服务器收到状态为true
的响应时, true
用户定向到仪表板。 否则,将向用户显示错误消息。
...
login() {
const formData = new FormData();
formData.append("email", this.model.email);
formData.append("password", this.model.password);
this.loading = "Signing 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;
}
});
}
}
};
</script>
Just like the register
method, the data is prepared and sent over to the backend server to authenticate the user. If the user exists and the details match, the user is directed to their dashboard.
就像register
方法一样,准备数据并将其发送到后端服务器以对用户进行身份验证。 如果用户存在并且详细信息匹配,则将用户定向到其仪表板。
Now, take a look at the template for the SignUp
component:
现在,看一下SignUp
组件的模板:
<template>
...
<div class="tab-pane fade show active" id="pills-login" role="tabpanel" aria-labelledby="pills-login-tab">
<div class="row">
<div class="col-md-12">
<form @submit.prevent="login">
<div class="form-group">
<label for="">Email:</label>
<input type="email" required class="form-control" placeholder="eg chris@invoiceapp.com" v-model="model.email">
</div>
<div class="form-group">
<label for="">Password:</label>
<input type="password" required class="form-control" placeholder="Enter Password" v-model="model.password">
</div>
<div class="form-group">
<button class="btn btn-primary" >Login</button>
{{ loading }}
{{ status }}
</div>
</form>
</div>
</div>
</div>
...
The login in form is shown above and the input fields are linked to the respective data properties specified when the components were created. When the submit button of the form is clicked, the login
method of the component is called.
上面以登录形式显示,输入字段链接到创建组件时指定的相应数据属性。 单击表单的提交按钮时,将调用组件的login
方法。
Usually, when the submit button of a form is clicked, the form is submitted via a GET
or POST
request. Instead of using that, we added <form @submit.prevent="login">
when creating the form to override the default behavior and specify that the login function should be called.
通常,当单击表单的提交按钮时, 将通过GET
或POST
请求提交表单。 代替使用它,我们在创建表单时添加了<form @submit.prevent="login">
以覆盖默认行为并指定应调用登录功能。
The register form also looks like this:
注册表格也如下所示:
...
<div class="tab-pane fade" id="pills-register" role="tabpanel" aria-labelledby="pills-register-tab">
<div class="row">
<div class="col-md-12">
<form @submit.prevent="register">
<div class="form-group">
<label for="">Name:</label>
<input type="text" required class="form-control" placeholder="eg Chris" v-model="model.name">
</div>
<div class="form-group">
<label for="">Email:</label>
<input type="email" required class="form-control" placeholder="eg chris@invoiceapp.com" v-model="model.email">
</div>
<div class="form-group">
<label for="">Company Name:</label>
<input type="text" required class="form-control" placeholder="eg Chris Tech" v-model="model.company_name">
</div>
<div class="form-group">
<label for="">Password:</label>
<input type="password" required class="form-control" placeholder="Enter Password" v-model="model.password">
</div>
<div class="form-group">
<label for="">Confirm Password:</label>
<input type="password" required class="form-control" placeholder="Confirm Passowrd" v-model="model.confirm_password">
</div>
<div class="form-group">
<button class="btn btn-primary" >Register</button>
{{ loading }}
{{ status }}
</div>
</form>
</div>
</div>
...
</template>
The @submit.prevent
is also used here to call the register
method when the submit button is clicked.
单击提交按钮时,这里还使用@submit.prevent
来调用register
方法。
Now, run your development server using this command:
现在,使用以下命令运行开发服务器:
- npm run dev npm run dev
Visit localhost:8080/
on your browser to see the newly created login and sign up page.
在浏览器上访问localhost:8080/
以查看新创建的登录和注册页面。
The Dashboard component will be displayed when the user gets routed to the /dashboard
route. It displays the Header
and the CreateInvoice
component by default. Create the Dashboard.vue
file in the src/components
directory
当用户被路由到/dashboard
路由时,将显示Dashboard组件。 默认情况下,它显示Header
和CreateInvoice
组件。 在src/components
目录中创建Dashboard.vue
文件
- touch Dashboard.vue 触摸Dashboard.vue
Edit the file to look like this:
编辑文件,如下所示:
<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>
...
Above, the necessary components are imported and rendered based on the template below:
上面,根据以下模板导入和渲染了必要的组件:
...
<template>
<div class="container-fluid" style="padding: 0px;">
<Header v-bind:user="user"/>
<template v-if="this.isactive == 'create'">
<CreateInvoice />
</template>
<template v-else>
<ViewInvoices />
</template>
</div>
</template>
The CreateInvoice
component contains the form needed to create a new invoice. Create a new file in the src/components
directory:
CreateInvoice
组件包含创建新发票所需的表单。 在src/components
目录中创建一个新文件:
- touch CreateInvoice.vue 触摸CreateInvoice.vue
Edit the CreateInvoice
component to look like this:
编辑CreateInvoice
组件,如下所示:
<template>
<div>
<div class="container">
<div class="tab-pane 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">
<label for="">Invoice Name:</label>
<input type="text" required class="form-control" placeholder="eg Seller's Invoice" v-model="invoice.name">
</div>
<div class="form-group">
<label for="">Invoice Price:</label><span> $ {{ invoice.total_price }}</span>
</div>
...
This creates a form that accepts the name of the invoice and displays the total price of the invoice. The total price is obtained by summing up the prices of individual transactions for the invoice.
这将创建一个接受发票名称并显示发票总价的表格。 总价是通过将发票的各个交易的价格相加得出的。
Let’s take a look at how transactions are added to the invoice:
让我们看一下如何将交易添加到发票中:
...
<hr />
<h3> Transactions </h3>
<div class="form-group">
<label for="">Add Transaction:</label>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#transactionModal">
+
</button>
<!-- Modal -->
<div class="modal fade" id="transactionModal" tabindex="-1" role="dialog" 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="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="">Transaction name</label>
<input type="text" id="txn_name_modal" class="form-control">
</div>
<div class="form-group">
<label for="">Price ($)</label>
<input type="numeric" id="txn_price_modal" class="form-control">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Discard Transaction</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" v-on:click="saveTransaction()">Save transaction</button>
</div>
</div>
</div>
</div>
</div>
...
A button is displayed for the user to add a new transaction. When the button is clicked, a modal is shown to the user to enter the details of the transaction. When the Save Transaction
button is clicked, a method adds it to the existing transactions.
显示一个按钮,供用户添加新交易。 单击该按钮时,将向用户显示一个模式,以输入交易的详细信息。 单击“ Save Transaction
按钮时,一种方法会将其添加到现有事务中。
...
<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)">X</button></td>
</tr>
</template>
</tbody>
</table>
</div>
<div class="form-group">
<button class="btn btn-primary" >Create Invoice</button>
{{ loading }}
{{ status }}
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</template>
...
The existing transactions are displayed in a tabular format. When the X button is clicked, the transaction in question is deleted from the transaction list and the Invoice Price
is recalculated. Finally, the Create Invoice
button triggers a function that then prepares the data and sends it to the backend server for creation of the invoice.
现有交易以表格格式显示。 单击X按钮后,相关交易将从交易列表中删除,并重新计算nvoice Price
。 最后,“ Create Invoice
按钮将触发一个函数,该函数随后准备数据并将其发送到后端服务器以创建发票。
Let’s also take a look at the component structure of the Create Invoice
component:
我们还看一下“ Create Invoice
组件的组件结构:
...
<script>
import axios from "axios";
export default {
name: "CreateInvoice",
data() {
return {
invoice: {
name: "",
total_price: 0
},
transactions: [],
nextTxnId: 1,
loading: "",
status: ""
};
},
...
First, you defined the data properties for the component. The component will have an invoice object containing the invoice name
and total_price
. It’ll also have an array of transactions
with the nextTxnId
index. This will keep track of the transactions and variables to send status updates to the user.
首先,您定义了组件的数据属性。 该组件将具有包含发票name
和total_price
的发票对象。 它还将具有一个带有nextTxnId
索引的transactions
数组。 这将跟踪事务和变量,以将状态更新发送给用户。
...
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 = "";
}
},
...
The methods for the CreateInvoice
component are also defined here. The saveTransaction()
method takes the values in the transaction form modal and then adds it to the transaction list. The deleteTransaction()
method deletes an existing transaction object from the list of transactions while the calcTotal()
method recalculates the total invoice price when a new transaction is added or deleted.
CreateInvoice
组件的方法也在此处定义。 saveTransaction()
方法采用交易形式模式中的值,然后将其添加到交易列表中。 deleteTransaction()
方法从事务列表中删除现有事务对象,而calcTotal()
方法在添加或删除新事务时重新计算总发票价格。
...
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);
});
this.invoice.total_price = total;
},
...
Finally, the onSubmit()
method will submit the form to the backend server. In the method, formData
and axios
are used to send the requests. The transaction array containing the transaction objects is split into two different arrays. One array holds the transaction names and the other holds the transaction prices. The server then attempts to process the request and send back a response to the user.
最后, onSubmit()
方法会将表单提交到后端服务器。 在该方法中, formData
和axios
用于发送请求。 包含事务对象的事务数组分为两个不同的数组。 一个数组保存交易名称,另一个保存交易价格。 然后,服务器尝试处理该请求,并将响应发送回用户。
onSubmit() {
const formData = new FormData();
// format for request
let txn_names = [];
let txn_prices = [];
this.transactions.forEach(element => {
txn_names.push(element.name);
txn_prices.push(element.price);
});
formData.append("name", this.invoice.name);
formData.append("txn_names", txn_names);
formData.append("txn_prices", txn_prices);
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;
}
});
}
}
};
</script>
When you go back to the application on localhost:8080
and sign in, you will get redirected to a dashboard.
当您回到localhost:8080
上的应用程序并登录时,您将被重定向到仪表板。
Now that you can create invoices, the next step is to create a visual picture of invoices and their statuses. To do this, create a ViewInvoices.vue
file in the src/components
directory of the application.
现在您可以创建发票,下一步是创建发票及其状态的可视化图片。 为此,请在应用程序的src/components
目录中创建一个ViewInvoices.vue
文件。
- touch ViewInvoices.vue 触摸ViewInvoices.vue
Edit the file to look like this:
编辑文件,如下所示:
<template>
<div>
<div class="container">
<div class="tab-pane fade show active">
<div class="row">
<div class="col-md-12">
<h3>Here are 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>
<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>
</div>
</template>
...
The template above contains a table displaying the invoices a user has created. It also has a button that takes the user to a single invoice page when an invoice is clicked.
上面的模板包含一个显示用户创建的发票的表。 它还具有一个按钮,单击发票时,该按钮会将用户带到单个发票页面。
...
<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>
The ViewInvoices
component has its data properties as an array of invoices and the user details. The user details are obtained from the route parameters. When the component is mounted
, a GET
request is made to the backend server to fetch the list of invoices created by the user which are then displayed using the template that was shown earlier.
ViewInvoices
组件的数据属性为发票和用户详细信息的数组。 用户详细信息从路由参数获取。 mounted
组件后,将向后端服务器发出GET
请求,以获取用户创建的发票清单,然后使用之前显示的模板显示这些清单。
When you go to the Dashboard, click the View Invoices option on the SideNav
to see a listing of invoices with the payment status.
当您转到仪表板时,单击SideNav
上的“ 查看发票”选项,以查看具有付款状态的发票清单。
结论 (Conclusion)
In this part of the series, you configured the user interface of the invoicing application using concepts from Vue. In the next part of the series, you will take a look at how to add JWT authentication to maintain user sessions, view single invoices, and send invoices via email to recipients.
在本系列的这一部分中,您使用Vue中的概念配置了发票应用程序的用户界面。 在本系列的下一部分中,您将了解如何添加JWT身份验证以维护用户会话,查看单个发票以及通过电子邮件将发票发送给收件人。
翻译自: https://www.digitalocean.com/community/tutorials/how-to-build-a-lightweight-invoicing-app-with-node-user-interface
父子节点 构建