laravel ajax 点赞与取消赞功能开发

laravel ajax 点赞与取消赞功能在很多的时候 应用非常广泛,同样的原理有产品的收藏功能,都是一个思路,接下来是我的代码,有很多的不足,仅用于参考。

点赞功能


点赞商品是电商网站一个常用的功能,本章节要实现收藏商品的基本功能。

1. 数据库结构


收藏商品本质上是用户和商品的多对多关联,因此不需要创建新的模型,只需要增加一个中间表即可:

$ php artisan make:migration create_user_fabulous_products_table --create=user_fabulous_products
注意:中间表命名,要越直白越好,名字长点也无所谓。一个简单的判断命名是否合格的方法是 —— 想象自己半年一年以后是否能快速地从数据库表名得知此表的功能。

database/migrations/< your_date >_create_user_fabulous_products_table.php

...
    public function up()
    {
        Schema::create('user_fabulous_products', function (Blueprint $table) {
            $table->increments('id');
            $table->unsignedInteger('user_id');
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->unsignedInteger('product_id');
            $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
            $table->timestamps();
        });
    }...
这里我们保留了时间戳的字段,这是因为我们希望用户看到的点赞商品是按用户点赞时间排序的,所以需要有一个时间字段用于排序。

然后执行数据库迁移:

$ php artisan migrate

2. 模型文件增加关联


接下来我们在 User 模型中增加与商品的关联关系:

app/Models/User.php

...
    public function fabulousProducts()
    {
        return $this->belongsToMany(Product::class, 'user_fabulous_products')
            ->withTimestamps()
            ->orderBy('user_fabulous_products.created_at', 'desc');
    }...

代码解析:

belongsToMany() 方法用于定义一个多对多的关联,第一个参数是关联的模型类名,第二个参数是中间表的表名。

withTimestamps() 代表中间表带有时间戳字段。

orderBy('user_fabulous_products.created_at', 'desc') 代表默认的排序方式是根据中间表的创建时间倒序排序。

3. 控制器


接下来我们需要在 ProductsController 中新增点赞与取消赞的接口:

app/Http/Controllers/ProductsController.php

...
    public function fabulous(Product $product, Request $request)
    {
        $user = $request->user();
        if ($user->fabulousProducts()->find($product->id)) {
            return [];
        }

        $user->fabulousProducts()->attach($product);

        return [];
    }...

这段代码先是判断当前用户是否已经点赞了此商品,如果已经点赞则不做任何操作直接返回,否则通过 attach() 方法将当前用户和此商品关联起来.

attach() 方法的参数可以是模型的 id,也可以是模型对象本身,因此这里还可以写成 attach($product->id)。

然后是取消点赞的接口:

app/Http/Controllers/ProductsController.php

...
    public function disfabulous(Product $product, Request $request)
    {
        $user = $request->user();
        $user->fabulousProducts()->detach($product);

        return [];
    }...

detach() 方法用于取消多对多的关联,接受的参数个数与 attach() 方法一致。

4. 路由


接下来把这两个接口添加到路由中:

routes/web.php

...Route::group(['middleware' => 'auth'], function() {...
    Route::group(['middleware' => 'email_verified'], function() {
        .
        .
        .
        Route::post('products/{product}/fabulous', 'ProductsController@fabulous')->name('products.fabulous');
        Route::delete('products/{product}/fabulous', 'ProductsController@disfabulous')->name('products.disfabulous');
    });});

5. 『点赞』按钮


接下来我们需要在前端模板页面实现『点赞』按钮的功能:

resources/views/products/show.blade.php

<script>
    /* @author:Romey
     * 动态点赞
     * 此效果包含css3,部分浏览器不兼容(如:IE10以下的版本)
    */
    $(function(){
        $("#praise").click(function(){
            var praise_img = $("#praise-img");
            var text_box = $("#add-num");
            var praise_txt = $("#praise-txt");
            var num=parseInt(praise_txt.text());
            if(praise_img.attr("src") == ("/Images/zan.png")){
                $(this).html("<img src='/Images/yizan.png' id='praise-img' class='animation' />");
                praise_txt.removeClass("hover");
                text_box.show().html("<em class='add-animation'>+1</em>");
                $(".add-animation").removeClass("hover");
                num +=1;
                praise_txt.text(num)
                $.ajax({
                    url: "{{ route('products.fabulous', ['product' => $product->id]) }}",
                    method: "POST",
                    dataType: "json"
                });
            }else{
                $(this).html("<img src='/Images/zan.png' id='praise-img' class='animation' />");
                praise_txt.addClass("hover");
                text_box.show().html("<em class='add-animation'>-1</em>");
                $(".add-animation").addClass("hover");
                num -=1;
                praise_txt.text(num)
                $.ajax({
                    url: "{{ route('products.fabulous', ['product' => $product->id]) }}",
                    method: "DELETE",
                    dataType: "json"
                });
            }
        });
    })
</script>

6. 『详情页面-点赞-取消攒』 按钮


接下来我们要在页面添加按钮及其功能,对于已经点赞了当前商品的用户,我们不展示 按钮,而展示 取消赞 按钮,因此需要在控制器中把收藏状态注入到模板中:

app/Http/Controllers/ProductsController.php

...
    public function show(Product $product,Request $request)
    {
        // 判断商品是否已经上架,如果没有上架则抛出异常。
        if(!$product->on_sale){
            throw new InvalidRequestException('商品未上架');
        }
        $fabulous = false;
        // 用户未登录时返回的是 null,已登录时返回的是对应的用户对象
        if($user = $request->user()){
            // 从当前用户已收藏的商品中搜索 id 为当前商品 id 的商品
            // boolval() 函数用于把值转为布尔值
            $fabulous = boolval($user->fabulousProducts()->find($product->id));
        }
        $zancount = \DB::table('user_fabulous_products')->where('product_id',$product->id)->count();
        return view('products.show',[
            'product'=>$product,
            'fabulous'=>$fabulous,
            'zancount'=>$zancount,
        ]);
    }...

有了 $fabulous 这个变量之后,我们在模板里加上判断:

resources/views/products/show.blade.php

...
   @guest
   @else
   <div class="praise">
      @if($fabulous)
      <span id="praise"><img src="/Images/yizan.png" id="praise-img" /></span>
      @else
      <span id="praise"><img src="/Images/zan.png" id="praise-img" /></span>
      @endif
      <span id="praise-txt">{{$zancount}}</span>
      <span id="add-num"><em>+1</em></span>
   </div>...
<script>
    /* @author:Romey
     * 动态点赞
     * 此效果包含css3,部分浏览器不兼容(如:IE10以下的版本)
    */
    $(function(){
        $("#praise").click(function(){
            var praise_img = $("#praise-img");
            var text_box = $("#add-num");
            var praise_txt = $("#praise-txt");
            var num=parseInt(praise_txt.text());
            if(praise_img.attr("src") == ("/Images/zan.png")){
                $(this).html("<img src='/Images/yizan.png' id='praise-img' class='animation' />");
                praise_txt.removeClass("hover");
                text_box.show().html("<em class='add-animation'>+1</em>");
                $(".add-animation").removeClass("hover");
                num +=1;
                praise_txt.text(num)
                $.ajax({
                    url: "{{ route('products.fabulous', ['product' => $product->id]) }}",
                    method: "POST",
                    dataType: "json"
                });
            }else{
                $(this).html("<img src='/Images/zan.png' id='praise-img' class='animation' />");
                praise_txt.addClass("hover");
                text_box.show().html("<em class='add-animation'>-1</em>");
                $(".add-animation").addClass("hover");
                num -=1;
                praise_txt.text(num)
                $.ajax({
                    url: "{{ route('products.fabulous', ['product' => $product->id]) }}",
                    method: "DELETE",
                    dataType: "json"
                });
            }
        });
    })
</script>
@endguest
1:在加载详情页面的时候 会检查用户是否登录,如果未登录将不现实点赞按钮。
2:js原理:获取当前点赞图片src参数 并进行比较,如果是点赞图片image/zan.png将进行点赞功能,如果不是就发送取消赞 信息。

7. 点赞按钮-CSS


<style type="text/css">
/* author Romey
   2016-09-13
*/
#buttons{ width:72px;margin: 0px auto; height: 34px;}
/*动态点赞开始*/
.praise{
    width:40px;
    height:34px;
    margin: 0px auto;
    cursor: pointer;
    font-size: 12px;
    text-align:center;
    position: relative;
}
#praise{
    display:block;
    width:40px;
    height:34px;
    margin:0 auto;
}
#praise-txt{
    height:25px;
    line-height:25px;
    display: block;
}
.praise img{
    width:40px;
    height:34px;
    display:block;
    margin: 0 auto;
}
.praise img.animation{
    animation: myfirst 0.5s;
    -moz-animation: myfirst 0.5s;   /* Firefox */
    -webkit-animation: myfirst 0.5s;    /* Safari 和 Chrome */
    -o-animation: myfirst 0.5s; /* Opera */
}
#add-num{
    display:none;
}
#add-num .add-animation{
    color: #000;
    position:absolute;
    top:-15px;
    left: 10px;
    font-size: 15px;
    opacity: 0;
    filter: Alpha(opacity=0);
    -moz-opacity:0;
    animation: mypraise 0.5s ;
    -moz-animation: mypraise 0.5s ; /* Firefox */
    -webkit-animation: mypraise 0.5s ;  /* Safari 和 Chrome */
    -o-animation: mypraise 0.5s ;   /* Opera */
    font-style:normal;
}
.praise .hover , #add-num .add-animation.hover , #praise-txt.hover{
    color: #EB4F38;
}

