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!
          
TDD 构建 Laravel 论坛笔记
                    
                    
            
            
                关于 LearnKu
              
                    
                    
                    
 
推荐文章: