我们之前构建了一个真实用户端点,并学习了一种新的方法来使用 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", "email":"[email protected]" }}
我们第三部分的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
对象字面量。我们已经为id
、name
和email
定义了默认值。
此时,如果您加载/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 usersclient.all().then((data) => mapData); // Find a userclient.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);});
上面的超时将显示加载消息五秒钟,然后设置loaded
和user
数据属性。
更新用户
我们准备连接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()
函数,并传递了绑定表单输入中的name
和email
值。
然后我们在 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: "" } };},
接下来,让我们更新我们的 ``,以便在设置时显示 `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` 方法来连接所有点。我们将在服务器端定义必要的验证。但是,我们还没有在前端进行连接。
首先,我们将在 `routes/api.php` 文件中为 `PUT /api/users/{user}` 请求定义一个新路由
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", "email":"[email protected]" }}
导航到编辑页面
我们一直在直接请求 `/users/:id/edit` 页面,但是我们还没有在界面中添加它。请随意尝试自己弄清楚如何在查看我的操作方式之前动态导航到编辑页面。
以下是我在我们在 第 2 部分 中创建的 `UsersIndex.vue` 模板中为列出的每个用户添加的编辑链接
<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` 属性。我们使用 `
为了更好地可视化 `
{ 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 部分。