Udemy - Nuxt JS with Laravel API - Building SSR Vue JS Apps 笔记14 Laravel Nuxt - Create and Read

Moving to CRUD

批注 2020-05-15 212940

Topic Model and Post Model Migration

执行:

php artisan make:model Topic -m
php artisan make:model Post -m
<?php

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

class CreateTopicsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('topics', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->unsignedBigInteger('user_id')->index();
            $table->timestamps();
            //user 删除的时候 删除属于该用户的topics
            $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
        });

    }

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

<?php

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

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

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

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

执行:

php artisan migrate

Topic/User/Posts Relationships

Topic.php:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Topic extends Model
{
    protected $fillable = [
        'title',
    ];

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

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

Post.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $fillable = ['body'];

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

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

}

User.php:

<?php

namespace App;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends Authenticatable implements JWTSubject
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    // Rest omitted for brevity

    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        //return the primary key of the user - user id
        return $this->getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     *
     * @return array
     */
    public function getJWTCustomClaims()
    {
        //return a key value array containing any claims to be added to JWT
        return [];
    }

    public function topics()
    {
        return $this->hasMany(Topic::class);
    }

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

Scope Trait

新建app\Traits\Orderable.php:

<?php

namespace App\Traits;

trait Orderable
{
    public function scopeLatestFirst($query)
    {
        return $query->orderBy('created_at', 'desc');
    }

    public function scopeOldestFirst($query)
    {
        return $query->orderBy('created_at', 'asc');
    }

}


然后:

在Post 和 Topic model中使用这个traits

批注 2020-05-15 225747

批注 2020-05-15 225725

Create a New Topic

先增加api.php中的route

Route::group(['prefix' => 'topics'], function () {
    Route::post('/', 'TopicController@store')->middleware('auth');
});
用group方便管理,

创建这个TopicController,执行

php artisan make:controller TopicController

TopicController.php:

<?php

namespace App\Http\Controllers;

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

class TopicController extends Controller
{
    public function store(Request $request)
    {
        $topic = new Topic;
        $topic->title = $request->get('title');
        $topic->user()->associate($request->user());


        $post = new Post;
        $post->body = $request->get('body');
        $post->user()->associate($request->user());


        $topic->save();
        $topic->posts()->save($post);
    }
}

注意这里没有验证,后面我们使用自定义的request 在该request类中进行验证。

打开Postman测试:

请求前请登录获取token 并把token设置到Bearer token

批注 2020-05-15 225925

批注 2020-05-15 225846

查看数据库中结果:

批注 2020-05-15 230035

批注 2020-05-15 230054

Topic Resource

执行

php artisan make:resource TopicResource
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class TopicResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param \Illuminate\Http\Request $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'created_at' => $this->created_at->diffForHumans(),
            'updated_at' => $this->updated_at->diffForHumans(),
//            'posts'=>
            'user' => $this->user,
        ];
//        return parent::toArray($request);
    }
}

上面可以用$this->user方式加载user 这样就不用with(‘user’) 或者load(‘user’)了。

TopicController.php修改:

<?php

namespace App\Http\Controllers;

use App\Http\Resources\TopicResource;
use App\Post;
use App\Topic;
use Illuminate\Http\Request;

class TopicController extends Controller
{
    public function store(Request $request)
    {
        $topic = new Topic;
        $topic->title = $request->get('title');
        $topic->user()->associate($request->user());


        $post = new Post;
        $post->body = $request->get('body');
        $post->user()->associate($request->user());


        $topic->save();
        $topic->posts()->save($post);

        return TopicResource::make($topic);
    }
}

Postman测试:

批注 2020-05-15 233348

Post Resource

执行:

php artisan make:resource PostResource
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class PostResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param \Illuminate\Http\Request $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'body' => $this->body,
            'created_at' => $this->created_at->diffForHumans(),
            'updated_at' => $this->updated_at->diffForHumans(),
            'user' => $this->user,
        ];

//        return parent::toArray($request);
    }
}

更新TopicResource.php

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class TopicResource extends JsonResource
{
    /**
     * Transform the resource into an array.
     *
     * @param \Illuminate\Http\Request $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'created_at' => $this->created_at->diffForHumans(),
            'updated_at' => $this->updated_at->diffForHumans(),
            'posts' => PostResource::collection($this->posts),
            'user' => $this->user,
        ];
//        return parent::toArray($request);
    }
}

Postman测试结果:

批注 2020-05-15 233844

Topic Request Validation

执行:

php artisan make:request TopicCreateRequest
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class TopicCreateRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|string|max:255',
            'body' => 'required|string|max:200',
        ];
    }
}

更新TopicController.php

<?php

namespace App\Http\Controllers;

use App\Http\Requests\TopicCreateRequest;
use App\Http\Resources\TopicResource;
use App\Post;
use App\Topic;
use Illuminate\Http\Request;

class TopicController extends Controller
{
    public function store(TopicCreateRequest $request)
    {
        $topic = new Topic;
        $topic->title = $request->get('title');
        $topic->user()->associate($request->user());


        $post = new Post;
        $post->body = $request->get('body');
        $post->user()->associate($request->user());


        $topic->save();
        $topic->posts()->save($post);

        return TopicResource::make($topic);
    }
}

用postman测试:

注意要设置BearerToken 以及 Headers 中设置 Accept和Content-Type

批注 2020-05-15 234451

否则会返回一个404 PageNotFound错误

批注 2020-05-15 234645

测试一下不发送title:

批注 2020-05-15 234536

正常结果:

批注 2020-05-15 234731

=============================================================

下面开始更新前端,

Topic Create Page

把之前创建的pages/profile.vue更为pages/dashboard.vue

批注 2020-05-15 235101

<template>
  <div>
    <h2>User Dashboard</h2>
  </div>
</template>

<script>
  export default {
    name: "dashboard"
  }
</script>

<style scoped>

</style>

pages/login.vue中:

批注 2020-05-15 235218

pages/register.vue中:

批注 2020-05-15 235248

middleware/guest.js:

批注 2020-05-16 000708

执行 npm run dev 启动,打开 http://localhost:3000/ 检查工作是否正常:

登录后跳转Dashboard

批注 2020-05-15 235434

正常!

dashboard.vue更新:

<template>
  <div class="container col-md-6 mt-5">
    <h2>User Dashboard</h2>
    <hr>
    <h3>Create a new topic</h3>
    <form @submit.prevent="create">
      <div class="form-group">
        <label><strong>Topic Title:</strong></label>
        <input type="text" class="form-control" v-model.trim="form.title" autofocus>
        <small class="form-text text-danger" v-if="errors.title">{{errors.title[0]}}</small>
      </div>
      <div class="form-group">
        <label><strong>Topic Body:</strong></label>
        <textarea class="form-control" rows="5" v-model.trim="form.body"></textarea>
        <small class="form-text text-danger" v-if="errors.body">{{errors.body[0]}}</small>
      </div>
      <button type="submit" class="btn btn-primary">Create</button>
    </form>
  </div>
</template>

<script>
  export default {
    name: "dashboard",
    data() {
      return {
        form: {
          title: '',
          body: '',
        }
      }
    },
    methods: {
      async create() {
        try {
          await this.$axios.post('/topics', this.form);
        } catch (e) {
          return;
        }

        this.$router.push('/');
      }
    }
  }
</script>

<style scoped>

</style>

更新下Navbar.vue:

批注 2020-05-16 000208

<template>
  <nav class="navbar navbar-expand-lg navbar-light bg-light">
    <nuxt-link to="/" class="navbar-brand">Frontend</nuxt-link>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
            aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNav">
      <ul class="navbar-nav">
        <li class="nav-item active">
          <nuxt-link class="nav-link" to="/">Home</nuxt-link>
        </li>
        <li class="nav-item">
          <nuxt-link class="nav-link" to="/dashboard">Create</nuxt-link>
        </li>
      </ul>
      <template v-if="!authenticated">
        <ul class="navbar-nav ml-auto">
          <li class="nav-item">
            <nuxt-link class="nav-link" to="/login">Login</nuxt-link>
          </li>
          <li class="nav-item">
            <nuxt-link class="nav-link" to="/register">Register</nuxt-link>
          </li>
        </ul>
      </template>
      <template v-else>
        <ul class="navbar-nav ml-auto">
          <li class="nav-item">
            <a class="nav-link">{{user.name}}</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" @click.prevent="logout">Logout</a>
          </li>
        </ul>
      </template>
    </div>

  </nav>
</template>

<script>

  export default {
    name: "Navbar",
    methods: {
      logout() {
        this.$auth.logout();
      },
    }
  }
</script>

<style scoped>

</style>

创建topic 进入dashboard 应该要认证的用户才可以,所以需要在前端页面加middleware:

批注 2020-05-16 000530

dashboard.vue更新为:

<template>
  <div class="container col-md-6 mt-5">
    <h2>User Dashboard</h2>
    <hr>
    <h3>Create a new topic</h3>
    <form @submit.prevent="create">
      <div class="form-group">
        <label><strong>Topic Title:</strong></label>
        <input type="text" class="form-control" v-model.trim="form.title" autofocus>
        <small class="form-text text-danger" v-if="errors.title">{{errors.title[0]}}</small>
      </div>
      <div class="form-group">
        <label><strong>Topic Body:</strong></label>
        <textarea class="form-control" rows="5" v-model.trim="form.body"></textarea>
        <small class="form-text text-danger" v-if="errors.body">{{errors.body[0]}}</small>
      </div>
      <button type="submit" class="btn btn-primary">Create</button>
    </form>
  </div>
</template>

<script>
  export default {
    name: "dashboard",
    middleware: ['auth',],
    data() {
      return {
        form: {
          title: '',
          body: '',
        }
      }
    },
    methods: {
      async create() {
        try {
          await this.$axios.post('/topics', this.form);
        } catch (e) {
          return;
        }

        this.$router.push('/');
      }
    }
  }
</script>

<style scoped>

</style>

测试dashboard创建,暂时不输入内容:

提示为:

批注 2020-05-16 000933

输入内容测试:

批注 2020-05-16 001041

创建点击后,跳转了首页。

因为

批注 2020-05-16 001137

数据库中结果:

批注 2020-05-16 001236

批注 2020-05-16 001308

Return All Posts

打开backend项目,

api.php添加一条路由:

批注 2020-05-16 001556

TopicController.php添加index方法:

批注 2020-05-16 002114

使用Postman测试:

批注 2020-05-16 002138

Get All Posts –Nuxt

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

<template>
  <div class="container">

  </div>
</template>

<script>
  export default {
    name: "index.vue",
    data() {
      return {
        topics: [],
      }
    },
    async asyncData({$axios}) {
      let response = await $axios.get('/topics');
      console.log(response.data);
    }
  }
</script>

<style scoped>

</style>


Navbar.vue添加一个link跳转topics

批注 2020-05-16 002824

刷新页面 ,点击批注 2020-05-16 002903跳转topics

结果:

批注 2020-05-16 002952

修一下再看结果:

<template>
  <div class="container">
    <h2>Latest Topics</h2>
    <pre>{{topics}}</pre>
  </div>
</template>

<script>
  export default {
    name: "index.vue",
    data() {
      return {
        topics: [],
      }
    },
    async asyncData({$axios}) {
      let {data} = await $axios.$get('/topics');
      return {
        topics: data.data,
      }
    }
  }
</script>

<style scoped>

</style>

批注 2020-05-16 003646

Showing All Topics

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>{{topic.title}}</h2>
      <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>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    name: "index.vue",
    data() {
      return {
        topics: [],
      }
    },
    async asyncData({$axios}) {
      let {data} = await $axios.$get('/topics');
      return {
        topics: data,
      }
    }
  }
</script>

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

效果:

批注 2020-05-16 004221

Pagination

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>{{topic.title}}</h2>
      <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>
      </div>
    </div>
    <nav>
      <ul class="pagination justify-content-center">
        <li v-for="(key,value) in links" class="page-item">
          <a @click="loadMore(key)" class="page-link">{{value}}</a>
        </li>
      </ul>
    </nav>
  </div>
</template>

<script>
  export default {
    name: "index.vue",
    data() {
      return {
        topics: [],
        links: [],
      }
    },
    async asyncData({$axios}) {
      let {data, links,} = await $axios.$get('/topics');
      return {
        topics: data,
        links,
      }
    },
    methods: {
      async loadMore(key) {
        console.log(key);
      }
    }
  }
</script>

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

然后测试点击:

批注 2020-05-16 004818

更新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>{{topic.title}}</h2>
      <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>
      </div>
    </div>
    <nav>
      <ul class="pagination justify-content-center">
        <li v-for="(key,value) in links" class="page-item">
          <a @click.prevent="loadMore(key)" class="page-link">{{value}}</a>
        </li>
      </ul>
    </nav>
  </div>
</template>

<script>
  export default {
    name: "index.vue",
    data() {
      return {
        topics: [],
        links: [],
      }
    },
    async asyncData({$axios}) {
      let {data, links,} = await $axios.$get('/topics');
      return {
        topics: data,
        links,
      }
    },
    methods: {
      async loadMore(key) {
        if (key === null) {
          return;
        }
        let {data, links} = await this.$axios.$get(key);
        return this.topics = {...this.topics, ...data};
      }
    }
  }
</script>

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

Respond Single Topic

backend 项目 增加一个route到api.php:

批注 2020-05-16 005901

TopicController.php添加show方法:

批注 2020-05-16 010023

可以用PostMan测试一下结果:

批注 2020-05-16 010109

Get Single Topic

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

需要支持点击跳转到对应topic页面:

批注 2020-05-16 010637

而对应的页面新建pages/topics/_id/index.vue:

<template>
  <div class="container">
    <h2>Single Topic Page</h2>
  </div>
</template>

<script>
  export default {
    name: "index.vue"
  }
</script>

<style scoped>

</style>

效果:

nuxt-create_read

继续更新pages/topics/_id/index.vue:

<template>
  <div class="container">
    <h2>Single Topic Page</h2>
    <pre>{{topic}}</pre>
  </div>
</template>

<script>
  export default {
    name: "index.vue",
    data() {
      return {
        topic: "",
      }
    },
    async asyncData({$axios, params}) {
      const {data} = await $axios.$get(`/topics/${params.id}`)
      return {
        topic: data,
      }
    }
  }
</script>

<style scoped>

</style>

效果

批注 2020-05-16 011409

Show Single Topic

继续更新pages/topics/_id/index.vue:

<template>
  <div class="container">
    <div class="bg-light mt-5 mb-5" style="padding: 20px;">
      <h2>{{topic.title}}</h2>
      <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>
      </div>
    </div>
  </div>
</template>

<script>
  export default {
    name: "index.vue",
    data() {
      return {
        topic: '',
      }
    },
    async asyncData({$axios, params}) {
      const {data} = await $axios.$get(`/topics/${params.id}`);
      return {
        topic: data,
      }
    }
  }
</script>

<style scoped>

</style>

批注 2020-05-16 012011

源代码:

FrontEnd部分:

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

选择:

批注 2020-05-16 012246

Backend部分:

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

选择:

批注 2020-05-16 012603

posted @ 2020-05-16 00:14  dzkjz  阅读(44)  评论(0编辑  收藏  举报