收藏功能就是收藏文章或者商品等内容,在用户面板上显示收藏以便用户随时查看或者购买。为了达到这个功能,我们需要创建用户和内容相对应的表,一个用户可以收藏很多内容,很多内容也可以被很多用户收藏。表里的结构应该是,收藏ID,用户ID,内容ID,收藏时间等等。下面我们就来开发这个功能:
1.创建表
执行命令:
php artisan make:migration create_user_favorites_table --create=user_favorites
生成迁移文件,--create这个后缀是设定表名。假设我们让用户收藏文章,有一个articles这个表。我们来编辑一下生成的迁移文件:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUserFavoritesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_favorites', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->unsignedBigInteger('article_id'); $table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_favorites');
}
}
注意一下外键的写法:foregin=>referrences=>on=>onDelete
执行迁移:
php artisan migrate
2.关联模型文件
在App\User模型文件里增加这句:
public function favoriteArticles()
{
return $this->belongsToMany(Article::class, 'user_favorites')
->withTimestamps()
->orderBy('user_favorites.created_at', 'desc');
}
belongsToMany说明是多对多的关联,第二个参数是中间表的表名,withTimestamps()表明中间表有时间戳的字段,orderBy('user_favorites.created_at', 'desc')代表默认的时间排序方式是中间表的创建时间倒序排序。
3.在控制器里写接口逻辑
逻辑很简单,用户可以收藏,也可以取消收藏,如果收藏了就不必重复收藏,如果取消收藏了就不必再取消,如果收藏了才能取消收藏。在AritclesController里面写具体方法:
public function favor(Article $article, Request $request)
{
$user = $request->user();
if ($user->favoriteProducts()->find($article->id)) {
return [];
}
//当前用户是否已经收藏了此文章,如果已经收藏则不做任何操作直接返回
$user->favoriteArticles()->attach($article);
//attach() 方法将当前用户和此商品关联起来, 参数可以是模型的 id,也可以是模型对象本身,因此这里还可以写成 attach($article->id)。
return [];
}
接下来是取消收藏的接口方法:
public function disfavor(Article $article, Request $request)
{
$user = $request->user();
$user->favoriteProducts()->detach($product);
//detach() 方法用于取消多对多的关联,接受的参数个数与 attach() 方法一致
return [];
}
4.路由设置
Route::group(['middleware' => ['auth', 'verified']], function() {
//中间件auth, verified是必须要登录和验证邮箱的中间件
Route::post('articles/{article}/favorite', 'ArticlesController@favor')->name('articles.favor');
Route::delete('articles/{article}/favorite', 'ArticlesController@disfavor')->name('article.disfavor');
});
5.前端收藏按钮
$(document).ready(function () {
// 监听收藏按钮的点击事件
$('.favor-btn').click(function () {
// 发起一个 post ajax 请求,请求 url 通过后端的 route() 函数生成。
// 这里用的是axios框架
axios.post('{{ route('articles.favor', ['article' => $article->id]) }}')
.then(function () { // 请求成功会执行这个回调
swal('操作成功', '', 'success');
swal是一个封装的模态提示窗口
}, function(error) { // 请求失败会执行这个回调
// 如果返回码是 401 代表没登录
if (error.response && error.response.status === 401) {
swal('请先登录', '', 'error');
} else if (error.response && (error.response.data.msg || error.response.data.message)) {
// 其他有 msg 或者 message 字段的情况,将 msg 提示给用户
swal(error.response.data.msg ? error.response.data.msg : error.response.data.message, '', 'error');
} else {
// 其他情况应该是系统挂了
swal('系统错误', '', 'error');
}
});
});
});
</script>
6.取消收藏按钮
接下来我们要在页面添加取消收藏按钮及其功能,对于已经收藏了当前文章的用户,我们要展示取消收藏按钮,因此需要在控制器中把收藏状态注入到模板中,此时我们返回的是一个布尔值。用户未登录时返回的是null, 用户登录而且文章被收藏的情况下返回的也是false,也就是取消收藏按钮。
先做控制器ArticlesController.php逻辑:
public function show(Article $article, Request $request)
{
$favored = false;
// 用户未登录时返回的是 null,已登录时返回的是对应的用户对象
if($user = $request->user()) {
// 从当前用户已收藏的文章中搜索 id 为当前文章的 id
// boolval() 函数用于把值转为布尔值
$favored = boolval($user->favoriteArticles()->find($article->id));
}
return view('articles.show', ['article' => $article, 'favored' => $favored]);
}
这里我们是把原来的单页文章的show方法修改了下。
7.修改收藏按钮的显示方式
我们主要是要判断传递的$favored这个变量是否是true。
<div class="buttons">
@if($favored)
<button class="btn btn-danger disfavor-btn">取消收藏</button>
@else
<button class="btn btn-success favor-btn">❤ 收藏</button>
@endif
</div>
然后我们在把取消收藏的ajax写一下:
$(document).ready(function () {
$('disfavor-btn').click(function () {
axios.delete('{{ route('articles.disfavor', ['article' => $product->id]) }}')
.then(function () {
swal('操作成功', '', 'success')
.then(function () {
location.reload();
});
});
});
})