在这一篇示例教程中,我们将会构建一个类似 Twitter 的 Web 应用。我们将使用到 Laravel 和 Vue.js,并且在 Vue.js 中定义一些组件,此外,还会使用 Axios 来发送网络请求。当然,篇幅有限,我们不可能开发一个完整的 Twitter 应用,而是实现一个简化版:用户可以发送 Tweet 并在自己的时间线中看到,可以关注或取消关注其他用户,如果关注了其他用户,那么也可以看到关注用户发布的 Tweet。麻雀虽小,五脏俱全,希望大家可以通过这个简单的应用学会 Laravel 和 Vue.js 的基础用法。
安装配置 Laravel
首先,我们需要安装一个新的 Laravel 应用(也可以通过 Composer 安装,看个人喜好):
laravel new laratwitter
进入该项目根目录,安装前端依赖:
npm install
接下来,修改 .env
中数据库相关配置符合本地环境,然后通过如下命令生成用户认证脚手架代码:
php artisan make:auth
运行如下命令生成相关数据表:
php artisan migrate
接下来配置下 Web 服务器(使用 Valet 的话略过),我这里配置的域名是 laratwitter.test
,配置完成后重启下 Web 服务器,然后通过 http://laratwitter.test/register
注册一个新用户:
接下来编辑 resoureces >> views >> home.blade.php
文件:
@extends('layouts.app')
@section('content')
class="container">
class="row justify-content-center">
class="col-md-4">
Tweet 表单
class="col-md-8">
时间线
@endsection
新注册用户登录后就能在新的 Home 页看到变更后的效果了。
创建一个 Tweet 表单
我们使用 Vue.js 来完成表单创建,首先到 resources >> assets >> js >> components
目录下新增一个 FormComponent.vue
文件:
// FormComponent.vue
<template>
<div class="col-md-4">
<form>
<div class="form-group">
<textarea class="form-control" rows="8" cols="8" maxlength="130" required>
textarea>
div>
<div class="form-group">
<button class="btn btn-primary">
发送
button>
div>
form>
div>
template>
<script>export default {
}script>
然后在 resources >> assets >> js >> app.js
中导入这个组件:
// app.js
require('./bootstrap');
window.Vue = require('vue');
Vue.component('form-component', require('./components/FormComponent.vue'));
const app = new Vue({
el: '#app'
});
最后在 home.blade.php
中使用这个组件:
@extends('layouts.app')
@section('content')
class="container"><div class="row justify-content-center"><form-component>form-component><div class="col-md-8">
TimeLinesdiv>div>div>
@endsection
为了监听前端资源变动,可以在项目根目录下运行如下命令监听前端资源变动并实时编译:
npm run watch
这样,刷新
http://laratwitter.test/home
页面就可以看到变更后的效果了:
创建 Post 模型类及对应数据库迁移文件
下面我们来实现表单提交保存操作,首先创建一个模型类及对应数据库迁移文件:
php artisan make:model Post -m
编写刚生成的数据库迁移文件
create_posts_table.php
:
// create_posts_table
public function up(){
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->string('body', 140);
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
}
运行数据库迁移命令:
php artisan migrate
现在可以在数据库中看到刚创建的
posts
数据表了。
接下来创建控制器
PostController
:
php artisan make:controller PostController
定义关联关系
在
User
模型类中,我们需要定义一个关联方法建立 Post
模型和 User
模型之间的关联关系(一对多):
public function posts(){
return $this->hasMany(Post::class);
}
相应地,在
Post
模型类中,也要定义与 User
模型的关联关系:
public function user(){
return $this->belongsTo(User::class);
}
在数据库中保存 Tweet
前面提到我们将通过
axios
库来发送 POST 请求到 Laravel 后端服务器,这个库在我们运行 npm install
命令的时候已经安装好了,在开始处理请求发送与处理之前,我们先来定义请求路由,在 routes >> web.php
文件中新增一个路由定义如下:
Route::post('tweet/save', 'PostController@store');
此外,还需要定义
Post
模型类的 $fillable
属性来避免批量赋值异常:
protected $fillable = ['user_id', 'body'];
最后要做的准备工作就是到
PostController.php
中定义 store
方法:
public function store(Request $request, Post $post){
$newPost = $request->user()->posts()->create([
'body' => $request->get('body')
]);
return response()->json($post->with('user')->find($newPost->id));
}
我们使用了关联关系来保存 Post 数据,它会自动将当前登录用户的
id
作为保存 Post 的 user_id
字段。
后端逻辑实现之后,接下来需要使用
axios
库类发送 POST 请求,我们将相应逻辑写到 FormComponent.vue
组件中:
// FormComponent.vue
<template>
<div class="col-md-4">
<form @submit.prevent="saveTweet">
<div class="form-group">
<textarea class="form-control" rows="8" cols="8" maxlength="130"v-model="body"required>
textarea>
div>
<div class="form-group">
<button class="btn btn-primary">
Tweet
button>
div>
form>
div>
template>
<script>export default {
data() {return {body: ''
}
},methods: {
saveTweet() {
axios.post('/tweet/save', {body: this.body}).then(res => {console.log(res.data);
}).catch(e => {console.log(e);
});
}
}
}script>
CSRF_TOKEN
会默认包含到每个 POST 请求,所以不必手动添加。现在,如果一切正常工作的话,你就可以成功保存 Post 数据并在响应中获取到 Post 对象及其关联用户:
创建一个 Vue 事件
要想在前端显示所有 Tweet,首先需要将它们显示到时间线中,为此,我们需要创建一个事件。注意,这里不是要通过刷新页面来获取所有 Tweet,而是当我们添加一条新的 Tweet 时,在不刷新页面的情况下在用户时间线中显示这条 Tweet。
为了实现这个功能,需要创建一个事件并触发这个事件,我们通过时间线组件来监听事件触发并基于该事件更新 UI。可以通过 Vuex 来实现该功能,但是现在,我们不想要深入存储和动作,只想将事情保持简单。
在
resources >> assets >> js
目录下创建一个名为 event.js
的文件,并编写代码如下:
// event.js
import Vue from 'vue';
export default new Vue();
然后将该文件导入
FormComponent.vue
:
// FormComponent.vue
<template>
<div class="col-md-4">
<form @submit.prevent="saveTweet">
<div class="form-group">
<textarea class="form-control" rows="8" cols="8" maxlength="130"v-model="body"required>
textarea>
div>
<div class="form-group">
<button class="btn btn-primary">
Tweet
button>
div>
form>
div>
template>
<script>import Event from '../event.js';export default {
data() {return {body: '',postData: {}
}
},methods: {
saveTweet() {
axios.post('/tweet/save', {body: this.body}).then(res => {this.postData = res.data;
Event.$emit('added_tweet', this.postData);
}).catch(e => {console.log(e);
});this.body = '';
}
}
}script>
这样,当新提交数据被保存后,就可以触发包含保存的 Tweet 和用户信息的事件,监听该事件的监听器就可以捕获数据并更新 UI。
创建时间线组件
接下来,我们来定义监听 Tweet 发送成功事件的监听器。
在
resources >> assets >> js >> components
目录下新增一个 TimelineComponent.vue
组件,编写该组件代码如下:
// TimelineComponent.vue
<template>
<div class="col-md-8 posts">
<p v-if="!posts.length">No postsp>
<div class="media" v-for="post in posts" :key="post.id">
<img class="mr-3" />
<div class="media-body">
<div class="mt-3">
<a href="#">{{ post.user.name }}a>
div>
<p>{{ post.body }}p>
div>
div>
div>
template>
<script>import Event from '../event.js';export default {
data() {return {posts: [],post: {}
}
},
mounted() {
Event.$on('added_tweet', (post) => {this.posts.unshift(post);
});
}
}script>
这里我们定义了一个监听
added_tweet
事件的监听器,当该事件被触发后,就可以执行相应的方法将数据渲染到时间线组件中。
和
FormComponent.vue
组件一样,在 app.js
中注册这个组件:
Vue.component('timeline-component', require('./components/TimelineComponent.vue'));
然后更新
home.blade.php
视图模板文件:
@extends('layouts.app')
@section('content')
class="container"><div class="row justify-content-center"><form-component>form-component><timeline-component>timeline-component>div>div>
@endsection
再次访问
http://laratwitter.test/home
,新增 Tweet 后,就可以在页面右侧看到刚刚发送的数据了:
在时间线中显示时间
在时间线中显示时间
我们需要在
Post
模型类中追加一个属性来获取时间:
<?php namespace App;use Illuminate\Database\Eloquent\Model;class Post extends Model{protected $fillable = ['user_id', 'body'];protected $appends = ['createdDate'];public function user(){return $this->belongsTo(User::class);
}public function getCreatedDateAttribute(){return $this->created_at->diffForHumans();
}
}
这样,我们就可以在
TimelineComponent.vue
中访问 createdDate
属性了:
<div class="mt-3">
<a href="#">
{{ post.user.name }}
a> | {{ post.createdDate }}
div>
刷新
http://laratwitter.test/home
,再次新增一条 Tweet 后,效果如下:
创建用户主页
创建用户主页
我们使用路由模型绑定来显示用户主页,并基于用户名生成链接作为用户唯一URL。为此,我们需要修改
RegisterController.php
文件中的验证方法保证用户名的唯一性:
protected function validator(array $data){
return Validator::make($data, [
'name' => 'required|string|max:255|unique:users',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
}
接下来创建一个控制器
UserController
:
php artisan make:controller UserController
然后在该控制器中添加如下方法:
public function show(User $user){
return view('user', compact('user'));
}
我们通过用户名来实现路由模型绑定,需要在
User
模型类中定义路由键值:
public function getRouteKeyName(){
return 'name';
}
在
views
目录下新增 user.blade.php
视图模板文件:
@extends('layouts.app')
@section('content')
<div class="container">
{{ $user->name }}
div>
@endsection
接下来在
routes/web.php
文件中新增用户主页路由:
Route::get('users/{user}', 'UserController@show')->name('user.show');
这样,通过之前注册用户名就可以访问该用户的主页了:
http://laratwitter.test/users/学院君
将用户主页链接显示到时间线中
将用户主页链接显示到时间线中
我们可以通过在业务代码中编码的方式每个用户生成用户链接,也可以通过模型属性的方式统一定义,在这里,我们通过模型属性方式来定义,在
User
模型类中新增一个 profileLink
访问器及对应获取方法:
protected $appends = ['profileLink'];
public function getProfileLinkAttribute(){
return route('user.show', $this);
}
这样我们就可以在时间线组件
TimelineComponent.vue
中显示用户主页链接了:
// TimelineComponent.vue
<template>
<div class="col-md-8 posts">
<p v-if="!posts.length">No postsp>
<div class="media" v-for="post in posts" :key="post.id">
<img class="mr-3" />
<div class="media-body">
<div class="mt-3">
<a :href="post.user.profileLink">{{ post.user.name }}a> | {{ post.createdDate }}
div>
<p>{{ post.body }}p>
div>
div>
div>
template>
<script>import Event from '../event.js';export default {
data() {return {posts: [],post: {}
}
},
mounted() {
Event.$on('added_tweet', (post) => {this.posts.unshift(post);
});
}
}script>
显示关注/取消关注链接
显示关注/取消关注链接
开始之前,先要为关注者创建模型类及对应数据库迁移文件:
php artisan make:model Follower -m
编写刚生成的
create_followers_table
迁移文件:
public function up(){
Schema::create('followers', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->integer('follower_id')->unsigned();
$table->nullableTimestamps();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('follower_id')->references('id')->on('users')->onDelete('cascade');
});
}
然后运行迁移命令生成数据表:
php artisan migrate
接下来在用户模型类中定义用户与关注者的关联关系(多对多):
public function following(){
return $this->belongsToMany(User::class, 'followers', 'user_id', 'follower_id');
}
下一步,我们需要为用户关注行为定义一些约束条件:
用户不能关注自己
在没有关注的情况下才能关注某用户
已经关注的情况下才能取消关注某用户
我们可以将这些约束条件定义到
User
模型类中:
// 不能关注自己
public function isNot($user){
return $this->id !== $user->id;
}
// 是否已经关注某用户
public function isFollowing($user){
return (bool) $this->following->where('id', $user->id)->count();
}
// 是否能够关注某用户
public function canFollow($user){
if(!$this->isNot($user)) {
return false;
}
return !$this->isFollowing($user);
}
好了,现在我们可以基于上述条件进行关注和取消关注操作了,首先我们在此基础上在用户主页显示关注/取消关注链接:
@extends('layouts.app')
@section('content')
class="container">
class="row">
class="col-md-12">
{{ $user->name }}
{{ $user->name }}
@if(auth()->user()->isNot($user))@if(auth()->user()->isFollowing($user))"#" class="btn btn-danger">取消关注</a>@else"#" class="btn btn-success">关注</a>@endif@endif
@endsection
现在如果你访问自己的个人主页的话那么什么链接也看不到,这就对了,如果以其他用户身份登录访问这个个人主页页面,则可以看到关注或取消关注链接。
关注用户
关注用户
首先在
routes/web.php
中定义关注用户路由:
Route::get('users/{user}/follow', 'UserController@follow')->name('user.follow');
然后在
UserController
控制器中定义 follow
方法:
public function follow(Request $request, User $user){
if($request->user()->canFollow($user)) {
$request->user()->following()->attach($user);
}
return redirect()->back();
}
此外还需要更新
user.blade.php
视图模板:
@extends('layouts.app')
@section('content')
class="container">
class="row">
class="col-md-12">
{{ $user->name }}
{{ $user->name }}
@if(auth()->user()->isNot($user))@if(auth()->user()->isFollowing($user))"#" class="btn btn-danger">取消关注</a>@else"{{ route('user.follow', $user) }}" class="btn btn-success">关注</a>@endif@endif
@endsection
为了测试关注功能,我们需要新注册一个用户,比如
学院君小号
,然后以新注册用户身份登录并访问 http://laratwitter.test/users/学院君
,就可以看到关注链接了:
点击「关注」按钮,关注成功后页面刷新:
取消关注
取消关注
在编写取消关注功能之前,需要在
User
模型类中定义如下方法用于判断是否可以取消关注:
public function canUnFollow($user){
return $this->isFollowing($user);
}
然后在
routes/web.php
中定义取消关注路由:
Route::get('users/{user}/unfollow', 'UserController@unfollow')->name('user.unfollow');
接下来在
UserController
中定义 unFollow
方法:
public function unFollow(Request $request, User $user){
if($request->user()->canUnFollow($user)) {
$request->user()->following()->detach($user);
}
return redirect()->back();
}
最后更新
user.blade.php
视图模板:
@if(auth()->user()->isNot($user))
@if(auth()->user()->isFollowing($user))
"{{ route('user.unfollow', $user) }}" class="btn btn-danger">取消关注</a>@else"{{ route('user.follow', $user) }}" class="btn btn-success">关注</a>@endif@endif
这样,关注和取消关注功能都完成了。
基于关注用户获取 Tweet
基于关注用户获取 Tweet
至此,我们已经基本完成了 Twitter 的核心功能。
最后要做的是获取你所关注用户发送的所有 Tweet,这样,当我们关注某个用户后,就可以看到他的所有 Tweet。
首先,我们在
PostController
控制器中定义一个 index
方法:
public function index(Request $request, Post $post){
$posts = $post->whereIn('user_id', $request->user()->following()
->pluck('users.id')
->push($request->user()->id))
->with('user')
->orderBy('created_at', 'desc')
->take($request->get('limit', 10))
->get();
return response()->json($posts);
}
通过该方法,我们可以获取到用户自己以及所关注用户的所有 Tweet,要在前台显示这些 Tweet,需要在
routes/web.php
中定义一个新的路由:
Route::get('posts', 'PostController@index')->name('posts.index');
然后,我们在时间线组件
TimelineComponent.vue
中通过 axios
库发送请求获取所有 Tweet:
<template>
<div class="col-md-8 posts">
<p v-if="!posts.length">No postsp>
<div class="media" v-for="post in posts" :key="post.id">
<img class="mr-3" />
<div class="media-body">
<div class="mt-3">
<a :href="post.user.profileLink">{{ post.user.name }}a> | {{ post.createdDate }}
div>
<p>{{ post.body }}p>
div>
div>
div>
template>
<script>import Event from '../event.js';export default {
data() {return {posts: [],post: {}
}
},
mounted() {
axios.get('/posts').then((resp => {this.posts = resp.data;
}));
Event.$on('added_tweet', (post) => {this.posts.unshift(post);
});
}
}script>
现在,我们访问
http://laratwitter.test/home
就可以看到关注者和自己发送的所有 Tweet 了:
至此,一个基于 Laravel + Vue 实现的小型 Twitter 网站已经开发完成,你可以对其进行添砖加瓦,将其变成一个真正可用的在线产品。
Tips:如果按照此教程在实践过程中,发现前端组件调整后不生效,可以尝试在每次更改后运行
npm run dev
手动编译前端资源。
本系列教程首发在Laravel学院(laravelacademy.org),你可以点击页面左下角阅读原文链接查看最新更新的教程。