文章转发自专业的 Laravel 开发者社区,原始链接:https://learnku.com/laravel/t…
我们在第三部分中放弃构建真实的用户端,而学习使用 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":"znikolaus@example.org"
}
}
第三部分的 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 组件
定义了 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>
首先看下 template 部分:我们在 div
中书写 <form>
,因为稍后我们只需要在加载了 user
数据后展示 <form>
。
<form>
有一个默认的 @submit
事件,我们定义了一个 onSubmit()
方法去处理它。最后需要提一下在 <input>
元素上的 v-model
属性,它和 data.users
对象一一对应。我们为 id
,name
,和 email
设置了默认值。
现在你打开 /users/1/edit
应该看到一个空白的表单:
我们准备编辑已经存在的用户,所以下一步会说明怎么获取路由中动态的 :id
,在 UsersEdit.vue
中加载用户数据。
使用专用的模块获取用户详情
在我们在组件中加载用户数据之前,我们先定义一个额外的专用模块去处理 /api/users
的资源,包括查询所有用户,查询单个用户和更新用户。
第一步,创建一个新的文件夹来放置请求后端的模块。你可以用任意方式来创建这些文件。下面我们展示了在 Nix
下的命令:
mkdir -p resources/assets/js/api/
touch resources/assets/js/api/users.js
users.js
模块会暴露一些用来处理 /api/users
资源的方法。这个模块会尽可能的简单,但之后你可以在请求之前或者之后随意处理数据。这个文件用作可复用的 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);
},
};
现在我们可以使用同样的模块去获取所有用户,查询和更新单个用户:
// 获取所有用户
client.all().then((data) => mapData);
// 查询单个用户
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
属性(尚未创建)并设置了 the user
属性。
现在往 data
中添加 loaded
属性,默认值为 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 速度很快,如果你要确定 loading 提示正常工作,你需要使用 setTimeout
去延迟设置 user
属性:
api.find(this.$route.params.id).then((response) => {setTimeout(() => {
this.loaded = true;
this.user = response.data.data;
}, 5000);
});
上面的代码会显示 loading 消息 5 秒,然后设置 loaded
和 user
属性。
更新用户
我们将完成 onSubmit()
方法,并用过 PUT /api/users/{user}
更新用户。
我们先完善 onSubmit()
,之后会转到后端处理数据库的更新:
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>
我们在 catch
之后另外链接了一个 then()
,一旦 API 请求结束,就将 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
文件底部为 alert
信息添加一些样式:
<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 后端更新用户
我们准备在 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
方法会使用 request 对象来验证数据,并返回我们要更新的数据。把下面的方法添加到 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":"hirthe.joel@example.org"
}
}
导航到编辑页面
我们一直在直接请求 /users/:id/edit
页面,但是,我们没有在界面的任何地方添加路由。在看到我是如何做到这一点之前,不妨自己尝试找出如何动态地导航到编辑页面。
这是我在 第二部分 创建了 UsersIndex.vue
模板,并为 /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 页面,因为我们具有用于编辑用户的动态路由。
如果您准备好了,请继续第五部分.