使用 Laravel 构建 Vue SPA 第四部分

发布时间 作者

Building a Vue SPA with Laravel Part 4 image

我们在 第三部分 中停止了构建一个真实的“用户”端点,并学习了一种使用 Vue 路由器获取组件数据的新方法。现在,我们准备将注意力转向为我们的用户创建 CRUD 功能——本教程将重点介绍编辑现有用户。

除了使用我们的第一个表单之外,我们还将有机会了解如何定义动态 Vue 路由。我们路由的动态部分将是用户 ID,它与他或她的数据库记录匹配。对于编辑用户,Vue 路由将如下所示

/users/:id/edit

此路由的动态部分是 :id 参数,它将取决于用户 ID。我们将使用数据库中的 id 字段,但您也可以使用 UUID 或其他内容。

设置

在我们专注于 Vue 组件之前,我们需要定义一个新的 API 端点来获取单个用户,然后我们还需要指定另一个端点来执行更新。

打开 routes/api.php 路由文件,并在获取所有用户的 index 路由下方添加以下路由

Route::namespace('Api')->group(function () {
Route::get('/users', 'UsersController@index');
Route::get('/users/{user}', 'UsersController@show');
});

使用 Laravel 的隐式路由模型绑定,我们的控制器方法很简单。将以下方法添加到 app/Http/Controllers/Api/UsersController.php 文件中

// app/Http/Controllers/Api/UsersController
 
public function show(User $user)
{
return new UserResource($user);
}

在类似 /api/users/1 的地址请求用户将返回以下 JSON 响应

{
"data": {
"name": "Antonetta Zemlak",
}
}

我们来自第三部分的 UserResource 需要更新以包含 id 列,因此您应该更新 app/Http/Resources/UserResource.php 文件以包含 id 数组键。我将在此处粘贴来自第三部分的整个文件

<?php
 
namespace App\Http\Resources;
 
use Illuminate\Http\Resources\Json\Resource;
 
class UserResource extends Resource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
];
}
}

现在,我们的 /api/users/api/users/{user} 路由将响应 id 字段,我们需要在我们的路由中识别用户。

定义 UsersEdit Vue 组件

有了 show 路由,我们可以将注意力转向定义前端 Vue 路由和相应的组件。将以下路由定义添加到 resources/js/app.js 路由中。这是导入 UsersEdit 组件(我们尚未创建)以及整个路由实例的代码片段

import UsersEdit from './views/UsersEdit';
 
// ...
 
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/hello',
name: 'hello',
component: Hello,
},
{
path: '/users',
name: 'users.index',
component: UsersIndex,
},
{
path: '/users/:id/edit',
name: 'users.edit',
component: UsersEdit,
},
],
});

我们在 routes 配置的末尾添加了 users.edit 路由。

接下来,我们需要在 resources/assets/js/views/UsersEdit.vue 中创建 UsersEdit 组件,并使用以下组件代码

<template>
<div>
<form @submit.prevent="onSubmit($event)">
<div class="form-group">
<label for="user_name">Name</label>
<input id="user_name" v-model="user.name" />
</div>
<div class="form-group">
<label for="user_email">Email</label>
<input id="user_email" type="email" v-model="user.email" />
</div>
<div class="form-group">
<button type="submit">Update</button>
</div>
</form>
</div>
</template>
<script>
export default {
data() {
return {
user: {
id: null,
name: "",
email: ""
}
};
},
methods: {
onSubmit(event) {
// @todo form submit event
}
},
created() {
// @todo load user details
}
};
</script>

让我们先关注模板部分:我们在一个结束 div 周围渲染一个 <form>,因为很快我们将需要在加载用户数据之后有条件地显示表单。

<form> 标签有一个占位符 @submit 事件,我们定义了一个 onSubmit() 方法处理程序,它接收一个事件对象。我要提到的最后一件事是 <input> 元素上的 v-model 属性,它映射到相应的 data.users 对象字面量。我们已经为 idnameemail 填充了默认值。

此时,如果您加载 /users/1/edit,您将看到一个渲染的空表单

我们打算编辑现有用户,所以我们的下一步是弄清楚如何从路由中获取动态 :id 属性,并在 UsersEdit.vue 组件中加载用户数据。

使用专用客户端加载用户详细信息

在我们加载组件中的用户数据之前,我们将去执行一个辅助任务,将 /api/users 资源提取到一个专用 API 模块中,我们可以使用它来查询所有用户、单个用户和更新用户。

