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表:
修改PostResource.php
Postman测试结果:
再修改PostResource.php:
Postman测试结果:
注意这段的作用:
Implementing Post Likes
api.php中新建route
创建这个PostLikeController 及store方法:
执行:
php artisan make:controller PostLikeController
注意,在用户添加对一个post的like的时候,需要验证Policy,用户是否有权点like该post。
我们这里设定为用户不能给自己的post点like
直接在PostPolicy中添加:
另外用户不能对一个post点赞多次,原教程是在User模型类中添加一个helper方法userHasLiked。
但是点赞应该用toggle就行了。
不过暂时过一遍教程:
模型类中添加:
然后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错误:
正常:
前端:
更新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:
未登录:
点击ok后会自动跳转 http://localhost:3000/login。
已经like过:
Show Like and Count
更新 把pages/topics/index.vue中Like文字替换为计数:
效果:
再修改下,否则点击导航按钮的时候,显示不正确。
Home Page
在pixabay上下载一张图片,保存到static文件夹:
然后更新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:
效果:
源码:
前端部分:
https://github.com/dzkjz/laravel-backend-nuxt-frontend-frontpart
选择:
后端:
https://github.com/dzkjz/laravel-backend-nuxt-frontend
选择:
























浙公网安备 33010602011771号