/*点赞图标放大动画开始*/
@keyframes myfirst
{
    0%{
        width:40px;
        height:40px;
    }
    50%{
        width:50px;
        height:50px;
    }
    100% {
        width:40px;
        height:40px;
    }
}

@-moz-keyframes myfirst /* Firefox */
{
    0%{
        width:40px;
        height:40px;
    }
    50%{
        width:50px;
        height:50px;
    }
    100% {
        width:40px;
        height:40px;
    }
}

@-webkit-keyframes myfirst /* Safari 和 Chrome */
{
    0%{
        width:40px;
        height:40px;
    }
    50%{
        width:50px;
        height:50px;
    }
    100% {
        width:40px;
        height:40px;
    }
}

@-o-keyframes myfirst /* Opera */
{
    0%{
        width:40px;
        height:40px;
    }
    50%{
        width:50px;
        height:50px;
    }
    100% {
        width:40px;
        height:40px;
    }
}
/*点赞图标放大动画结束*/
/*点赞数量加减动画开始*/
@keyframes mypraise
{
    0%{
        top:-15px;
        opacity: 0;
        filter: Alpha(opacity=0);
        -moz-opacity:0;
    }
    25%{
        top:-20px;
        opacity: 0.5;
        filter: Alpha(opacity=50);
        -moz-opacity:0.5;
    }
    50%{
        top:-25px;
        opacity: 1;
        filter: Alpha(opacity=100);
        -moz-opacity:1;
    }
    75%{
        top:-30px;
        opacity: 0.5;
        filter: Alpha(opacity=50);
        -moz-opacity:0.5;
    }
    100% {
        top:-35px;
        opacity: 0;
        filter: Alpha(opacity=0);
        -moz-opacity:0;
    }
}

@-moz-keyframes mypraise /* Firefox */
{
    0%{
        top:-15px;
        opacity: 0;
        filter: Alpha(opacity=0);
        -moz-opacity:0;
    }
    25%{
        top:-20px;
        opacity: 0.5;
        filter: Alpha(opacity=50);
        -moz-opacity:0.5;
    }
    50%{
        top:-25px;
        opacity: 1;
        filter: Alpha(opacity=100);
        -moz-opacity:1;
    }
    75%{
        top:-30px;
        opacity: 0.5;
        filter: Alpha(opacity=50);
        -moz-opacity:0.5;
    }
    100% {
        top:-35px;
        opacity: 0;
        filter: Alpha(opacity=0);
        -moz-opacity:0;
    }
}

@-webkit-keyframes mypraise /* Safari 和 Chrome */
{
    0%{
        top:-15px;
        opacity: 0;
        filter: Alpha(opacity=0);
        -moz-opacity:0;
    }
    25%{
        top:-20px;
        opacity: 0.5;
        filter: Alpha(opacity=50);
        -moz-opacity:0.5;
    }
    50%{
        top:-25px;
        opacity: 1;
        filter: Alpha(opacity=100);
        -moz-opacity:1;
    }
    75%{
        top:-30px;
        opacity: 0.5;
        filter: Alpha(opacity=50);
        -moz-opacity:0.5;
    }
    100% {
        top:-35px;
        opacity: 0;
        filter: Alpha(opacity=0);
        -moz-opacity:0;
    }
}

@-o-keyframes mypraise /* Opera */
{
    0%{
        top:-15px;
        opacity: 0;
        filter: Alpha(opacity=0);
        -moz-opacity:0;
    }
    25%{
        top:-20px;
        opacity: 0.5;
        filter: Alpha(opacity=50);
        -moz-opacity:0.5;
    }
    50%{
        top:-25px;
        opacity: 1;
        filter: Alpha(opacity=100);
        -moz-opacity:1;
    }
    75%{
        top:-30px;
        opacity: 0.5;
        filter: Alpha(opacity=50);
        -moz-opacity:0.5;
    }
    100% {
        top:-35px;
        opacity: 0;
        filter: Alpha(opacity=0);
        -moz-opacity:0;
    }
}
/*点赞数量加减动画结束*/
/*动态点赞结束*/
</style>

这样就将文章点赞功能完成了。本文参考与《L05 Laravel 教程 - 电商实战》5.7收藏商品章节https://laravel-china.org/courses/laravel-shop/1562/collection-of-goods

阅读 332

Comments

匿名

匿名 4天前

如果在页面计算和显示点赞数量? 然后点赞的信息是公开而不是私有?

回复此评论

匿名

匿名 4天前

回复 @ 匿名:如何在页面计算和显示点赞数量? 然后点赞的信息是公开而不是私有? 点赞列表。

回复此评论