32. Vue 回复显示组件
- 本系列文章为
laracasts.com
的系列视频教程——Let's Build A Forum with Laravel and TDD 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频, 支持正版 ;- 视频源码地址:github.com/laracasts/Lets-Build-a-...;
- 本项目为一个 forum(论坛)项目,与本站的第二本实战教程 《Laravel 教程 - Web 开发实战进阶》 类似,可互相参照。
本节说明
- 对应视频第 32 小节:A Vue Reply Component
本节内容
本节我们来为回复编写一个 Vue 组件。首先修改reply
视图:
forum\resources\views\threads\reply.blade.php
<reply inline-template>
<div id="reply-{{ $reply->id }}" class="panel panel-default">
<div class="panel-heading">
<div class="level">
<h5 class="flex">
<a href="{{ route('profile',$reply->owner) }}"> {{ $reply->owner->name }}</a>
回复于
{{ $reply->created_at->diffForHumans() }}
</h5>
<div>
<form method="POST" action="/replies/{{ $reply->id }}/favorites">
{{ csrf_field() }}
<button type="submit" class="btn btn-default" {{ $reply->isFavorited() ? 'disabled' : '' }}>
{{ $reply->favorites_count }} {{ str_plural('Favorite',$reply->favorites_count) }}
</button>
</form>
</div>
</div>
</div>
<div class="panel-body">
{{ $reply->body }}
</div>
@can('update',$reply)
<div class="panel-footer level">
<button class="btn btn-xs mr-1">Edit</button>
<form method="POST" action="/replies/{{ $reply->id }}">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<button type="submit" class="btn btn-danger btn-xs">Delete</button>
</form>
</div>
@endcan
</div>
</reply>
我们通过在组件中添加inline-template
属性,向 Vue 指示内部内容是其模板。接下来ti添加组件:
forum\resources\assets\js\components\Reply.vue
<script>
export default {
}
</script>
注册组件:
forum\resources\assets\js\app.js
/**
* First we will load all of this project's JavaScript dependencies which
* includes Vue and other libraries. It is a great starting point when
* building robust, powerful web applications using Vue and Laravel.
*/
require('./bootstrap');
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
Vue.component('flash', require('./components/Flash.vue'));
Vue.component('reply', require('./components/Reply.vue'));
const app = new Vue({
el: '#app'
});
我们在回复视图使用了:
<button class="btn btn-xs mr-1">Edit</button>
但是我们没有编写mr-1
的样式,我们先在主视图文件暂时定义一下样式,以后我们把样式单独放到样式文件中:
forum\resources\views\layouts\app.blade.php
.
.
<style>
body{ padding-bottom: 100px; }
.level { display: flex;align-items: center; }
.flex { flex: 1 }
.mr-1 {margin-right: 1em;}
</style>
.
.
编译后访问页面:
接下来我们修改回复的视图跟组件,增加回复的编辑区域:
forum\resources\views\threads\reply.blade.php
<reply :attributes="{{ $reply }}" inline-template>
.
.
<div class="panel-body">
<div v-if="editing">
<textarea class="form-control" v-model="body"></textarea>
</div>
<div v-else>
{{ $reply->body }}
</div>
</div>
@can('update',$reply)
<div class="panel-footer level">
<button class="btn btn-xs mr-1" @click="editing = true">Edit</button>
<form method="POST" action="/replies/{{ $reply->id }}">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<button type="submit" class="btn btn-danger btn-xs">Delete</button>
</form>
</div>
@endcan
</div>
</reply>
forum\resources\assets\js\components\Reply.vue
<script>
export default {
props: ['attributes'],
data() {
return {
editing: false,
body: this.attributes.body
};
}
}
</script>
我们通过:attributes="{{ $reply }}"
把reply
的属性以json
的格式放到组件的属性中,还增加了一个回复的编辑区域<textarea class="form-control" v-model="body"></textarea>
,并设置Edit
按钮@click="editing = true"
,通过点击按钮激活回复的编辑区域。现在编译后刷新页面,点击Edit
按钮可以看到可编辑的回复区域:
接下来我们在回复的编辑区域下方添加两个按钮:Update
和Cancel
,并且点击Update
按钮实现回复的更新,点击Cancel
取消编辑回复。让我们先来为更新回复添加测试,我们需要添加两个测试,分别测试无权限用户和有权限用户更新回复的行为:
forum\tests\Feature\ParticipateInForumTest.php
.
.
/** @test */
public function unauthorized_users_cannot_update_replies()
{
$this->withExceptionHandling();
$reply = create('App\Reply');
$this->patch("/replies/{$reply->id}")
->assertRedirect('login');
$this->signIn()
->patch("/replies/{$reply->id}")
->assertStatus(403);
}
/** @test */
public function authorized_users_can_update_replies()
{
$this->signIn();
$reply = create('App\Reply',['user_id' => auth()->id()]);
$updatedReply = 'You have been changed,foo.';
$this->patch("/replies/{$reply->id}",['body' => $updatedReply]);
$this->assertDatabaseHas('replies',['id' => $reply->id,'body' => $updatedReply]);
}
}
添加更新路由:
forum\routes\web.php
.
.
Route::patch('/replies/{reply}','RepliesController@update');
Route::delete('/replies/{reply}','RepliesController@destroy');
.
.
增加update()
方法:
forum\app\Http\Controllers\RepliesController.php
.
.
public function update(Reply $reply)
{
$this->authorize('update',$reply);
$reply->update(request(['body']));
}
.
.
依次测试这两个测试:
测试通过,这意味着我们可以继续完善组件:
forum\resources\views\threads\reply.blade.php
.
.
<div class="panel-body">
<div v-if="editing">
<div class="form-group">
<textarea class="form-control" v-model="body"></textarea>
</div>
<button class="btn btn-xs btn-primary" @click="update">Update</button>
<button class="btn btn-xs btn-link" @click="editing = false">Cancel</button>
</div>
<div v-else>
{{ $reply->body }}
</div>
</div>
.
.
我们还需要在Reply.vue
组件中增加update
方法:
<script>
export default {
props: ['attributes'],
data() {
return {
editing: false,
body: this.attributes.body
};
},
methods:{
update() {
axios.patch('/replies/' + this.attributes.id,{
body:this.body
});
this.editing = false;
}
}
}
</script>
现在我们尝试更新:
会发现页面无变化。但是如果我们打开开发者工具栏,会发现其实我们的请求已经成功:
我们刷新页面会发现回复已经成功更新:
接下来我们把更新的内容显示出来:
forum\resources\views\threads\reply.blade.php
.
.
<div class="panel-body">
<div v-if="editing">
<div class="form-group">
<textarea class="form-control" v-model="body"></textarea>
</div>
<button class="btn btn-xs btn-primary" @click="update">Update</button>
<button class="btn btn-xs btn-link" @click="editing = false">Cancel</button>
</div>
<div v-else v-text="body"> </div>
</div>
.
.
现在我们已经可以把更新的内容显示出来了。但是我们还有几个地方需要优化:
- 当我们刷新页面的时候,可以看到回复编辑区域;
- 更新回复之后我们需要给出消息提示;
第一个问题是因为 HTML 绑定 Vue实例,在页面加载时会闪烁。我们使用v-cloak
指令来解决这个问题:
这个指令保持在元素上直到关联实例结束编译。和 CSS 规则如 [v-cloak] { display: none } 一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。
编辑视图:
forum\resources\views\threads\reply.blade.php
<reply :attributes="{{ $reply }}" inline-template v-cloak>
.
.
增加 CSS 样式:
forum\resources\views\layouts\app.blade.php
.
.
<style>
body{ padding-bottom: 100px; }
.level { display: flex;align-items: center; }
.flex { flex: 1 }
.mr-1 {margin-right: 1em;}
[v-cloak] { display: none; }
</style>
.
.
最后,我们为更新动作加上消息提示:
forum\resources\assets\js\components\Reply.vue
<script>
export default {
props: ['attributes'],
data() {
return {
editing: false,
body: this.attributes.body
};
},
methods:{
update() {
axios.patch('/replies/' + this.attributes.id,{
body:this.body
});
this.editing = false;
flash('Updated!');
}
}
}
</script>
刷新页面再次更新回复: