42.话题订阅(三)
- 本系列文章为
laracasts.com
的系列视频教程 ——Let's Build A Forum with Laravel and TDD 的学习笔记。若喜欢该系列视频,可去该网站订阅后下载该系列视频, 支持正版 ;- 视频源码地址:github.com/laracasts/Lets-Build-a-...;
- 本项目为一个 forum(论坛)项目,与本站的第二本实战教程 《Laravel 教程 - Web 开发实战进阶》 类似,可互相参照。
本节说明#
- 对应视频教程第 42 小节:Thread Subscriptions (Part 3)
本节内容#
在前两节我们为订阅功能建立了测试,本节我们在前端页面显示订阅按钮,并且实现订阅功能。首先新建组件:
forum\resources\assets\js\components\SubscribeButton.vue
<template>
<button class="btn btn-default" @click="subscribe">Subscribe</button>
</template>
<script>
export default {
methods:{
subscribe(){
axios.post(location.pathname + '/subscriptions');
flash('Subscribed');
}
}
}
</script>
当我们点击 Subscribe
按钮时,我们发送 Ajax
请求,订阅话题。我们还需要在话题组件中引入该组件:
forum\resources\assets\js\pages\Thread.vue
<script>
import Replies from '../components/Replies';
import SubscribeButton from '../components/SubscribeButton';
export default {
props: ['initialRepliesCount'],
components: { Replies,SubscribeButton},
data() {
return {
repliesCount:this.initialRepliesCount
}
}
}
</script>
接下里显示订阅组件:
forum\resources\views\threads\show.blade.php
.
.
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-body">
<p>
This thread was published {{ $thread->created_at->diffForHumans() }} by
<a href="#">{{ $thread->creator->name }}</a>,and currently
has <span v-text="repliesCount"></span> {{ str_plural('comment',$thread->replies_count) }}
</p>
<p>
<subscribe-button></subscribe-button>
</p>
</div>
</div>
</div>
.
.
编译后测试订阅功能:
可以看到订阅功能已经实现,但是目前我们还有两个小地方需要修改:
- 点击多次按钮,多次订阅话题;
- 点击按钮后,应该显示已订阅而不是继续显示订阅按钮;
首先我们来修复第一个问题:
forum\database\migrations{timestamp}_create_thread_subscriptions_table.php
.
.
public function up()
{
Schema::create('thread_subscriptions', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id');
$table->unsignedInteger('thread_id');
$table->timestamps();
$table->unique(['user_id','thread_id']);
$table->foreign('thread_id')
->references('id')
->on('threads')
->onDelete('cascade');
});
}
.
.
我们为 thread_subscriptions
表加上了唯一性索引,所以我们需要运行迁移:
$ php artisan migrate:rollback
$ php artisan migrate
现在我们清空表数据,再次尝试:
是的,现在我们已经在数据库层面加上了限制,如果你再次点击订阅按钮,是不会增加另一条数据的。接下来我们对第二个问题进行调整:
forum\resources\views\threads\show.blade.php
.
.
<p>
<subscribe-button :active="true"></subscribe-button>
</p>
.
.
forum\resources\assets\js\components\SubscribeButton.vue
<template>
<button :class="classes" @click="subscribe">Subscribe</button>
</template>
<script>
export default {
props: ['active'],
computed: {
classes() {
return ['btn',this.active ? 'btn-primary' : 'btn-default'];
}
},
methods:{
subscribe(){
axios.post(location.pathname + '/subscriptions');
flash('Subscribed');
}
}
}
</script>
我们为 SubscribeButton
组件绑定了 active
属性,并且在 classes
根据 active
的属性值为 Subscribe
按钮赋予不同的样式。当然我们这里的 active
属性默认赋予的是 true
,我们会在之后动态获取。我们先来看一下页面效果:
如果我们把 active
的值改为 false
:
现在我们进行下一步,动态获取 active
的值。我们将通过下面的方式来获取:
$thread->isSubscribedTo
我们首先新建测试:
forum\tests\Unit\ThreadTest.php
.
.
/** @test */
public function it_knows_if_the_authenticated_user_is_subscribed_to_it()
{
// Given we have a thread
$thread = create('App\Thread');
// And a user who is subscribed to the thread
$this->signIn();
$this->assertFalse($thread->isSubscribedTo);
$thread->subscribe();
$this->assertTrue($thread->isSubscribedTo);
}
}
接着我们定义一个 访问器:getIsSubscribedToAttribute
,并且使用 序列化 的方式,添加一个在数据库中没有对应字段的属性。然后我们就可以通过 $thread->isSubscribedTo
的方式来获取 isSubscribedTo
属性。
forum\app\Thread.php
.
.
class Thread extends Model
{
use RecordsActivity;
protected $guarded = [];
protected $with = ['creator','channel'];
protected $appends = ['isSubscribedTo'];
.
.
public function getIsSubscribedToAttribute()
{
return $this->subscriptions()
->where('user_id',auth()->id())
->exists();
}
}
最后别忘了我们修改绑定属性的方式:
forum\resources\views\threads\show.blade.php
.
.
<subscribe-button :active="{{ json_encode($thread->isSubscribedTo)}}"></subscribe-button>
.
.
我们来试一下效果:
我们最终想要实现的效果当然不仅仅是这样:在我们点击了按钮之后,样式发生改变,表示已订阅;我们再次点击按钮,样式再次改变,表示我们取消订阅。完善我们的组件:
forum\resources\assets\js\components\SubscribeButton.vue
<template>
<button :class="classes" @click="subscribe">Subscribe</button>
</template>
<script>
export default {
props: ['active'],
computed: {
classes() {
return ['btn',this.active ? 'btn-primary' : 'btn-default'];
}
},
methods:{
subscribe(){
axios[
(this.active ? 'delete' : 'post')
](location.pathname + '/subscriptions');
this.active = ! this.active;
}
}
}
</script>
我们根据 this.active
的值来决定发送 delete
或者 post
请求:值为 true
,我们发送 delete
请求,取消订阅;值为 false
,我们发送 post
请求,进行订阅。但是我们现在还没有 delete
请求的处理逻辑,我们需要增加。首先依然是新建测试:
forum\tests\Feature\SubscribeToThreadsTest.php
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class SubscribeToThreadsTest extends TestCase
{
use DatabaseMigrations;
/** @test */
public function a_user_can_subscribe_to_threads()
{
$this->signIn();
// Given we have a thread
$thread = create('App\Thread');
// And the user subscribes to the thread
$this->post($thread->path() . '/subscriptions');
// Then,each time a new reply is left...
$thread->addReply([
'user_id' => auth()->id(),
'body' => 'Some reply here'
]);
// A notification should be prepared for the user.
// $this->assertCount(1,auth()->user()->notifications);
}
/** @test */
public function a_user_can_unsubscribe_from_threads()
{
$this->signIn();
// Given we have a thread
$thread = create('App\Thread');
// And the user unsubscribes from the thread
$this->delete($thread->path() . '/subscriptions');
$this->assertCount(0,$thread->subscriptions);
}
}
接着增加路由:
forum\routes\web.php
.
.
Route::post('/threads/{channel}/{thread}/subscriptions','ThreadSubscriptionsController@store')->middleware('auth');
Route::delete('/threads/{channel}/{thread}/subscriptions','ThreadSubscriptionsController@destroy')->middleware('auth');
.
.
然后增加 destroy
方法:
forum\app\Http\Controllers\ThreadSubscriptionsController.php
.
.
public function destroy($channelId,Thread $thread)
{
$thread->unsubscribe();
}
}
运行测试:
测试通过,我们再在页面进行验证:
推荐文章: