使用 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()方法不接受任何分页查询参数,但我将留给您来实现分页,并用我们新的all()客户端函数替换我们在UsersIndex.vue组件上拥有的内容。

从 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>

此时,如果您刷新页面,组件将短暂闪烁一条“加载中...”消息

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

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: ""
}
};
},

接下来,让我们更新我们的 `