首先,我们将创建一个新文件夹和文件来容纳我们后端的 API 模块。您可以通过任何您喜欢的方式创建这些文件。我们将在 `Nix 命令行上从命令行演示

mkdir -p resources/assets/js/api/
touch resources/assets/js/api/users.js

users.js 组件将公开一些我们可以调用的函数,以便对 /api/users 资源进行操作。这个模块将会比较简单,但在以后可以让你在 API 请求之前之后进行任何映射、数据操作等。这个文件充当可重用 API 操作的存储库

import axios from 'axios';
 
export default {
all() {
return axios.get('/api/users');
},
find(id) {
return axios.get(`/api/users/${id}`);
},
update(id, data) {
return axios.put(`/api/users/${id}`, data);
},
};

现在,我们可以使用相同的模块来获取所有用户,以及查找和更新单个用户

// Get all users
client.all().then((data) => mapData);
 
// Find a user
client.find(userId);

目前,all() 方法不接受任何分页查询参数,但我留给你实现分页,并将我们在 UsersIndex.vue 组件上的内容替换为我们新的 all() 客户端函数。

从 UsersEdit 组件加载用户

现在我们有了可重用(尽管非常基础)的 API 客户端,我们可以让它工作来在渲染编辑页面时加载用户数据。

我们最初在我们的组件上填充了一个 created() 函数,我们将在这里请求用户数据

// UsersEdit.vue Component
<script>
import api from '../api/users';
 
export default {
// ...
created() {
api.find(this.$route.params.id).then((response) => {
this.loaded = true;
this.user = response.data.data;
});
}
}
</script>

我们的 created() 回调调用 users.js 客户端 find() 函数,该函数返回一个 Promise。在 Promise 回调中,我们设置了一个 loaded 数据属性(我们还没有创建)并设置 this.user 数据属性。

让我们将 loaded 属性添加到我们的 data 键中,并默认将其设置为 false

data() {
return {
loaded: false,
user: {
id: null,
name: "",
email: ""
}
};
},

由于我们的组件在 created() 内部加载数据,因此我们最初将在组件上显示一个条件“加载”消息

<div v-if="! loaded">Loading...</div>
<form @submit.prevent="onSubmit($event)" v-else>
<!-- ... -->
</form>

此时,如果您刷新页面,组件将短暂闪烁一个 Loading... 消息

然后用户数据应该填充表单

API 非常快,因此,如果您想验证条件是否有效,您可以调用 setTimeout 来延迟设置 this.user 数据属性

api.find(this.$route.params.id).then((response) => {
setTimeout(() => {
this.loaded = true;
this.user = response.data.data;
}, 5000);
});

上面的超时将显示加载消息五秒钟,然后设置 loadeduser 数据属性。

更新用户

我们准备连接 onSubmit() 事件处理程序,并通过 PUT /api/users/{user} API 端点更新用户。

首先,让我们添加 onSubmit() 代码,然后我们将转向 Laravel 后端,使其在数据库上执行更新

onSubmit(event) {
this.saving = true;
 
api.update(this.user.id, {
name: this.user.name,
email: this.user.email,
}).then((response) => {
this.message = 'User updated';
setTimeout(() => this.message = null, 2000);
this.user = response.data.data;
}).catch(error => {
console.log(error)
}).then(_ => this.saving = false);
},

我们使用当前用户的 ID 调用了 api.update() 函数,并传递了来自绑定表单输入的 nameemail 值。

然后我们在 Promise 对象上链接一个回调,以在 API 成功后设置成功消息并设置更新后的用户数据。在 2000 毫秒后,我们清除消息,这将有效地隐藏模板中的消息。

目前,我们正在捕获任何错误并将其记录到控制台中。在将来,我们可能会回头处理错误,例如服务器故障或验证错误,但目前,我们将跳过它,专注于成功状态。

我们使用 this.saving 来确定我们的组件是否正在更新用户。我们的模板确保在保存正在进行时禁用提交按钮,以避免使用绑定 :disabled 属性进行双重提交

<div class="form-group">
<button type="submit" :disabled="saving">Update</button>
</div>

API 请求完成后,我们要做的最后一件事是在 catch 之后链接另一个 then() 回调,将 this.saving 设置为 false。我们需要将此属性重置为 false,以便组件可以再次提交表单。我们的最后一个 then() 链使用 _ 下划线变量作为一些语言中的约定,表示这里有一个参数,但我们不需要使用它。您也可以定义带空括号的短箭头函数

.then(() => this.saving = false);

我们引入了两个新数据属性,需要将它们添加到我们的 data() 调用中

data() {
return {
message: null,
loaded: false,
saving: false,
user: {
id: null,
name: "",
email: ""
}
};
},

接下来,让我们更新我们的 <template> 以在设置时显示 message

<template>
<div>
<div v-if="message" class="alert">{{ message }}</div>
<div v-if="! loaded">Loading...</div>
<form @submit.prevent="onSubmit($event)" v-else>
<div class="form-group">
<label for="user_name">Name</label>
<input id="user_name" v-model="user.name" />
</div>
<div class="form-group">
<label for="user_email">Email</label>
<input id="user_email" type="email" v-model="user.email" />
</div>
<div class="form-group">
<button type="submit" :disabled="saving">Update</button>
</div>
</form>
</div>
</template>

最后,让我们在 `UsersEdit.vue` 文件底部添加一些关于警报消息的样式

<style lang="scss" scoped>
$red: lighten(red, 30%);
$darkRed: darken($red, 50%);
.form-group label {
display: block;
}
.alert {
background: $red;
color: $darkRed;
padding: 1rem;
margin-bottom: 1rem;
width: 50%;
border: 1px solid $darkRed;
border-radius: 5px;
}
</style>

我们已经完成了更新前端组件以处理提交的表单,并在 API 请求成功后相应地更新模板。现在我们需要将注意力转移回 API,将其连接起来。

在 API 后端更新用户

我们准备通过在 `User` 资源控制器上定义一个 `update` 方法来连接所有点。我们将在服务器端定义必要的验证。但是,我们还没有在前端将其连接起来。

首先,我们将为 `PUT /api/users/{user}` 请求在 `routes/api.php` 文件中定义一个新路由

Route::namespace('Api')->group(function () {
Route::get('/users', 'UsersController@index');
Route::get('/users/{user}', 'UsersController@show');
Route::put('/users/{user}', 'UsersController@update');
});

接下来,`UsersController@update` 方法将使用请求对象来验证数据并返回我们打算更新的字段。将以下方法添加到 `app/Http/Controllers/Api/UsersController.php` 文件中

public function update(User $user, Request $request)
{
$data = $request->validate([
'name' => 'required',
'email' => 'required|email',
]);
 
$user->update($data);
 
return new UserResource($user);
}

就像 `show()` 方法一样,我们使用隐式请求模型绑定从数据库加载用户。在验证必需字段后,我们更新用户模型并通过创建 `UserResource` 类的新实例返回更新后的模型。

对后端的成功请求将返回用户更新后的 JSON 数据,然后我们将其用于更新 Vue 组件中的 `this.user` 属性。

{
"data": {
"id": 1,
"name":"Miguel Boyle",
}
}

导航到编辑页面

我们一直在直接请求 `/users/:id/edit` 页面,但是我们还没有在界面中添加它。请随意尝试找出如何在看到我的方法之前动态导航到编辑页面。

以下是我如何在 `UsersIndex.vue` 模板(我们在 第 2 部分 中创建)中为 `/users` 索引页面上列出的每个用户添加编辑链接的方式

<ul v-if="users">
<li v-for="{ id, name, email } in users">
<strong>Name:</strong> {{ name }},
<strong>Email:</strong> {{ email }} |
<router-link :to="{ name: 'users.edit', params: { id } }">Edit</router-link>
</li>
</ul>

我们重新构建循环中的 `user` 对象以提供 `id`、`name` 和 `email` 属性。我们使用 `<router-link/>` 组件引用我们的 `users.edit` 命名路由,并使用 `params` 键传递 `id` 参数。

为了更好地可视化 `<router-link>` 属性,以下是我们之前添加的 `app.js` 文件中的路由定义

{
path: '/users/:id/edit',
name: 'users.edit',
component: UsersEdit,
},

如果你刷新应用程序或访问 `/users` 端点,你将看到以下内容

整合在一起

如果你现在编辑一个用户,后端应该保存它并返回 `200` 成功状态(如果一切顺利)。在 `PUT` 请求成功后,你应该看到以下内容,持续两秒钟

以下是完整的 `UsersEdit.vue` 组件,供你参考

<template>
<div>
<div v-if="message" class="alert">{{ message }}</div>
<div v-if="! loaded">Loading...</div>
<form @submit.prevent="onSubmit($event)" v-else>
<div class="form-group">
<label for="user_name">Name</label>
<input id="user_name" v-model="user.name" />
</div>
<div class="form-group">
<label for="user_email">Email</label>
<input id="user_email" type="email" v-model="user.email" />
</div>
<div class="form-group">
<button type="submit" :disabled="saving">Update</button>
</div>
</form>
</div>
</template>
<script>
import api from '../api/users';
 
export default {
data() {
return {
message: null,
loaded: false,
saving: false,
user: {
id: null,
name: "",
email: ""
}
};
},
methods: {
onSubmit(event) {
this.saving = true;
 
api.update(this.user.id, {
name: this.user.name,
email: this.user.email,
}).then((response) => {
this.message = 'User updated';
setTimeout(() => this.message = null, 10000);
this.user = response.data.data;
}).catch(error => {
console.log(error)
}).then(_ => this.saving = false);
}
},
created() {
api.find(this.$route.params.id).then((response) => {
setTimeout(() => {
this.loaded = true;
this.user = response.data.data;
}, 5000);
});
}
};
</script>
<style lang="scss" scoped>
$red: lighten(red, 30%);
$darkRed: darken($red, 50%);
.form-group label {
display: block;
}
.alert {
background: $red;
color: $darkRed;
padding: 1rem;
margin-bottom: 1rem;
width: 50%;
border: 1px solid $darkRed;
border-radius: 5px;
}
</style>

作业

在用户更新成功后,我们只需两秒钟后重置消息。更改行为以设置消息,然后将用户重定向回上一个位置(即 `/users` 索引页面)。

其次,在表单底部添加一个“返回”或“取消”按钮,该按钮将丢弃表单更新并导航回上一个页面。

如果你想冒险,在 `UsersEdit` 组件向 API 发送无效请求时显示验证错误。在成功提交表单后清除错误消息。

下一步

在更新用户之后,我们将注意力转移到删除用户。删除用户将有助于演示在成功删除后以编程方式导航。我们还将研究定义一个全局 404 页面,因为我们现在有用于编辑用户的动态路由。

如果你准备好了,请继续进行 第 5 部分

Paul Redmond photo

Laravel News 的特约撰稿人。全栈 Web 开发人员和作家。

归档于
Cube

Laravel 新闻

加入 40,000 多名其他开发人员,绝不错过新的提示、教程等。

Laravel Forge logo

Laravel Forge

轻松创建和管理您的服务器,并在几秒钟内部署您的 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发人员必备的代码运行器。使用 AI、自动完成和本地和生产环境的即时反馈来调试代码。

Tinkerwell
No Compromises logo

绝不妥协

Joel 和 Aaron 是来自“绝不妥协”播客的两名经验丰富的开发人员,现在可以为您的 Laravel 项目聘用。 ⬧ 固定价格 7500 美元/月。 ⬧ 无需漫长的销售流程。 ⬧ 无需合同。 ⬧ 100% 退款保证。

绝不妥协
Kirschbaum logo

Kirschbaum

提供创新和稳定性,以确保您的 Web 应用程序取得成功。

Kirschbaum
Shift logo

Shift

运行旧版本的 Laravel?即时、自动化的 Laravel 升级和代码现代化,使您的应用程序保持新鲜。

Shift
Bacancy logo

Bacancy

只需 2500 美元/月,即可为您的项目配备一名经验丰富的 Laravel 开发人员(拥有 4-6 年经验)。获得 160 小时的专项专业知识和 15 天的无风险试用。立即安排通话!

Bacancy
Lucky Media logo

Lucky Media

现在就幸运 - Laravel 开发的理想选择,拥有超过十年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel 电子商务

Laravel 的电子商务。一个开源包,它将现代无头电子商务功能的强大功能引入 Laravel。

Lunar: Laravel 电子商务
LaraJobs logo

LaraJobs

官方 Laravel 工作板

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS 启动套件

SaaSykit 是一个 Laravel SaaS 启动套件,它包含运行现代 SaaS 所需的所有功能。付款、精美的结账流程、管理面板、用户仪表板、身份验证、就绪组件、统计信息、博客、文档等等。

SaaSykit: Laravel SaaS 启动套件
Rector logo

Rector

您无缝 Laravel 升级、降低成本和加速创新的合作伙伴,为成功企业服务

Rector
MongoDB logo

MongoDB

通过强大的 MongoDB 和 Laravel 集成来增强您的 PHP 应用程序,使开发人员能够轻松高效地构建应用程序。在使用熟悉的 Eloquent API 的同时,支持事务性、搜索、分析和移动用例。了解灵活、现代的 MongoDB 数据库如何改变您的 Laravel 应用程序。

MongoDB
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单的无依赖输入掩码库

阅读文章
Add Swagger UI to Your Laravel Application image

在 Laravel 应用程序中添加 Swagger UI

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel 提示构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速、模糊的站点搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 包为您的 Laravel 应用程序添加评论

阅读文章