Udemy - Nuxt JS with Laravel API - Building SSR Vue JS Apps 笔记16 Laravel Nuxt–Likes

Likes PolyMorphic Relationship

这个原教程的likes 其实可以就在post user topic 之间写非多态模型关联。

为了示例,写这个多态的关联;一般情况下可以不这么写。

更多可以参考:

https://laravel.com/docs/7.x/eloquent-relationships#polymorphic-relationships


逻辑上原教程就是把likes当成了tags来操作。

一个likes的创建只属于一个user,一个post或topic可以有多个用户标记的likes。

post或topic就定义为likeable。

执行

php artisan make:model Like -m

migration create_likes_table.php:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateLikesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('likes', function (Blueprint $table) {
            $table->id();
            $table->unsignedBigInteger('likeable_id');
            $table->string('likeable_type');
            $table->unsignedBigInteger('user_id')->index();
            $table->timestamps();

            $table->foreign('user_id')
                ->references('id')
                ->on('users')
                ->cascadeOnDelete();


        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('likes');
    }
}

外键定义cascade即指:用户删除的时候,其发布的like也自然被删除。

执行:

php artisan migrate

模型关联:

Like.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Like extends Model
{
    public function likeable()
    {
        return $this->morphTo();
    }

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

Post.php:

<?php

namespace App;

use App\Traits\Orderable;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use  Orderable;

    protected $fillable = ['body'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function topic()
    {
        return $this->belongsTo(Topic::class);
    }

    public function likes()
    {
        return $this->morphMany(Like::class, 'likeable');
    }

}

Topic.php:

<?php

namespace App;

use App\Traits\Orderable;
use Illuminate\Database\Eloquent\Model;

class Topic extends Model
{
    use Orderable;

    protected $fillable = [
        'title',
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function posts()
    {
        return $this->hasMany(Post::class);
    }

    public function likes()
    {
        return $this->morphMany(Like::class, 'likeable');
    }
}

为了演示效果,在数据库中手动添加一条记录到likes表:

批注 2020-05-16 191439

修改PostResource.php

批注 2020-05-16 191509

Postman测试结果:

批注 2020-05-16 191540

再修改PostResource.php:

批注 2020-05-16 192022

Postman测试结果:

批注 2020-05-16 192059

注意这段的作用:

批注 2020-05-16 192441

Implementing Post Likes

api.php中新建route

批注 2020-05-16 193147

创建这个PostLikeController 及store方法:

执行:

php artisan make:controller PostLikeController

注意,在用户添加对一个post的like的时候,需要验证Policy,用户是否有权点like该post。

我们这里设定为用户不能给自己的post点like

直接在PostPolicy中添加:

批注 2020-05-16 193749

另外用户不能对一个post点赞多次,原教程是在User模型类中添加一个helper方法userHasLiked。

但是点赞应该用toggle就行了。

不过暂时过一遍教程:

模型类中添加:

批注 2020-05-16 195659

然后PostLikeController.php:

<?php

namespace App\Http\Controllers;

use App\Like;
use App\Post;
use App\Topic;
use Illuminate\Http\Request;

class PostLikeController extends Controller
{
    public function store(Request $request, Topic $topic, Post $post)
    {
        $this->authorize('like', $post);

        // check
        if ($request->user()->userHasLikedPost($post)) {
            return response(null, 409);
        }

        $like = new Like;
        $like->user()->associate($request->user());

        $post->likes()->save($like);

        return response(null, 204);

    }
}

Postman测试:

因已经有like记录,所以409错误:

批注 2020-05-16 200438

正常:

批注 2020-05-16 200004

前端:

更新pages/topics/index.vue文件:

<template>
  <div class="container">
    <h2>Latest Topics</h2>
    <div v-for="(topic, index) in topics" :key="index" class="bg-light mt-5 mb-5" style="padding:20px;">
      <h2>
        <nuxt-link :to="{name: 'topics-id', params: {id: topic.id}}">{{topic.title}}</nuxt-link>
      </h2>

      <div v-if="authenticated">
        <div v-if="user.id === topic.user.id">
          <button @click="deleteTopic(topic.id)" class="btn btn-outline-danger fa fa-trash fa-2x pull-right"></button>

          <nuxt-link :to="{name: 'topics-edit', params: {id: topic.id}}">
            <button class="btn btn-outline-success fa fa-edit fa-2x pull-right"></button>
          </nuxt-link>
        </div>
      </div>

      <p class="text-muted">{{topic.created_at}} by {{topic.user.name}}</p>

      <div v-for="(content, index) in topic.posts" :key="index" class="ml-5 content">
        {{content.body}}
        <p class="text-muted">{{content.created_at}} by {{content.user.name}}</p>
        <!-- add likes button -->
        <div class="btn btn-outline-primary fa fa-thumbs-up ml-5 mb-2" @click="likePost(topic.id, content)">
          <span class="badge">{{content.like_count}}</span>
        </div>
      </div>
    </div>

    <nav>
      <ul class="pagination justify-content-center">
        <li v-for="(key, value) in links" class="page-item">
          <a @click="loadMore(key)" href="#" class="page-link">{{value}}</a>
        </li>
      </ul>
    </nav>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        topics: [],
        links: []
      }
    },
    async asyncData({$axios}) {
      let {data, links} = await $axios.$get('/topics')
      console.log(links)
      return {
        topics: data,
        links
      }
    },
    methods: {
      async loadMore(key) {
        let {data} = await this.$axios.$get(key)
        return this.topics = {...this.topics, ...data}
      },
      async deleteTopic(id) {
        await this.$axios.$delete(`/topics/${id}`)
        this.$router.push('/')
      },
      async likePost(topicId, content) {
        const userFromVuex = this.$store.getters["auth/user"];
        if (userFromVuex) {
          // cant like your own post
          if (userFromVuex.id === content.user.id) {
            alert('You cant like your own post')
          }
          // if user have already liked
          if (content.users) {
            if (content.users.some(user => user.id === userFromVuex.id)) {
              alert('You have already liked this post')
            } else {
              await this.$axios.$post(`/topics/${topicId}/posts/${content.id}/likes`)
              let {data, links} = await this.$axios.$get(`/topics`)
              this.topics = data
              this.links = links
            }
          }
        } else {
          alert('Please log in')
          this.$router.push('/login')
        }
      }
    }
  }
