34. Vue 点赞组件
- 本系列文章为
laracasts.com
的系列视频教程——Let's Build A Forum with Laravel and TDD 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频, 支持正版 ;- 视频源码地址:github.com/laracasts/Lets-Build-a-...;
- 本项目为一个 forum(论坛)项目,与本站的第二本实战教程 《Laravel 教程 - Web 开发实战进阶》 类似,可互相参照。
本节说明
- 对应视频教程第 34 小节:A Vue Favorite Component
本节内容
本节我们将回复点赞的表单换成 Vue 点赞组件。首先我们来修改视图:
forum\resources\views\threads\reply.blade.php
. . <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> <favorite :reply="{{ $reply }}"></favorite> </div> </div> </div> . .
接下来新增Favorite.vue
组件:
forum\resources\assets\js\components\Favorite.vue
<template> <button type="submit" class="btn btn-default"> <span class="glyphicon glyphicon-heart"></span> <span v-text="favoritesCount"></span> </button> </template> <script> export default { data() { return { favoritesCount: 10 } } } </script>
现在Favorite.vue
依然是不可用的,因为我们想要在Reply.vue
组件中使用Favorite.vue
,首先必须引入它:
forum\resources\assets\js\components\Reply.vue
<script> import Favorite from './Favorite.vue'; export default { props: ['attributes'], components: { Favorite }, data() { return { editing: false, body: this.attributes.body }; }, methods:{ update() { axios.patch('/replies/' + this.attributes.id,{ body:this.body }); this.editing = false; flash('Updated!'); }, destroy() { axios.delete('/replies/' + this.attributes.id); $(this.$el).fadeOut(300, () => { flash('Your reply has been deleted!'); }); } } } </script>
现在刷新页面即可看到点赞组件:
但是我们现在点击按钮是不会有任何反应的,因为我们没有为按钮定义动作。让我们来梳理一下接下来要做的事情:首先,我们要给按钮定义动作,点击按钮,点赞该回复,再次点击则取消点赞;接下来,我们要正确显示点赞的数量。
首先,给按钮定义动作:
forum\resources\assets\js\components\Favorite.vue
<template> <button type="submit" :class="classes" @click="toggle"> <span class="glyphicon glyphicon-heart"></span> <span v-text="favoritesCount"></span> </button> </template> <script> export default { props:['reply'], data() { return { favoritesCount: this.reply.favoritesCount, isFavorited:false } }, computed: { classes() { return ['btn',this.isFavorited ? 'btn-primary' : 'btn-default'] } }, methods: { toggle() { if (this.isFavorited){ axios.delete('/replies/' + this.reply.id + '/favorites') }else { axios.post('/replies/' + this.reply.id + '/favorites'); this.isFavorited = true; this.favoritesCount++; } } } } </script>
其实在以上我们已经补充完整了两个步骤所需要进行的动作。我们使用favoritesCount
来获取点赞的数量。那么这个属性是如何得到的呢?我们在之前的章节中将获取点赞数的方法封装成了Favoritable
Trait,并且在 Trait 定义了一个 访问器:getFavoritesCountAttribute
,然后我们通过$reply->favorites_count
来获取favorites_count
属性。但是我们在 Vue 组件中无法使用这样的方式获取到favorites_count
:
所以我们改为使用 序列化 的方式,添加一个在数据库中没有对应字段的属性。要使用序列化,首先你需要为这个值定义一个 访问器(我们已经创建)。访问器创建成功后,只需添加该属性到该模型的 appends
属性中:
forum\app\Reply.php
class Reply extends Model { use Favoritable,RecordsActivity; protected $guarded = []; protected $with = ['owner','favorites']; protected $appends = ['favoritesCount']; . .
我们刷新页面:
可以看到favoritesCount
属性已经添加到模型属性组当中,并且在 Vue 组件的属性组中。我们已经将点赞的动作补充完整,如果我们现在点击按钮,会发现按钮变色,同时点赞数变成了 1:
接下来我们进行取消点赞的动作。首先我们先新建一个测试:
forum\tests\Feature\FavoritiesTest.php
. . /** @test */ public function an_authenticated_user_can_unfavorite_a_reply() { $this->signIn(); $reply = create('App\Reply'); $this->post('replies/' . $reply->id . '/favorites'); $this->assertCount(1,$reply->favorites); $this->delete('replies/' . $reply->id . '/favorites'); $this->assertCount(0,$reply->favorites); } . .
添加路由:
forum\routes\web.php
. . Route::post('/replies/{reply}/favorites','FavoritesController@store'); Route::delete('/replies/{reply}/favorites','FavoritesController@destroy'); . .
添加destroy()
方法:
forum\app\Http\Controllers\FavoritesController.php
. . public function destroy(Reply $reply) { $reply->unfavorite(); } }
添加unfavorite()
方法:
forum\app\Favoritable.php
. . public function favorite() { $attributes = ['user_id' => auth()->id()]; if (!$this->favorites()->where($attributes)->exists()) { return $this->favorites()->create($attributes); } } public function unfavorite() { $attributes = ['user_id' => auth()->id()]; $this->favorites()->where($attributes)->delete(); } . .
现在我们可以运行测试:
测试未通过,原因是什么呢?如果你对之前的内容记得很清楚的话,那么你应该记得我们是通过Reply
模型的$with
属性组来获取favorites
:
forum\app\Reply.php
. . class Reply extends Model { use Favoritable,RecordsActivity; protected $guarded = []; protected $with = ['owner','favorites']; protected $appends = ['favoritesCount']; . .
而我们使用$reply->favorites
来获取点赞数,这会让我们预加载favorites
属性,所以我们的第二个断言未通过:
. . $this->assertCount(0,$reply->favorites); . .
我们需要使用fresh()
函数:
. . $this->assertCount(0,$reply->fresh()->favorites); . .
再次测试,测试通过:
现在我们可以进行一些重构:
. . /** @test */ public function an_authenticated_user_can_unfavorite_a_reply() { $this->signIn(); $reply = create('App\Reply'); $reply->favorite(); $this->delete('replies/' . $reply->id . '/favorites'); $this->assertCount(0,$reply->favorites); } . .
再次测试:
到目前为止,我们取消点赞的动作已经完成,接下来我们只需要完善我们的组件即可:
forum\resources\assets\js\components\Favorite.vue
<script> export default { props:['reply'], data() { return { favoritesCount: this.reply.favoritesCount, isFavorited:false } }, computed: { classes() { return ['btn',this.isFavorited ? 'btn-primary' : 'btn-default'] } }, methods: { toggle() { if (this.isFavorited){ axios.delete('/replies/' + this.reply.id + '/favorites'); this.isFavorited = false; this.favoritesCount--; }else { axios.post('/replies/' + this.reply.id + '/favorites'); this.isFavorited = true; this.favoritesCount++; } } } } </script>
现在我们刷新页面,已经可以完整地进行点赞和取消点赞行为了。但是,我们默认了isFavorited
属性是false
:
isFavorited:false
我们该如何正确获取是否已经进行过点赞行为呢?答案是 序列化 。要使用序列化,首先我们要定义一个 访问器:
forum\app\Favoritable.php
. . public function getIsFavoritedAttribute() { return $this->isFavorited(); } public function getFavoritesCountAttribute() { return $this->favorites->count(); } }
接着将isFavorited
添加到模型的$appends
属性组:
forum\app\Reply.php
. . protected $appends = ['favoritesCount','isFavorited']; . .
最后我们在组件中使用:
forum\resources\assets\js\components\Favorite.vue
. . data() { return { favoritesCount: this.reply.favoritesCount, isFavorited:this.reply.isFavorited } }, . .
现在你可以刷新页面进行测试。你知道接下来我们要做什么吗?重构。最后让我们来重构组件,使其更具可读性:
<template> <button type="submit" :class="classes" @click="toggle"> <span class="glyphicon glyphicon-heart"></span> <span v-text="count"></span> </button> </template> <script> export default { props:['reply'], data() { return { count: this.reply.favoritesCount, active:this.reply.isFavorited } }, computed: { classes() { return ['btn', this.active ? 'btn-primary' : 'btn-default' ]; }, endpoint() { return '/replies/' + this.reply.id + '/favorites'; } }, methods: { toggle() { this.active ? this.destroy() : this.create(); }, create() { axios.post(this.endpoint); this.active = true; this.count++; }, destroy() { axios.delete(this.endpoint); this.active = false; this.count--; } } } </script>
再次刷新页面进行测试。最后的最后,运行一下全部测试:
Perfect!
推荐文章: