六、使用 PrimeVue UI 框架构建度假预订应用
在第 5 章使用 Ionic构建多用途计算器移动应用中,我们使用 Ionic 移动应用框架构建了一个基于 Vue.js 的移动应用。然而,到目前为止,在本书中,我们还没有使用基于 Vue.js 的 UI 库或框架构建任何 web 应用。此外,我们还没有构建任何有自己后端的东西。在大多数系统中,后端绝对是必需的,因为我们需要一个地方来存储数据、验证用户、运行后台任务等等。在本章中,我们将使用 PrimeVue UI 框架构建一个假期预订应用。
我们将使用 Vue 3 前端进行管理,另一个前端用于用户添加预订。我们还将包括一个简单的后端,用于在管理员执行只能由他们完成的任务之前对其进行身份验证。为了使项目尽可能简单,面向公众的前端不需要身份验证。
在本章中,我们将重点关注以下主题:
- 使用 PrimeVue UI 框架构建前端
- 使用 Express 构建用于身份验证的简单后端
- 使用 SQLite 在后端持久化数据
- 在前端使用 Vue 路由进行身份验证
- 使用 Vee Validate 和 Yup 进行表单验证
技术要求
本章代码位于https://github.com/PacktPublishing/-Vue.js-3-By-Example/tree/master/Chapter06 。
PrimeVue是基于 Vue 3 的 UI 框架。这意味着我们可以将其用于 Vue 3 应用。基于 Vue 2 的框架不能由 Vue 3 应用使用,因为 API 已经历重大更改。Vue 3 的底层代码也不同于 Vue 2。PrimeVue 包括许多我们在 web 应用中使用的常用组件,如文本输入、按钮、菜单栏、表格等。就所包括的内容而言,它是非常全面的。此外,它还提供了主题形式的项目样式。这意味着我们可以立即使用内置组件。由于 PrimeVue 是为 Vue 3 设计的,因此我们可以简单地注册组件、导入 CSS 并在代码中使用组件。我们还可以根据给定组件所需的组件,在本地或全局注册它们。
了解 PrimeVue
PrimeVue 提供了输入样式和各种文本,如验证错误和按钮。它还附带 flexbox 助手,我们可以使用它轻松设置组件的间距。这非常有用,因为我们可以简单地使用 PrimeVue 附带的 CSS 类来设置组件的位置和间距。
到目前为止,在本书中,我们还没有使用任何库来使表单验证更加方便。表单验证是我们必须为大多数 web 应用做的很多事情。
理解 Vee 验证和 Yup
Vee Validate 4是一个与 Vue 3 兼容的表单验证库。有了它,我们可以在前端添加组件,为表单添加验证。我们将与 Yup 数据验证库一起使用它来创建表单验证模式,Vee Validate 可以使用该模式进行验证。通过Form
组件,我们可以添加一个表单,使我们能够使用 Vee Validate 4 进行表单验证。然后,Field
组件可用于验证 PrimeVue 附带的表单控制组件。它通过将[T2]组件附带的插槽道具作为道具传递给输入组件来实现这一点。
Yup 库将与 Vee Validate 一起使用,使我们能够轻松地验证表单值,而无需从头编写所有代码。它允许我们创建表单验证模式对象,我们可以将这些对象传递到使用 Vee Validate 创建的表单中,以添加表单验证。
理解快车
为了创建一个简单的后端来存储数据,我们使用Express framework。这是一个非常简单的 Node.js 后端框架,它允许我们快速创建一个简单的后端。为了存储数据,我们将使用 SQLite 数据库使项目保持简单。我们将使用 Express 框架创建一个 API,使前端能够向其发出 HTTP 请求。我们让他们通过公开 API 端点发出请求,前端可以通过向其添加路由来使用该端点。每个路由都有一个处理函数,用于处理前端提交的数据。我们从前端发出的 HTTP 请求中获取请求数据,其中包括头和正文,并在路由处理程序中使用它们以我们想要的方式获取和存储数据。
连接前端和后端
要使前端应用与后端应用通信,我们需要在后端启用跨域通信,以便前端的流量可以通过后端。这可以通过跨源资源共享(CORS中间件轻松实现,我们将添加到我们的 Express app 中。
为了使用 SQLite 数据库,我们使用sqlite3
库,它允许我们在 Node.js 应用中操作 SQLite 数据库。我们可以进行查询并运行 SQL 命令来插入或删除数据库中的数据。
此外,我们将为管理员前端提供简单的身份验证。我们将检查管理员登录的用户名和密码,如果有效,我们可以发出令牌并将其发送到前端。然后,前端将使用存储在报头中的令牌来检查是否可以从前端发出请求。我们只为admin only路由添加身份验证,因此我们只需要在加载之前检查需要身份验证的路由的令牌。
为了创建和检查令牌,我们使用jsonwebtoken
库。这允许我们创建一个令牌,并用一个秘密字符串对其进行签名。它还使我们能够检查带有密码的令牌,以查看其是否有效。我们将jsonwebtoken
库放在一个中间件中,该中间件在路由处理程序执行检查之前运行。
如果令牌是有效的,那么我们调用一个函数继续路由处理程序。否则,我们会将401
状态发送回客户端。
现在,我们要建造这个项目。
创建假期预订项目
要创建假期预订应用,我们需要为前端、管理前端和后端创建子项目。为了创建frontend
和admin frontend
项目支架,我们使用 Vue CLI。要创建backend
文件夹,我们使用Express Generator
全局包。
按照以下步骤设置项目:
- 首先,创建
travel-booking-app
文件夹以容纳所有项目。 - 接下来,在主文件夹中创建
admin-frontend
、frontend
和backend
文件夹。 -
Go into the
admin-frontend
folder and run the following command:js npx vue create
这将在
admin-frontend
文件夹中添加 Vue 项目的脚手架代码。 -
If you are asked to create the project in the current folder, select Y. Then, when you're asked to choose the Vue version of the project, choose
Vue 3
.同样,对[T0]文件夹以相同的方式运行 Vue CLI。
-
To create an Express project, run the Express application generator app. To do this, go into the
backend
folder and run the following command:js npx express-generator
前面的命令将在
backend
文件夹中添加项目所需的所有文件。如果您出现错误,请尝试将express-generator
作为administrator
运行。
现在我们已经创建了项目 scaffold 文件和文件夹,我们已经准备好开始后端工作了。
创建后端
现在我们已经用 scaffolding 代码创建了项目文件夹,我们可以开始处理项目代码了。我们将从后端开始,因为两个前端都需要它。
首先,让我们添加一些库,这些库是操作 SQLite 数据库和向我们的应用添加身份验证所需的。此外,我们还需要库将 CORS 添加到我们的应用中。
要安装所有这些组件,请运行以下命令:
npm i cors jsonwebtoken sqlite3
安装包之后,我们就可以开始编写代码了。
添加身份验证中间件
首先,我们添加中间件,用于检查令牌。我们可以通过jsonwebtoken
库轻松实现这一点。这有verify
方法来检查令牌。
要添加中间件,请在后端文件夹中创建middlewares
文件夹,然后添加verify-token.js
文件。接下来,为中间件添加以下代码:
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
const token = req.get('x-token')
try {
jwt.verify(token, 'secret');
next()
} catch (err) {
res.status(401)
}
}
这里,我们使用req.get
方法获得x-token
请求头。然后,我们使用返回的token
和secret
调用jwt.verify
,以验证令牌是否有效。然后,如果有效,我们调用next
。如果无效,将抛出一个错误,catch
块将运行。运行带有401
的res.status
以将401
响应返回到前端,因为令牌在此场景中无效。
module.exports
属性被设置为middleware
函数作为我们正在导出的值。导出该函数可在我们后端应用的其他模块中使用。
添加路由以处理请求
接下来,我们将添加带有路由的router
模块。首先,添加路线以操纵预订。为此,请将bookings.js
文件添加到routes
文件夹中。在文件中,编写以下代码:
const express = require('express');
const sqlite3 = require('sqlite3').verbose();
const router = express.Router();
const verifyToken = require('../middlewares/verify-token')
router.get('/', (req, res, next) => {
const db = new sqlite3.Database('./db.sqlite');
db.serialize(() => {
db.all(`
SELECT
bookings.*,
catalog_items.name AS catalog_item_name,
catalog_items.description AS catalog_item_description
FROM bookings
INNER JOIN catalog_items ON catalog_items.id =
bookings.catalog_item_id
`,
[],
(err, rows = []) => {
res.json(rows)
});
})
db.close();
});
...
在这里,我们导入所需的模块,包括我们刚刚创建的verify-token middleware
文件。
router.get
方法允许我们创建 GET 请求 API 端点。路径在第一个参数中。它是路由的路径,并且与路由的路径相关。因此,路由的路由路径是第一段,router.get
的第一个参数中的路径构成 URL 的其余部分。
router.get
方法的第二个参数是路由处理程序。req
参数是具有请求数据的对象。res
参数是一个对象,允许我们向前端发送各种响应。我们使用带有数据库文件路径的sqlite3.Database
构造函数获取数据库。
接下来,我们调用db.serialize
函数,以便可以在回调中按顺序运行代码。
db.all
方法获取查询返回的所有结果。字符串是从 bookings 表中检索所有数据的 SQL 命令,我们将使用自己的 SQL 代码创建该表。bookings
表与catalog_items
表联接,使vacation
套餐数据与预订关联。
db.all
的第二个参数是我们想要传入的额外参数,我们通过传入一个空数组来忽略这些参数。然后,在最后一个参数中,我们有一个带有err
参数的函数,它的对象形式有错误。rows
参数具有来自查询的结果。在回调中,我们调用res.json
返回带有rows
参数数据的 JSON 响应。
然后,一旦完成所需的操作,我们调用db.close
关闭数据库连接。
接下来,我们将创建一个 POST 路由。这将允许我们运行一个INSERT
SQL 命令,在 bookings 表中插入一个条目。添加以下代码:
...
router.post('/', (req, res) => {
const db = new sqlite3.Database('./db.sqlite');
const { catalogItemId, name, address, startDate, endDate } =
req.body
db.serialize(() => {
const stmt = db.prepare(`
INSERT INTO bookings (
catalog_item_id,
name,
address,
start_date,
end_date
) VALUES (?, ?, ?, ?, ?)
`);
stmt.run(catalogItemId, name, address, startDate, endDate)
stmt.finalize();
res.json({ catalogItemId, name, address, startDate,
endDate })
})
db.close();
});
...
在这里,我们用req.body
属性得到身体属性。我们得到了要插入到条目中的所有属性。接下来,我们用INSERT
语句创建一个准备好的语句。末尾的值是问号;这意味着它们是占位符,当我们运行stmt.run
方法时,我们可以在其中放置自己的值。
准备好的语句很有用,因为它们使我们能够安全地将值传递给 SQL 命令。这些值都已清理,因此恶意代码无法在代码内部运行。我们运行stmt.run
以使用我们想要替换占位符的值运行准备好的语句。然后我们调用stmt.finalize
通过写入数据来完成操作。接下来,我们调用res.json
将 JSON 响应作为响应返回到前端。然后,我们调用db.close
再次关闭数据库连接。
接下来,我们将使用router.delete
方法创建一个DELETE
端点。为此,请编写以下代码:
...
router.delete('/:id', verifyToken, (req, res) => {
const db = new sqlite3.Database('./db.sqlite');
const { id } = req.params
db.serialize(() => {
const stmt = db.prepare("DELETE FROM bookings WHERE id = (?)");
stmt.run(id)
stmt.finalize();
res.json({ status: 'success' })
})
db.close();
});
...
这里,我们有/:id
路径。:id
是路由的 URL 参数占位符。我们还在booking.js
文件顶部导入了verifyToken
中间件。在继续运行路由处理程序的代码之前,我们可以使用它来验证令牌。要使此路由中的令牌通过身份验证,则意味着此路由标头中的令牌调用需要成功。
在路由处理程序中,我们从req.params
属性获取id
URL 参数。然后,我们调用db.serialize
,就像我们之前的路线一样。在回调中,我们有准备好的语句,因此我们可以使用在stmt.run
方法中设置的id
值发出一个DELETE
SQL 命令。然后,我们像在其他路线上一样,叫stmt.finalize
、res.json
和db.close
。
最后,在booking.js
文件的末尾,我们添加以下内容:
module.exports= = router;
添加前面的语句允许我们将其导入另一个文件以注册路由。注册路由将使其可访问。
接下来,我们将在routes
文件夹中创建catalog.js
文件。该文件是一个带有 API 端点的router
模块,用于添加我们的度假包。首先,我们从以下方面着手:
const express = require('express');
const router = express.Router();
const sqlite3 = require('sqlite3').verbose();
const verifyToken = require('../middlewares/verify-token')
router.get('/', (req, res,) => {
const db = new sqlite3.Database('./db.sqlite');
db.serialize(() => {
db.all("SELECT * FROM catalog_items", [], (err, rows = [])
=> {
res.json(rows)
});
})
db.close();
});
...
这与bookings.js
中的路线GET
路线几乎相同;然而,在这里,我们从catalog_items
表中检索所有项目。
接下来,让我们添加一个POST
路由,将一个条目添加到catalog_items
表中。编写以下代码:
...
router.post('/', verifyToken, (req, res,) => {
const { name, description, imageUrl } = req.body
const db = new sqlite3.Database('./db.sqlite');
db.serialize(() => {
const stmt = db.prepare(`
INSERT INTO catalog_items (
name,
description,
image_url
) VALUES (?, ?, ?)
`
);
stmt.run(name, description, imageUrl)
stmt.finalize();
res.json({ status: 'success' })
})
db.close();
});
...
这里,我们在第二个参数中有verifyToken
在第三个参数中运行路由处理程序之前检查该路由中的令牌。
接下来,我们添加一个路由,使我们能够删除catalog_items
条目。我们使用以下代码执行此操作:
...
router.delete('/:id', verifyToken, (req, res,) => {
const { id } = req.params
const db = new sqlite3.Database('./db.sqlite');
db.serialize(() => {
const stmt = db.prepare("DELETE FROM catalog_items WHERE
id = (?)");
stmt.run(id)
stmt.finalize();
res.json({status:'success'})
db.close();
});
...
最后,我们导出路由:
module.exports = router;
本模块与booking.js
没有太大区别。
接下来,我们删除routes/users.js
文件的内容,如果它不存在,则创建它。然后,我们添加以下代码:
const express = require('express');
const jwt = require('jsonwebtoken');
const router = express.Router();
router.post('/login', (req, res) => {
const { username, password } = req.body
if (username === 'admin' && password === 'password') {
res.json({ token: jwt.sign({ username }, 'secret') })
}
res.status(401)
});
module.exports= = router;
这是我们检查管理员用户的username
和password
是否有效的地方。我们只有一个用户在这里检查,以保持项目的简单。我们从包含 JSON 请求对象的req.body
对象获得username
和password
。然后,我们用if
语句检查username
和password
,如果if
中的表达式返回true
,我们调用jwt.sign
使用第一个参数中的令牌数据和第二个参数中的secret
创建令牌。然后,我们返回带有身份验证令牌的响应,其中包含[T12]。
否则,我们用401
调用res.status
返回401
响应,因为username
或password
无效。
接下来,我们在app.js
中注册我们的router
模块和全局中间件。为此,我们编写以下代码:
...
const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const catalogRouter = require('./routes/catalog');
const bookingsRouter = require('./routes/bookings');
const app = express();
const cors = require('cors')
...
app.use('/users', usersRouter);
app.use('/catalog', catalogRouter);
app.use('/bookings', bookingsRouter);
我们使用require
导入前面导出的router
模块和router
文件的最后一行。然后我们导入cors
模块:
const cors = require('cors')
我们调用app.use
来添加cors
中间件,然后添加router
模块。在最后三行中,我们传入path
作为第一个参数,router
模块作为第二个参数。这使我们能够访问先前创建的端点。通过cors
模块,我们可以在我们的 Express app 中启用跨域通信。
接下来,让我们创建 SQL 脚本,以便可以轻松地删除和创建表。为此,在backend
文件夹中创建db.sql
文件,并编写以下代码:
DROP TABLE IF EXISTS bookings;
DROP TABLE IF EXISTS catalog_items;
CREATE TABLE catalog_items (
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL,
image_url TEXT NOT NULL
);
CREATE TABLE bookings (
id INTEGER NOT NULL PRIMARY KEY,
catalog_item_id INTEGER NOT NULL,
name TEXT NOT NULL,
address TEXT NOT NULL,
start_date TEXT NOT NULL,
end_date TEXT NOT NULL,
FOREIGN KEY (catalog_item_id) REFERENCES catalog_items(id)
);
在这里,我们创建了bookings
和catalog_items
表。每个表都有不同的字段。TEXT
创建一个文本列。NOT NULL
使列不可为空。PRIMARY KEY
表示该列为主键列。FOREIGN KEY
表示一列是另一列的外键。
我们可以通过安装 SQLite 程序的 DB 浏览器来运行 SQL 代码,该程序可以在下载 https://sqlitebrowser.org/ ,然后在backend
文件夹中创建db.sqlite
。然后,我们可以进入执行 SQL选项卡,将代码粘贴到文本输入中。接下来,我们可以选择所有文本并按F5运行代码。这将删除任何现有的bookings
和catalog_items
表,并再次创建它们。要将数据库的更改写入磁盘,必须保存这些更改。要执行此操作,请单击文件菜单,然后单击写入更改。我们也可以按[T17]Ctrl+S[T18]组合键保存更改。
最后,为了让我们的应用在更改代码时自动运行和重新启动,我们可以在全局范围内安装nodemon
包。要执行此操作,请运行以下命令:
npm i -g nodemon
然后,在package.json
文件中,将script.start
属性的值更改为以下代码:
{
...
"scripts": {
"start": "nodemon ./bin/www"
},
...
}
我们可以使用nodemon
来运行npm start
,而不是常规的节点可执行文件,这意味着当我们更改任何代码文件并保存它时,应用将自动重启。
现在我们已经为前端用户创建了一个基本的后端,我们可以继续使用 PrimeVue 和 Vue 3 创建前端应用。
创建管理前端
既然后端应用已经完成,我们就可以开始管理前端的工作了。我们之前已经在admin-frontend
文件夹中为管理员前端创建了 Vue 3 项目,所以我们只需要安装所需的软件包并编写代码。我们需要 PrimeVue 软件包,即 Vee Validate、Vue 路由、Axios 和 Yup 软件包。
要安装它们,请在admin-frontend
文件夹中运行以下命令:
npm i axios primeflex primeicons primevue@^3.1.1 vee-validate@next vue-router@4 yup
Axios 允许我们向后端发出 HTTP 请求。Vue 路由允许我们将 URL 映射到page
组件。Vee Validate 和 Yup 允许我们轻松地将表单验证添加到表单中,其余的包是 PrimeVue 包。
创建管理前端页面
安装完软件包后,我们可以开始代码工作。首先,我们将处理组件。在components
文件夹中添加CatalogForm.vue
文件,并编写以下代码:
<template>
<Form @submit="onSubmit" :validation-schema="schema">
<Field v-slot="{ field, errors }" v-model="name"
name="name">
<div class="p-col-12">
<div class="p-inputgroup">
<InputText
placeholder="Name"
:class="{ 'p-invalid': errors.length > 0 }"
v-bind="field"
/>
</div>
<small class="p-error" v-if="errors.length > 0">
Name is invalid.
</small>
</div>
</Field>
...
</Form>
</template>
这里,我们有来自 Vee Validate 包的Form
组件来添加一个带有表单验证的表单。只有当所有表单值都有效时才会发出submit
事件。稍后我们将注册Form
组件。validation-schema
属性设置为 Yup 创建的验证模式对象。
在Form
组件中,我们有一个Field
组件,它也是由 Vee 验证包提供的。稍后,我们还将全局注册此组件,以便使用它。在Field
组件中,我们有InputText
组件向我们的应用添加一个输入字段。为了启用InputText
组件的表单验证,我们将field
对象传递给slot
道具,并将整个内容作为v-bind
指令的值传递。v-bind
指令允许 Vee Validate 处理表单值,并将验证添加到form
字段。[T10]数组为我们提供了可能发生的任何验证错误。
p-col-12
类由 PrimeVue 的 PrimeFlex 包提供。它允许我们将div
标记的宽度设置为全宽,这意味着它从页面上的 12 列中取出 12 列。通过p-inputgroup
类,我们可以创建一个输入组。p-error
类将文本颜色设置为红色,这样我们就可以以用户容易看到的方式显示表单验证消息。p-invalid
类使输入的边缘变为红色。如果误差长度大于0
,我们仅将其更改为红色,因为这意味着存在验证误差,并且当误差长度大于0
时,我们仅显示较小的元素。
Field
组件具有v-model
指令,用于将输入值绑定到相应的反应属性。我们还有一个name
属性,该属性也用作submit
事件处理程序的value
参数的属性名,该参数具有输入值。这些值始终有效,因为提交处理程序仅在所有表单值都有效时运行。
通过name
字段,我们可以输入vacation
包的名称。
接下来,我们需要添加一个文本区域,允许用户输入假期包的描述。为此,请编写以下代码:
<template>
<Form @submit="onSubmit" :validation-schema="schema">
...
<Field v-slot="{ field, errors }" v-model="description"
name="description">
<div class="p-col-12">
<div class="p-inputgroup">
<Textarea
placeholder="Description"
:class="{ 'p-invalid': errors.length > 0 }"
v-bind="field"
/>
</div>
<small class="p-error" v-if="errors.length > 0">
Description is invalid
</small>
</div>
</Field>
...
</Form>
</template>
这与name
字段几乎相同;然而,在这个场景中,我们将InputText
组件切换为Textarea
组件。我们还更改了v-model
和name
值。Textarea
组件来自 PrimeVue 软件包,该软件包渲染成具有自己风格的textarea
元素。
接下来,我们添加图像 URL 字段,以便为假期包添加图像 URL。我们只是让用户输入图像 URL,以简化我们的项目。要将字段添加到Form
组件,请编写以下代码:
<template>
<Form @submit="onSubmit" :validation-schema="schema">
...
<Field v-slot="{ field, errors }" v-model="imageUrl"
name="imageUrl">
<div class="p-col-12">
<div class="p-inputgroup">
<InputText
placeholder="Image URL"
:class="{ 'p-invalid': errors.length > 0 }"
v-bind="field"
/>
</div>
<small class="p-error" v-if="errors.length > 0">
Image URL is invalid.
</small>
</div>
</Field>
...
</Form>
</template>
这只是另一个具有不同名称和v-model
值的文本输入。最后,让我们使用以下代码在表单中添加一个submit
按钮:
<template>
<Form @submit="onSubmit" :validation-schema="schema">
...
<div class="p-col-12">
<Button label="Add" type="submit" />
</div>
</Form>
</template>
Button
组件来自 PrimeVue 软件包,我们稍后将在全球注册该软件包,使其在任何地方都可用。
接下来,我们添加component
选项对象。我们使用component
选项 API 创建组件。首先,我们导入所有内容并使用 Yup 库创建表单验证模式。要添加代码,请在components/CatalogForm.vue
中写入以下内容:
<script>
import * as yup from "yup";
import axios from "axios";
import { APIURL } from "@/constants";
const schema = yup.object().shape({
name: yup.string().required(),
description: yup.string().required(),
imageUrl: yup.string().url().required(),
});
export default {
name: "BookingForm",
data() {
return {
name: "",
description: "",
imageUrl: "",
schema,
};
},
...
};
</script>
在这里,我们使用yup.object
方法创建模式对象,它允许我们验证具有某些属性的对象。validation
模式与v-model
绑定是分开的。传递到shape
方法的对象属性必须与Field
组件的name
属性值匹配。
为了验证将name
设置为name
的字段的值,我们将name
属性设置为yup.string().required()
,以确保name
字段是一个字符串并且有一个值。我们为description
设置了相同的值。将imageUrl
值设置为yup.string().url().required()
,以确保输入的值是一个 URL 且已填写。
data
方法返回模式,以便我们可以使用Form
组件的validation-schema
属性。
为了完成组件,我们添加了onSubmit
方法,当Form
组件发出submit
事件时调用该方法:
<script>
...
export default {
...
methods: {
async onSubmit(value) {
const { name, description, imageUrl } = value;
await axios.post(`${APIURL}/catalog`, {
name,
description,
imageUrl,
});
this.$emit("catalog-form-close");
},
},
};
</script>
这里,我们只需从具有有效表单字段值的value
参数中获取property
值。然后,我们向目录端点发出 POST 请求,并将 JSON 有效负载传递到第二个参数中。接下来,我们调用this.$emit
方法发出catalog-form-close
事件,向对话框组件发出信号,表示该表单将被封装在其中以关闭。
添加顶部栏和菜单栏
接下来,我们将在应用中添加一个top bar
组件。为此,请在src/components
文件夹中创建TopBar.vue
。然后,将以下模板代码添加到文件中:
<template>
<Menubar :model="items">
<template #start>
<b>Admin Frontend</b>
</template>
</Menubar>
</template>
Menubar
组件由 PrimeVue 组件提供。我们可以使用它添加一个菜单栏,其中包含一些项目,我们可以单击这些项目导航到不同的页面。model
属性被设置为items
被动属性,这是一个我们将很快添加的菜单项对象数组。start
插槽允许我们将项目添加到菜单栏的左侧。我们可以将一些粗体文本放入插槽,它将显示在左侧。
接下来,我们可以为组件添加一个component
对象。要添加它,请编写以下代码:
<script>
export default {
name: "TopBar",
props: {
title: String,
},
data() {
return {
items: [
{
label: "Manage Bookings",
command: () => {
this.$router.push("/bookings");
},
},
...
methods: {
logOut() {
localStorage.clear();
this.$router.push("/");
},
},
beforeMount() {
document.title= = this.title;
},
};
</script>
这里,我们注册title
道具,用于设置document.title
值。document.title
属性在顶部栏上设置标题。在data
方法中,我们返回一个具有该项被动属性的对象。这被设置为具有label
和command
属性的对象。label
属性显示在用户的菜单栏项中。该项目显示为链接。当我们点击项目时,command
方法运行。
通过this.$router.push
方法,我们可以导航到映射到给定 URL 的页面。logOut
方法导航回映射到/
路径的页面,这是登录页面,我们将在后面讨论。此外,我们清除本地存储,以便可以清除身份验证令牌。
在beforeMount
钩子中,我们将document.title
属性设置为title
属性的值。
添加共享代码以处理请求
接下来,让我们为 Axios 请求拦截器编写代码,让我们将身份验证令牌添加到除向/login
端点发出请求之外的所有请求的x-token
请求头中。为此,创建src/plugins
文件夹并将axios.js
添加到其中。然后,在此文件中,编写以下代码:
import axios from 'axios'
import { APIURL } from '@/constants'
axios.interceptors.request.use((config) => {
if (config.url.includes(APIURL)
&& !config.url.includes('login')) {
config.headers['x-token'] = localStorage.getItem('token')
return config
}
return config
}, (error) => {
return Promise.reject(error)
})
在这里,我们通过检索带有config.url
属性的 URL 来检查请求所针对的 URL。然后,如果我们向/login
以外的端点发出任何请求,我们将设置x-token
请求头:
config.headers['x-token'] = localStorage.getItem('token')
请注意,我们从本地存储获取令牌,并将其设置为值config.headers['x-token']
。config.headers
属性是具有请求头的对象。第二个参数是请求错误处理程序。在这里,我们只需返回一个被拒绝的带有Promise.reject
的承诺,以便处理错误。
接下来,我们将 Vue 路由路由添加到路由中。我们留在src/plugins
文件夹中并创建一个vue-router.js
文件。然后,我们将以下代码添加到该文件中:
import { createWebHashHistory, createRouter } from 'vue-
router'
import Login from '../views/Login.vue'
import Bookings from '../views/Bookings.vue'
import Catalog from '../views/Catalog.vue'
const beforeEnter = (to, from, next) => {
try {
const token = localStorage.getItem('token')
if (to.fullPath !== '/' && !token) {
return next({ fullPath: '/' })
}
return next()
} catch (error) {
return next({ fullPath: '/' })
}
}
const routes = [
{ path: '/', component: Login },
{ path: '/bookings', component: Bookings, beforeEnter },
{ path: '/catalog', component: Catalog, beforeEnter },
]
const router = createRouter({
history: createWebHashHistory(),
routes,
})
export default router
我们添加了beforeEnter
功能,如果我们转到除主页之外的任何前端页面,则检查令牌。我们可以使用to.fullPath
属性检查用户尝试进入的路径。如果它不是'/'
并且本地存储中没有令牌,那么我们用一个对象调用next
,并且fullPath
属性设置为'/'
进入登录页面。否则,我们调用next
时没有任何参数转到我们应该去的页面。如果我们有一个错误,那么我们也会转到登录页面,正如您可以从catch
块中的代码中看到的那样。
接下来,我们有一个带有路由定义的routes
数组。它具有path
属性中的route
路径,component
是路径映射到的组件。beforeEnter
属性被添加到最后两个 route 对象中,这样我们只有在登录后才能到达那里。
然后,为了创建router
对象,我们使用createWebHashHistory
函数返回的对象设置了history
属性的对象调用createRouter
;这样我们就可以在主机名和 URL 的其余部分之间保留哈希。我们将routes
属性设置为routes
数组以注册路由。这样,当我们转到路线时,可以看到正确的组件。
最后,我们将router
对象导出为默认导出,以便稍后可以使用app.use
将路由对象添加到我们的应用中。
接下来,我们在src
文件夹中创建views
文件夹。这意味着我们可以添加用户可以访问的页面。现在,让我们添加一个页面,允许管理员通过将Bookings.vue
文件添加到src/views
文件夹来管理任何预订。我们打开文件并将以下模板添加到组件中。这样我们就可以添加我们之前创建的TopBar
组件:
<template>
<TopBar title="Manage Bookings" />
<div class="p-col-12">
<h1>Manage Bookings</h1>
</div>
<div class="p-col-12">
<Card v-for="b of bookings" :key="b.id">
<template #title>{{ b.name }} </template>
<template #content>
<p>Address: {{ b.address }}</p>
<p>Description: {{ b.description }}</p>
<p>Start Date: {{ b.start_date }}</p>
<p>End Date: {{ b.end_date }}</p>
</template>
<template #footer>
<Button
icon="pi pi-times"
label="Cancel"
class="p-button-secondary"
@click="deleteBooking(b.id)"
/>
</template>
</Card>
</div>
</template>
注意,我们使用h1
元素为页面添加标题。然后,我们添加Card
组件以向管理员显示预订情况。Card
组件由 PrimeVue 提供,我们稍后会注册。我们使用v-for
指令将 bookings 数组呈现为多个Card
组件。key
道具设置为唯一 ID,以便 Vue 3 能够正确区分每个道具。
我们用不同的内容填充title
、content
和footer
插槽。footer
插槽有一个Button
组件,当我们点击按钮时,该组件将运行deleteBooking
功能。icon
道具允许我们设置按钮左侧的图标。label
道具的button
文本位于图标右侧。通过p-button-secondary
类,我们可以设置按钮的颜色。
接下来,我们可以使用getBooking
和deleteBooking
方法添加component
选项对象,分别通过后端 API 检索预订和删除预订。要添加它们,请编写以下代码:
<script>
import axios from "axios";
import { APIURL } from "@/constants";
import TopBar from "@/components/TopBar";
export default {
name: "Bookings",
components: {
TopBar,
},
data() {
return {
bookings: [],
};
},
...
beforeMount() {
this.getBookings();
},
};
</script>
我们还在components
属性中注册TopBar
组件。getBookings
方法调用axios.get
发出 GET 请求,并将this.bookings
reactive 属性的值设置为响应对象。
bookings
存储在作为返回承诺的解析值返回的对象的data
属性中。
同样地,我们在deleteBooking
方法中调用axios.delete
来发出DELETE
请求删除项目。然后,我们调用this.getBookings
再次获取数据。我们还在beforeMount
钩子中调用this.getBookings
,以便在页面加载时获取数据。
接下来,我们添加一个页面,允许管理员管理假期包项目。为此,让我们将Catalog.vue
文件添加到src/views
文件夹中。然后,在文件中写入以下内容:
<template>
<TopBar title="Manage Vacation Packages" />
<div class="p-col-12">
<h1>Manage Vacation Packages</h1>
</div>
<div class="p-col-12">
<Button label="Add Vacation Package"
@click="displayCatalog= = true" />
<Dialog header="Add Vacation Package" v-
model:visible="displayCatalog">
<CatalogForm
@catalog-form-close="
displayCatalog= = false;
getCatalog();
"
/>
</Dialog>
</div>
...
</template>
在这里,我们添加TopBar
组件来显示顶部栏;h1
显示一个标题。接下来,我们添加一个按钮,通过将displayCatalog
设置为true
来显示对话框。然后,我们通过将带有可见修饰符的v-model
指令设置为displayCatalog
值来显示Dialog
组件。使用此功能,我们可以控制Dialog
组件何时显示。Dialog
组件显示一个对话框,该组件由 PrimeVue 提供。
header
道具设置对话框的标题文本。我们使用CatalogForm
作为内容,并监听CatalogForm
组件发出的catalog-form-close
事件。当它发出时,我们将displayCatalog
设置为false
并调用getCatalog
再次获取数据:
<template>
...
<div class="p-col-12">
<Card v-for="c of catalog" :key="c.id">
<template #header>
<img :alt="c.description" :src="c.image_url" />
</template>
<template #title> {{ c.name }} </template>
<template #content>
{{ c.description }}
</template>
<template #footer>
<Button
icon="pi pi-times"
label="Delete"
class="p-button-secondary"
@click="deleteCatalogItem(c.id)"
/>
</template>
</Card>
</div>
</template>
接下来,我们使用[T1]指令添加从 catalog reactive 属性呈现的[T0]组件,以呈现目录条目。剩下的代码与我们在Bookings.vue
文件中的代码类似,但是现在渲染属性不同了,当我们单击Button
时,它会调用不同的方法。
接下来,我们通过向src/views/Catalog.vue
添加以下代码来添加组件对象:
<script>
import axios from "axios";
import { APIURL } from "@/constants";
import TopBar from "@/components/TopBar";
import CatalogForm from "@/components/CatalogForm";
...
methods: {
async getCatalog() {
const{ { data } = await axios.get(`${APIURL}/catalog`);
this.catalog = data;
async deleteCatalogItem(id) {
await axios.delete(`${APIURL}/catalog/${id}`);
this.getCatalog();
},
},
beforeMount() {
this.getCatalog();
},
};
</script>
在这里,代码与我们在src/views/Bookings.vue
中的代码类似,只是在这里,我们向目录端点发出请求以获取和删除数据。
然后,我们在管理员前端应用中创建最后一个页面,即登录页面。为了添加登录页面,我们将Login.vue
文件添加到src/views
文件夹中。然后,在文件中,我们使用以下代码添加form
和username
字段:
<template>
<Form @submit="onSubmit" :validation-schema="schema">
<div class="p-col-12">
<h1>Admin Log In</h1>
</div>
<Field v-slot="{ field, errors }" v-model="username"
name="username">
<div class="p-col-12">
<div class="p-inputgroup">
<InputText
placeholder="Username"
:class="{ 'p-invalid': errors.length > 0 }"
v-bind="field"
/>
</div>
<small class="p-error" v-if="errors.length > 0">
Username is invalid.
</small>
</div>
</Field>
...
</Form>
</template>
[T0]字段与我们之前添加的所有其他字段类似。接下来,我们使用以下代码添加password
输入和按钮:
<template>
<Form @submit="onSubmit" :validation-schema="schema">
...
<Field v-slot="{ field, errors }" v-model="password"
name="password">
<div class="p-col-12">
<div class="p-inputgroup">
<InputText
placeholder="Password"
type="password"
:class="{ 'p-invalid': errors.length > 0 }"
v-bind="field"
/>
</div>
<small class="p-error" v-if="errors.length > 0">
Password is invalid
</small>
</div>
</Field>
<div class="p-col-12">
<Button label="Log In" type="submit" />
</div>
</Form>
</template>
我们将type
属性设置为password
以使该字段成为密码输入。按钮的type
属性被设置为submit
,这样我们可以在点击它时触发submit
事件,并且所有表单值保持有效。
接下来,我们添加Login.vue
文件的组件对象部分,该部分使用onSubmit
方法进行登录请求:
<script>
import * as yup from "yup";
import axios from "axios";
import { APIURL } from "@/constants";
const schema = yup.object().shape({
username: yup.string().required(),
password: yup.string().required(),
});
export default {
name: "Login",
data() {
return {
username: "",
password: "",
schema,
};
},
methods: {
async onSubmit(values) {
const { username, password } = values;
try {
const {
data: { token },
} = await axios.post(`${APIURL}/users/login`, {
username,
password,
});
localStorage.setItem("token", token);
this.$router.push("/bookings");
} catch (error) {
alert("Login failed");
}
},
},
};
</script>
我们使用验证模式创建schema
对象,这与我们之前使用的其他模式类似。然后,我们将其添加到我们在data
方法中返回的对象中。onSubmit
方法从value
参数中获取username
和password
属性,以便我们可以使用它向/users/login
端点发出 POST 请求。
一旦我们这样做了,如果请求与localStorage.setItem
方法一起成功,我们将从响应中获得一个令牌。接下来,我们调用this.$router.push
方法重定向到/bookings
URL。如果有任何错误,我们将显示带有"Login failed"
消息的警报。
接下来,我们将 Vue 路由提供的router-view
组件添加到App.vue
中。这样我们就可以显示我们在routes
对象中创建的页面。要添加它,请编写管理员前端:共享代码,添加以处理请求“以下代码:
<template>
<router-view></router-view>
</template>
<script>
export default {
name: "App",
};
</script>
<style>
body {
background-color: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI,
Roboto, Helvetica,
Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji,
Segoe UI Symbol;
font-weight: normal;
color: #495057;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin: 0px;
}
</style>
我们还有一个style
标签来设置字体系列,并将正文的边距设置为0px
,因此元素和页面边缘之间没有空白。
接下来,我们将constants.js
添加到src
文件夹中,然后将APIURL
添加到其中:
export const APIURL = 'http://localhost:3000'
在main.js
中,我们注册了所有全局组件以及我们之前创建的路由对象。我们还导入 PrimeVue 提供的全局样式,因此一切看起来都很好:
import { createApp } from 'vue'
import App from './App.vue'
import PrimeVue from 'primevue/config';
import InputText from "primevue/inputtext";
import Button from "primevue/button";
import Card from 'primevue/card';
import Toolbar from 'primevue/toolbar';
import router from './plugins/vue-router'
import Textarea from 'primevue/textarea';
import Dialog from 'primevue/dialog';
import Menubar from 'primevue/menubar';
import { Form, Field } from "vee-validate";
import "primeflex/primeflex.css";
import 'primevue/resources/themes/bootstrap4-light-blue/theme.css'
import "primevue/resources/primevue.min.css";
import "primeicons/primeicons.css";
import './plugins/axios'
...
app.component("Form", Form);
app.component("Field", Field);
app.use(PrimeVue);
app.use(router)
app.mount('#app')
在package.json
中,我们通过将script.serve
属性更改为以下内容来更改开发服务器运行的端口:
{
...
"scripts": {
"serve": "vue-cli-service serve --port 8082",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
...
}
现在,当我们运行npm run serve
时,我们得到以下屏幕截图:
图 6.1–管理前端
现在,我们已经创建了管理前端应用,我们只需要添加用户前端。
创建用户前端
现在我们已经完成了管理前端的,我们将通过创建用户的前端来完成本章的项目。用户前端与管理员前端类似;但是,在这种情况下,使用它不需要身份验证。
我们将首先安装我们为管理前端安装的相同软件包。导航到前端文件夹并运行以下命令:
npm i axios primeflex primeicons primevue@^3.1.1 vee-validate@next vue-router@4 yup
接下来,创建src/components
文件夹,如果它不存在。然后,在src/components
中创建BookingForm.vue
文件,以便用户可以添加他们的预订。
添加form
和name
字段,允许用户输入姓名:
<template>
<Form @submit="onSubmit" :validation-schema="schema">
<Field v-slot="{ field, errors }" v-model="name"
name="name">
<div class="p-col-12">
<div class="p-inputgroup">
<InputText
placeholder="Name"
:class="{ 'p-invalid': errors.length > 0 }"
v-bind="field"
/>
</div>
<small class="p-error" v-if="errors.length > 0">Name
is invalid </small>
</div>
</Field>
...
</Form>
</template>
这与我们前面添加的其他文本输入字段非常相似。然后,使用以下代码添加address
字段:
<template>
<Form @submit="onSubmit" :validation-schema="schema">
...
<Field v-slot="{ field, errors }" v-model="address"
name="address">
<div class="p-col-12">
<div class="p-inputgroup">
<InputText
placeholder="Address"
:class="{ 'p-invalid': errors.length > 0 }"
v-bind="field"
/>
</div>
<small class="p-error" v-if="errors.length > 0"
>Address is invalid</small
>
</div>
</Field>
...
</Form>
</template>
现在,让我们添加 PrimeVue 提供的Calendar
组件,这是我们在本项目中没有使用过的。Calendar
组件允许用户选择日期。我们可以添加Start Date
字段,允许用户选择假期的开始日期:
<template>
<Form @submit="onSubmit" :validation-schema="schema">
...
<Field v-slot="{ field, errors }" v-model="startDate" name="startDate">
<div class="p-col-12">
<label>Start Date</label>
<div class="p-inputgroup">
<Calendar
inline
placeholder="Start Date"
:class="{ 'p-invalid': errors.length > 0 }"
:minDate="new Date()"
v-bind="field"
v-model="startDate"
/>
</div>
<small class="p-error" v-if="errors.length > 0">
Start date is invalid
</small>
</div>
</Field>
...
</Form>
</template>
在这里,我们有minDate
道具,它设置了用户可以选择的最早日期。inline
道具将使日期选择器显示在表单上而不是弹出窗口中。同样,我们可以使用以下代码添加End Date
字段:
<template>
<Form @submit="onSubmit" :validation-schema="schema">
...
<Field v-slot="{ field, errors }" v-model="endDate" name="endDate">
<div class="p-col-12">
<label>End Date</label>
<div class="p-inputgroup">
<Calendar
inline
placeholder="End Date"
:class="{ 'p-invalid': errors.length > 0 }"
v-bind="field"
v-model="endDate"
:minDate="new Date(+startDate + 24 * 3600 * 1000)"
/>
</div>
<small class="p-error" v-if="errors.length > 0"
>End date is invalid</small
>
</div>
</Field>
...
</Form>
</template>
这里,我们将minDate
道具设置为startDate
之后的一天。24 * 3600 * 1000
毫秒相当于一天。最后,我们添加了submit
按钮,就像我们在其他表单中所做的那样:
<template>
<Form @submit="onSubmit" :validation-schema="schema">
...
<div class="p-col-12">
<Button label="Book" type="submit" />
</div>
...
</Form>
</template>
接下来,我们通过编写以下内容创建schema
:
<script>
import { Form, Field } from "vee-validate";
import * as yup from "yup";
import axios from "axios";
import { APIURL } from "@/constants";
const schema = yup.object().shape({
name: yup.string().required(),
address: yup.string().required(),
startDate: yup.date().required().min(new Date()),
endDate: yup
.date()
.required()
.when(
"startDate",
(startDate, schema) => startDate &&
schema.min(startDate)
),
});
...
</script>
为了验证endDate
,我们使用要检查的field
名称调用when
方法。然后,我们调用schema.min
以确保的endDate
晚于startDate
。
接下来,我们添加component
对象来注册selectedCatalogId
道具,并添加onSubmit
方法。我们编写以下代码:
<script>
...
export default {
name: "BookingForm",
components: {
Form,
Field,
},
props: {
...
methods: {
async onSubmit(values) {
const { name, address, startDate, endDate } = values;
await axios.post(`${APIURL}/bookings`, {
name,
address,
startDate,
endDate,
catalogItemId: this.selectedCatalogId,
});
this.$emit("booking-form-close");
},
},
};
</script>
onSubmit
方法从values
参数获取表单字段值,并向预订端点发出 POST 请求以添加预订。我们使用selectedCatalogId
添加预订。然后,我们发出booking-form-close
事件,向父级发出事件,通知窗体关闭。
接下来,我们通过将vue-router.js
添加到src/plugins
文件夹,将 Vue 路由添加到我们的应用中:
import { createWebHashHistory, createRouter } from 'vue-router'
import Catalog from '../views/Catalog.vue'
const routes = [
{ path:'/', component: Catalog },
]
const router = createRouter({
history: createWebHashHistory(),
routes,
})
export default router
这与我们在管理前端中所做的非常相似。
接下来,我们创建一个页面,通过添加src/views/Catalog.vue
文件,然后添加以下模板代码,向用户显示所有假期套餐:
<template>
<Card v-for="c of catalog" :key="c.id">
<template #header>
<img :alt="c.description" :src="c.image_url" />
</template>
<template #title> {{ c.name }} </template>
<template #content>
{{ c.description }}
</template>
<template #footer>
<Button
icon="pi pi-check"
label="Book"
class="p-button-secondary"
@click="book(c.id)"
/>
...
:selectedCatalogId="selectedCatalogId"
/>
</Dialog>
</template>
</Card>
</template>
这里,我们只是从目录数组中呈现一个表单。我们有一个Dialog
组件,里面有BookingForm
组件。我们监听它发出的booking-form-close
事件,通过将displayBookingForm
设置为false
并调用displayMessage
显示警报来关闭Dialog
组件。我们传入selectedCatalogId
作为同名道具的值。
模板代码的其余部分几乎与我们之前的相同,除了显示的属性名称和在标题槽中添加的图像。
接下来,我们通过编写以下代码将component
选项对象添加到同一个文件中:
<script>
import axios from "axios";
import { APIURL } from "@/constants";
import BookingForm from "../components/BookingForm.vue";
export default {
name: "Catalog",
components: {
BookingForm,
},
data() {
return {
selectedCatalogId: undefined,
displayBookingForm: false,
...
displayMessage() {
alert("Booking successful");
},
},
beforeMount() {
this.getCatalog();
},
};
</script>
我们在components
属性中注册BookingForm
组件。getCatalog
函数从 API 获取度假包目录项。booking
功能将displayBookingForm
设置为true
打开Dialog
组件,并且selectedCatalogId
也设置在那里。beforeMount
钩子调用getCatalog
来检索目录数据。
添加路由视图和入口点代码
在App.vue
中,我们为添加router-view
编写以下代码,并设置与我们在管理前端相同的样式:
<template>
<router-view></router-view>
</template>
<script>
export default {
name: "App",
};
</script>
<style>
body {
background-color: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI,
Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji,
Segoe UI Emoji, Segoe UI Symbol;
font-weight: normal;
color: #495057;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin: 0px;
}
</style>
然后,我们在src
文件夹中创建constants.js
,并添加以下行以添加APIURL
:
export const APIURL = 'http://localhost:3000'
然后,在main.js
中,我们用以下代码替换文件内容,以全局注册组件和路由。我们还导入 PrimeVue 提供的样式,以使我们的应用看起来不错:
import { createApp } from 'vue'
import App from './App.vue'
import PrimeVue from 'primevue/config';
import InputText from "primevue/inputtext";
import Button from "primevue/button";
import Card from 'primevue/card';
import Toolbar from 'primevue/toolbar';
import Calendar from 'primevue/calendar';
import Dialog from 'primevue/dialog';
import router from './plugins/vue-router'
import "primeflex/primeflex.css";
import 'primevue/resources/themes/bootstrap4-light-blue/theme.css'
import "primevue/resources/primevue.min.css";
import "primeicons/primeicons.css";
const app = createApp(App)
app.component("InputText", InputText);
app.component("Button", Button);
app.component("Card", Card);
app.component("Toolbar", Toolbar);
app.component("Calendar", Calendar);
app.component("Dialog", Dialog);
app.use(PrimeVue);
app.use(router)
app.mount('#app')
在package.json
中,我们通过将script.serve
属性更改为以下内容来更改开发服务器运行的端口:
{
...
"scripts": {
"serve": "vue-cli-service serve --port 8082",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
...
}
现在,当我们运行npm run serve
时,我们得到以下屏幕截图:
图 6.2–用户前端
随着用户前端的创建,假期预订系统现在已经完成。
总结
在本章中,我们学习了如何有效地使用 PrimeVue 构建假期预订应用。使用 PrimeVue,我们可以轻松创建美观的 Vue 3 web 应用。PrimeVue 附带了许多有用的组件,我们可以添加这些组件来创建 web 应用,例如输入、文本区域、表格、对话框、日期选择器等等。它还内置了样式,因此我们不必自己添加任何样式。此外,我们还可以添加 PrimeVue 提供的 PrimeFlex 包;使用 flexbox,我们可以轻松更改元素和组件的间距和位置。
Vee Validate 和 Yup 允许我们将表单验证添加到 Vue 3 应用中。这很容易与 PrimeVue 提供的输入组件集成。这两个库使许多表单验证工作变得简单,因为我们不必自己编写所有表单验证代码。
为了制作一个简单的后端,我们使用 Express 创建了一个简单的 API 来与前端交互。我们还使用sqlite3
包在 API 中使用 SQLite 数据库操作数据。Express 附带了许多附加组件,我们可以使用这些附加组件添加许多功能,例如跨域通信。我们还可以通过jsonwebtoken
库轻松地将 JSON Web 令牌身份验证添加到我们的 Express 应用中。
在下一章中,我们将学习如何使用 GraphQL 和 Vue 3 构建店面。
版权属于:月萌API www.moonapi.com,转载请注明出处