</script>

<style scoped>
  .content {
    border-left: 10px solid white;
    padding: 0 10px 0 10px;
  }

  .btn-outline-success, .btn-outline-danger {
    border: none;
  }
</style>

效果:

成功like:

批注 2020-05-16 202130

未登录:

批注 2020-05-16 202320

点击ok后会自动跳转 http://localhost:3000/login

已经like过:

批注 2020-05-16 202229

like用户自己的post:批注 2020-05-16 202251

Show Like and Count

更新 把pages/topics/index.vue中Like文字替换为计数:

批注 2020-05-16 202827

效果:

批注 2020-05-16 202855

再修改下,否则点击导航按钮的时候,显示不正确。

批注 2020-05-16 203359

Home Page

在pixabay上下载一张图片,保存到static文件夹:

批注 2020-05-16 204652

然后更新assets/styles/main.css

html {
  font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI',
  Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-size: 16px;
  word-spacing: 1px;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  box-sizing: border-box;
}

*,
*:before,
*:after {
  box-sizing: border-box;
  margin: 0;
}

.button--green {
  display: inline-block;
  border-radius: 4px;
  border: 1px solid #3b8070;
  color: #3b8070;
  text-decoration: none;
  padding: 10px 30px;
}

.button--green:hover {
  color: #fff;
  background-color: #3b8070;
}

.button--grey {
  display: inline-block;
  border-radius: 4px;
  border: 1px solid #35495e;
  color: #35495e;
  text-decoration: none;
  padding: 10px 30px;
  margin-left: 15px;
}

.button--grey:hover {
  color: #fff;
  background-color: #35495e;
}

.bg {
  background-image: url("/background.jpg");
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}

pages/index.vue:

<template>
  <div class="d-flex flex-column">
    <div class="bg flex-fill">
      <div class="flex-center position-ref full-height">
        <div class="content mb-5">
          <div class="title text-light">
            Laravel API Development
            <br>
            Vue/Nuxt JS Web App
          </div>
          <hr>
          <nuxt-link to="/dashboard" class="btn btn-outline-primary mr-2">Post a Topic</nuxt-link>
          <nuxt-link to="/topics" class="btn btn-outline-warning">Browse Topics</nuxt-link>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    // middleware: ['auth'],
  }
</script>
<style scoped>
  html, body {
    background-color: #fff;
    color: #636b6f;
    font-family: 'Nunito', sans-serif;
    font-weight: 200;
    height: 100vh;
    margin: 0;
  }

  .full-height {
    height: 100vh;
  }

  .flex-center {
    align-items: center;
    display: flex;
    justify-content: center;
  }

  .content {
    text-align: center;
  }

  .title {
    font-size: 84px;
  }

</style>

再修改一下assets/styles/main.css:

批注 2020-05-16 210316

效果:

批注 2020-05-16 210337

源码:

前端部分:

https://github.com/dzkjz/laravel-backend-nuxt-frontend-frontpart

选择:

批注 2020-05-16 210602

后端:

https://github.com/dzkjz/laravel-backend-nuxt-frontend

选择:

批注 2020-05-16 210820





posted @ 2020-05-16 20:42  dzkjz  阅读(23)  评论(0)    收藏  举报