基于Laravel开发网站实时评论系统

基于Laravel搭建网站实时评论系统

为了在你的在线社区或博客中建立信任,你需要设计开发一套体验良好的Laravel实时评论系统。

然而,要想在第一次尝试时就做到这一点并不容易,除非你依赖于诸如Discus或Commento这样的自托管评论系统,因为它们都有自己的缺点。他们拥有你的数据,提供有限的设计和定制,最重要的是,他们不是免费的。

有了这些限制,如果构建实时评论系统的想法吸引了你,那就继续阅读吧。该系统可以控制你的数据,设计和定制适合你博客的外观和感觉。

本文将教您如何开发一个设计良好的实时评论系统,该系统具有不同的评论功能。遵循使用Vue.js和Socket.io构建实时聊天应用程序的原则,我们将使用Laravel、Pusher和React来开发实时评论系统。

  1. 我们将开发什么
  2. 开发所需的技术
  3. 开发评论系统实例

我们将开发什么

我们将建立一个实时评论系统,可以集成到任何网站或博客中,以建立社区信任。

开发所需的技术

在深入开发之前,让我们先讨论一下开发实时评论系统所使用的技术。

Laravel

Laravel是一个开源的面向MVC的PHP框架。它用于构建简单到复杂的PHP web应用程序,以其优雅的语法而闻名。学习什么是拉拉维尔是必不可少的建设这个评论系统。

Pusher

Pusher使开发人员能够按比例创建实时功能。本文将结合Laravel Echo创建到Pusher服务器的实时广播事件,并使用Vue.js在前端显示内容。

Vue.js

Vue.js是我们选择的前端框架。Vue.js是一个渐进式JavaScript前端框架,以其易于学习和直接的前端开发方法而闻名。我们将使用Vue.js开发实时评论系统。

开发评论系统实例

如果我们上面概述的评论系统听起来像您想要的,那么让我们继续构建它。

1. 安装和设置Laravel, Pusher和Echo

Laravel、Echo和Pusher的安装和设置非常简单,因为Laravel通过设置和配置Laravel Echo以完美配合Pusher完成了所有后台任务。

首先,我们将从安装和配置我们的后端PHP框架Laravel开始。如果您已全局安装了Laravel CLI,则可以使用此命令获取Laravel的新实例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
laravel new commenter
laravel new commenter
laravel new commenter

新的Laravel实例将安装在名为commenter的文件夹中。让我们在VSCode中打开文件夹,并在终端中导航到它:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cd commenter
code .
cd commenter code .
cd commenter

code .

在启动开发服务器之前,让我们安装并配置一些必要的包,这些包将用于项目。

运行此命令以安装Pusher PHP SDK:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
composer require pusher/pusher-php-server
composer require pusher/pusher-php-server
composer require pusher/pusher-php-server

运行此命令为Vue.js前端安装必要的NPM软件包:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm install --save laravel-echo pusher-js
npm install --save laravel-echo pusher-js
npm install --save laravel-echo pusher-js

接下来,我们将配置Laravel Echo和Pusher。打开resources/js/bootstrap.js文件并粘贴到以下脚本中:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
window._ = require("lodash");
window.axios = require("axios");
window.moment = require("moment");
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
window.axios.defaults.headers.post["Content-Type"] =
"application/x-www-form-urlencoded";
window.axios.defaults.headers.common.crossDomain = true;
window.axios.defaults.baseURL = "/api";
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common["X-CSRF-TOKEN"] = token.content;
} else {
console.error("CSRF token not found");
}
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that Laravel broadcasts. Echo and event broadcasting
* allows your team to build robust real-time web applications quickly.
*/
import Echo from "laravel-echo";
window.Pusher = require("pusher-js");
window.Echo = new Echo({
broadcaster: "pusher",
key: process.env.MIX_PUSHER_APP_KEY,
cluster: process.env.MIX_PUSHER_APP_CLUSTER,
forceTLS: true
});
window._ = require("lodash"); window.axios = require("axios"); window.moment = require("moment"); window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest"; window.axios.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded"; window.axios.defaults.headers.common.crossDomain = true; window.axios.defaults.baseURL = "/api"; let token = document.head.querySelector('meta[name="csrf-token"]'); if (token) { window.axios.defaults.headers.common["X-CSRF-TOKEN"] = token.content; } else { console.error("CSRF token not found"); } /** * Echo exposes an expressive API for subscribing to channels and listening * for events that Laravel broadcasts. Echo and event broadcasting * allows your team to build robust real-time web applications quickly. */ import Echo from "laravel-echo"; window.Pusher = require("pusher-js"); window.Echo = new Echo({ broadcaster: "pusher", key: process.env.MIX_PUSHER_APP_KEY, cluster: process.env.MIX_PUSHER_APP_CLUSTER, forceTLS: true });
window._ = require("lodash");
window.axios = require("axios");
window.moment = require("moment");
window.axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
window.axios.defaults.headers.post["Content-Type"] =
    "application/x-www-form-urlencoded";
window.axios.defaults.headers.common.crossDomain = true;
window.axios.defaults.baseURL = "/api";
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
    window.axios.defaults.headers.common["X-CSRF-TOKEN"] = token.content;
} else {
    console.error("CSRF token not found");
}


/**
 * Echo exposes an expressive API for subscribing to channels and listening
 * for events that Laravel broadcasts. Echo and event broadcasting
 * allows your team to build robust real-time web applications quickly.
 */
import Echo from "laravel-echo";
window.Pusher = require("pusher-js");
window.Echo = new Echo({
    broadcaster: "pusher",
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    forceTLS: true
});

您会从上面的脚本中注意到,我们只是使用默认配置配置Axios实例。接下来,我们将配置Laravel Echo以使用Pusher及其配置。

2. 数据库设置和迁移

接下来,我们将创建并设置数据库来存储评论以保持持久性。我们将使用 SQLite,但您可以使用您选择的任何数据库客户端。

在数据库文件夹内创建一个数据库.sqlite文件,并更新您的.env文件如下:

接下来,我们将创建并设置数据库,以存储持久性注释。我们将使用SQLite,不过您可以选择使用任何数据库客户机。

在数据库文件夹中创建一个database.sqlite文件,并按如下方式更新.env文件:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
DB_CONNECTION=sqlite
DB_DATABASE=/Users/all/paths/to/project/commenter_be/database/database.sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=
DB_CONNECTION=sqlite DB_DATABASE=/Users/all/paths/to/project/commenter_be/database/database.sqlite DB_HOST=127.0.0.1 DB_PORT=3306 DB_USERNAME=root DB_PASSWORD=
DB_CONNECTION=sqlite
DB_DATABASE=/Users/all/paths/to/project/commenter_be/database/database.sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=

接下来,运行此命令创建注释迁移,并使用以下脚本进行更新:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan make:migration create_comments_table
php artisan make:migration create_comments_table
php artisan make:migration create_comments_table

打开database/migrations/xxxx_create_comments_table_xxxx.php文件并粘贴以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCommentsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->string('content');
$table->string('author');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('comments');
}
}
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateCommentsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('comments', function (Blueprint $table) { $table->id(); $table->string('content'); $table->string('author'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('comments'); } }
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateCommentsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('comments', function (Blueprint $table) {
            $table->id();
            $table->string('content');
            $table->string('author');
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('comments');
    }
}

这将创建一个新的comments数据库表,并添加content和author列。

最后,要创建迁移,请运行以下命令:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan migrate
php artisan migrate
php artisan migrate

3. 创建模型

在Laravel中,模型非常重要——它们是与数据库通信和处理数据管理的最可靠的方式。

要在Laravel中创建模型,我们将运行以下命令:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan make:model Comment
php artisan make:model Comment
php artisan make:model Comment

接下来,打开app/models/Comment.php文件并粘贴以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
use HasFactory;
protected $fillable = ['content', 'author'];
}
The $fillable array allows us to create and update the model in mass.
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Comment extends Model { use HasFactory; protected $fillable = ['content', 'author']; } The $fillable array allows us to create and update the model in mass.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
    use HasFactory;
    protected $fillable = ['content', 'author'];
}

The $fillable array allows us to create and update the model in mass.

4. 创建控制器

控制器非常重要,因为它们包含应用程序的所有逻辑、业务和其他方面,所以让我们创建一个控制器来处理注释逻辑:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
php artisan make:controller CommentController
php artisan make:controller CommentController
php artisan make:controller CommentController

接下来,打开app/Http/Controllers/CommentController.php文件并粘贴以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
namespace App\Http\Controllers;
use App\Models\Comment;
use App\Events\CommentEvent;
use Illuminate\Http\Request;
class CommentController extends Controller
{
//
public function index()
{
return view('comments');
}
public function fetchComments()
{
$comments = Comment::all();
return response()->json($comments);
}
public function store(Request $request)
{
$comment = Comment::create($request->all());
event(new CommentEvent($comment));
return $comment;
}
}
<?php namespace App\Http\Controllers; use App\Models\Comment; use App\Events\CommentEvent; use Illuminate\Http\Request; class CommentController extends Controller { // public function index() { return view('comments'); } public function fetchComments() { $comments = Comment::all(); return response()->json($comments); } public function store(Request $request) { $comment = Comment::create($request->all()); event(new CommentEvent($comment)); return $comment; } }
<?php
namespace App\Http\Controllers;
use App\Models\Comment;
use App\Events\CommentEvent;
use Illuminate\Http\Request;

class CommentController extends Controller
{
    //
    public function index()
    {
        return view('comments');
    }
    public function fetchComments()
    {
        $comments = Comment::all();
        return response()->json($comments);
    }
    public function store(Request $request)
    {
        $comment = Comment::create($request->all());
        event(new CommentEvent($comment));
        return $comment;
    }
}

控制器有三种不同的方法:分别返回注释视图、获取所有注释和存储新注释。最重要的是,每次存储新评论时,我们都会触发一个事件,前端将监听该事件,以便使用Pusher和Laravel Echo实时使用新评论更新相关页面。

5. 创建路由

要正确配置路由,我们需要更新大量文件,所以让我们开始吧。

首先,我们将更新routes文件夹中的api.php文件。打开文件并添加以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
use App\Http\Controllers\CommentController;
//...
Route::get('/', [CommentController::class, 'index']);
Route::get('/comments', [CommentController::class, 'fetchComments']);
Route::post('/comments', [CommentController::class, 'store']);
use App\Http\Controllers\CommentController; //... Route::get('/', [CommentController::class, 'index']); Route::get('/comments', [CommentController::class, 'fetchComments']); Route::post('/comments', [CommentController::class, 'store']);
use App\Http\Controllers\CommentController;
//...

Route::get('/', [CommentController::class, 'index']);
Route::get('/comments', [CommentController::class, 'fetchComments']);
Route::post('/comments', [CommentController::class, 'store']);

接下来,打开同一文件夹中的channels.php文件,并添加以下代码以授权我们先前触发的事件:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Broadcast::channel('comment', function ($user) {
return true;
});
Broadcast::channel('comment', function ($user) { return true; });
Broadcast::channel('comment', function ($user) {
    return true;
});

接下来,在同一文件夹中打开Web.php文件,并添加以下代码以将我们的请求重定向到主页,Vue.js将在主页上接收请求:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
use App\Http\Controllers\CommentController;
//...
Route::get('/', [CommentController::class, 'index']);
use App\Http\Controllers\CommentController; //... Route::get('/', [CommentController::class, 'index']);
use App\Http\Controllers\CommentController;
//...

Route::get('/', [CommentController::class, 'index']);

最后,我们将在resources/views文件夹中创建一个名为comments.blade.php的新blade文件,并添加以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Commenter</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css" />
<style>
.container {
margin: 0 auto;
position: relative;
width: unset;
}
#app {
width: 60%;
margin: 4rem auto;
}
.question-wrapper {
text-align: center;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<div class="question-wrapper">
<h5 class="is-size-2" style="color: #220052;">
What do you think about <span style="color: #47b784;">Dogs</span>?</h5>
<br>
<a href="#Form" class="button is-medium has-shadow has-text-white" style="background-color: #47b784">Comment</a>
</div>
<br><br>
<comments></comments>
<new-comment></new-comment>
</div>
</div>
<script async src="{{mix('js/app.js')}}"></script>
</body>
</html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Commenter</title> <meta name="csrf-token" content="{{ csrf_token() }}"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css" /> <style> .container { margin: 0 auto; position: relative; width: unset; } #app { width: 60%; margin: 4rem auto; } .question-wrapper { text-align: center; } </style> </head> <body> <div id="app"> <div class="container"> <div class="question-wrapper"> <h5 class="is-size-2" style="color: #220052;"> What do you think about <span style="color: #47b784;">Dogs</span>?</h5> <br> <a href="#Form" class="button is-medium has-shadow has-text-white" style="background-color: #47b784">Comment</a> </div> <br><br> <comments></comments> <new-comment></new-comment> </div> </div> <script async src="{{mix('js/app.js')}}"></script> </body> </html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Commenter</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css" />
    <style>
        .container {
            margin: 0 auto;
            position: relative;
            width: unset;
        }
        #app {
            width: 60%;
            margin: 4rem auto;
        }
        .question-wrapper {
            text-align: center;
        }
    </style>
</head>
<body>


    <div id="app">
        <div class="container">
            <div class="question-wrapper">
                <h5 class="is-size-2" style="color: #220052;">
                    What do you think about <span style="color: #47b784;">Dogs</span>?</h5>
                <br>
                <a href="#Form" class="button is-medium has-shadow has-text-white" style="background-color: #47b784">Comment</a>
            </div>
            <br><br>
            <comments></comments>
            <new-comment></new-comment>
        </div>
    </div>
    <script async src="{{mix('js/app.js')}}"></script>
</body>
</html>

脚本将添加一个文章标题和一个Vue组件,以显示并向上面创建的文章标题添加新注释。

运行以下命令以测试是否正确获取了所有内容:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm run watch
php artisan serve
npm run watch php artisan serve
npm run watch

php artisan serve

如果您看到了这个页面,您就可以进入本文的下一步了。

Laravel的实时评论系统

Laravel的实时评论系统

6. 设置Vue (前端)

我们将创建并设置我们的Vue实例,以创建并显示对此帖子的所有评论。

我们将从建立Vuex store开始。在resource/js/store文件夹中创建以下文件。

创建注释状态

创建actions.js并添加以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let actions = {
ADD_COMMENT({ commit }, comment) {
return new Promise((resolve, reject) => {
axios
.post(`/comments`, comment)
.then(response => {
resolve(response);
})
.catch(err => {
reject(err);
});
});
},
GET_COMMENTS({ commit }) {
axios
.get("/comments")
.then(res => {
{
commit("GET_COMMENTS", res.data);
}
})
.catch(err => {
console.log(err);
});
}
};
export default actions;
let actions = { ADD_COMMENT({ commit }, comment) { return new Promise((resolve, reject) => { axios .post(`/comments`, comment) .then(response => { resolve(response); }) .catch(err => { reject(err); }); }); }, GET_COMMENTS({ commit }) { axios .get("/comments") .then(res => { { commit("GET_COMMENTS", res.data); } }) .catch(err => { console.log(err); }); } }; export default actions;
let actions = {
    ADD_COMMENT({ commit }, comment) {
        return new Promise((resolve, reject) => {
            axios
                .post(`/comments`, comment)
                .then(response => {
                    resolve(response);
                })
                .catch(err => {
                    reject(err);
                });
        });
    },
    GET_COMMENTS({ commit }) {
        axios
            .get("/comments")
            .then(res => {
                {
                    commit("GET_COMMENTS", res.data);
                }
            })
            .catch(err => {
                console.log(err);
            });
    }
};
export default actions;

操作文件调用后端中的注释端点。

接下来,创建一个getters.js文件并添加以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let getters = {
comments: state => {
return state.comments;
}
};
export default getters;
let getters = { comments: state => { return state.comments; } }; export default getters;
let getters = {
    comments: state => {
        return state.comments;
    }
};
export default getters;

Getter文件用于检索状态中的所有注释。

创建translations.js文件并将其粘贴到以下代码中:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let mutations = {
GET_COMMENTS(state, comments) {
state.comments = comments;
},
ADD_COMMENT(state, comment) {
state.comments = [...state.comments, comment];
}
};
export default mutations;
let mutations = { GET_COMMENTS(state, comments) { state.comments = comments; }, ADD_COMMENT(state, comment) { state.comments = [...state.comments, comment]; } }; export default mutations;
let mutations = {
    GET_COMMENTS(state, comments) {
        state.comments = comments;
    },
    ADD_COMMENT(state, comment) {
        state.comments = [...state.comments, comment];
    }
};
export default mutations;

接下来,创建state.js文件并将其粘贴到以下代码中:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let state = {
comments: []
};
export default state;
let state = { comments: [] }; export default state;
let state = {
    comments: []
};
export default state;

最后,我们将向导出到Vue实例的index.js文件添加所有内容,创建index.js文件并添加以下内容:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import Vue from "vue";
import Vuex from "vuex";
import actions from "./actions";
import mutations from "./mutations";
import getters from "./getters";
import state from "./state";
Vue.use(Vuex);
export default new Vuex.Store({
state,
mutations,
getters,
actions
});
import Vue from "vue"; import Vuex from "vuex"; import actions from "./actions"; import mutations from "./mutations"; import getters from "./getters"; import state from "./state"; Vue.use(Vuex); export default new Vuex.Store({ state, mutations, getters, actions });
import Vue from "vue";
import Vuex from "vuex";
import actions from "./actions";
import mutations from "./mutations";
import getters from "./getters";
import state from "./state";
Vue.use(Vuex);
export default new Vuex.Store({
    state,
    mutations,
    getters,
    actions
});

创建组件

最后,我们将创建注释组件以显示和添加新注释。让我们从创建单个注释组件开始。

resource/js文件夹中创建一个名为components的文件夹,添加comment.vue并添加以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<template>
<li class="comment-wrapper animate slideInLeft">
<div class="profile">
</div>
<div class="msg has-shadow">
<div class="msg-body">
<p class="name">
{{ comment.author }} <span class="date">{{ posted_at }}</span>
</p>
<p class="content">{{ comment.content }}</p>
</div>
</div>
</li>
</template>
<script>
export default {
name: "Comment",
props: ["comment"],
computed: {
posted_at() {
return moment(this.comment.created_at).format("MMMM Do YYYY");
},
},
};
</script>
<style lang="scss" scoped>
.comment-wrapper {
list-style: none;
text-align: left;
overflow: hidden;
margin-bottom: 2em;
padding: 0.4em;
.profile {
width: 80px;
float: left;
}
.msg-body {
padding: 0.8em;
color: #666;
line-height: 1.5;
}
.msg {
width: 86%;
float: left;
background-color: #fff;
border-radius: 0 5px 5px 5px;
position: relative;
&::after {
content: " ";
position: absolute;
left: -13px;
top: 0;
border: 14px solid transparent;
border-top-color: #fff;
}
}
.date {
float: right;
}
.name {
margin: 0;
color: #999;
font-weight: 700;
font-size: 0.8em;
}
p:last-child {
margin-top: 0.6em;
margin-bottom: 0;
}
}
</style>
<template> <li class="comment-wrapper animate slideInLeft"> <div class="profile"> </div> <div class="msg has-shadow"> <div class="msg-body"> <p class="name"> {{ comment.author }} <span class="date">{{ posted_at }}</span> </p> <p class="content">{{ comment.content }}</p> </div> </div> </li> </template> <script> export default { name: "Comment", props: ["comment"], computed: { posted_at() { return moment(this.comment.created_at).format("MMMM Do YYYY"); }, }, }; </script> <style lang="scss" scoped> .comment-wrapper { list-style: none; text-align: left; overflow: hidden; margin-bottom: 2em; padding: 0.4em; .profile { width: 80px; float: left; } .msg-body { padding: 0.8em; color: #666; line-height: 1.5; } .msg { width: 86%; float: left; background-color: #fff; border-radius: 0 5px 5px 5px; position: relative; &::after { content: " "; position: absolute; left: -13px; top: 0; border: 14px solid transparent; border-top-color: #fff; } } .date { float: right; } .name { margin: 0; color: #999; font-weight: 700; font-size: 0.8em; } p:last-child { margin-top: 0.6em; margin-bottom: 0; } } </style>
<template>
  <li class="comment-wrapper animate slideInLeft">
    <div class="profile">
    </div>
    <div class="msg has-shadow">
      <div class="msg-body">
        <p class="name">
          {{ comment.author }} <span class="date">{{ posted_at }}</span>
        </p>
        <p class="content">{{ comment.content }}</p>
      </div>
    </div>
  </li>
</template>
    
    <script>
export default {
  name: "Comment",
  props: ["comment"],
  computed: {
    posted_at() {
      return moment(this.comment.created_at).format("MMMM Do YYYY");
    },

  },
};
</script>
    
    <style lang="scss" scoped>
.comment-wrapper {
  list-style: none;
  text-align: left;
  overflow: hidden;
  margin-bottom: 2em;
  padding: 0.4em;
  .profile {
    width: 80px;
    float: left;
  }
  .msg-body {
    padding: 0.8em;
    color: #666;
    line-height: 1.5;
  }
  .msg {
    width: 86%;
    float: left;
    background-color: #fff;
    border-radius: 0 5px 5px 5px;
    position: relative;
    &::after {
      content: " ";
      position: absolute;
      left: -13px;
      top: 0;
      border: 14px solid transparent;
      border-top-color: #fff;
    }
  }
  .date {
    float: right;
  }
  .name {
    margin: 0;
    color: #999;
    font-weight: 700;
    font-size: 0.8em;
  }
  p:last-child {
    margin-top: 0.6em;
    margin-bottom: 0;
  }
}
</style>

接下来,在同一文件夹中创建名为comments.vue的以下文件,并添加以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<template>
<div class="container">
<ul class="comment-list">
<Comment
:key="comment.id"
v-for="comment in comments"
:comment="comment"
></Comment>
</ul>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import Comment from "./Comment";
export default {
name: "Comments",
components: { Comment },
mounted() {
this.$store.dispatch("GET_COMMENTS");
this.listen();
},
methods: {
listen() {
Echo.channel("comment").listen("comment", (e) => {
console.log(e);
this.$store.commit("ADD_COMMENT", e);
});
},
},
computed: {
...mapGetters(["comments"]),
},
};
</script>
<style scoped>
.comment-list {
padding: 1em 0;
margin-bottom: 15px;
}
</style>
<template> <div class="container"> <ul class="comment-list"> <Comment :key="comment.id" v-for="comment in comments" :comment="comment" ></Comment> </ul> </div> </template> <script> import { mapGetters } from "vuex"; import Comment from "./Comment"; export default { name: "Comments", components: { Comment }, mounted() { this.$store.dispatch("GET_COMMENTS"); this.listen(); }, methods: { listen() { Echo.channel("comment").listen("comment", (e) => { console.log(e); this.$store.commit("ADD_COMMENT", e); }); }, }, computed: { ...mapGetters(["comments"]), }, }; </script> <style scoped> .comment-list { padding: 1em 0; margin-bottom: 15px; } </style>
<template>
  <div class="container">
    <ul class="comment-list">
      <Comment
        :key="comment.id"
        v-for="comment in comments"
        :comment="comment"
      ></Comment>
    </ul>
  </div>
</template>
    
    <script>
import { mapGetters } from "vuex";
import Comment from "./Comment";
export default {
  name: "Comments",
  components: { Comment },
  mounted() {
    this.$store.dispatch("GET_COMMENTS");
    this.listen();
  },
  methods: {
    listen() {
      Echo.channel("comment").listen("comment", (e) => {
        console.log(e);
        this.$store.commit("ADD_COMMENT", e);
      });
    },
  },
  computed: {
    ...mapGetters(["comments"]),
  },
};
</script>
    
    <style scoped>
.comment-list {
  padding: 1em 0;
  margin-bottom: 15px;
}
</style>

最后,创建一个名为NewComment.vue的文件并添加以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<template>
<div id="commentForm" class="box has-shadow has-background-white">
<form @keyup.enter="postComment">
<div class="field has-margin-top">
<div class="field has-margin-top">
<label class="label">Your name</label>
<div class="control">
<input
type="text"
placeholder="Your name"
class="input is-medium"
v-model="comment.author"
/>
</div>
</div>
<div class="field has-margin-top">
<label class="label">Your comment</label>
<div class="control">
<textarea
style="height: 100px"
name="comment"
class="input is-medium"
autocomplete="true"
v-model="comment.content"
placeholder="lorem ipsum"
></textarea>
</div>
</div>
<div class="control has-margin-top">
<button
style="background-color: #47b784"
:class="{ 'is-loading': submit }"
class="button has-shadow is-medium has-text-white"
:disabled="!isValid"
@click.prevent="postComment"
type="submit"
>
Submit
</button>
</div>
</div>
</form>
<br />
</div>
</template>
<script>
export default {
name: "NewComment",
data() {
return {
submit: false,
comment: {
content: "",
author: "",
},
};
},
methods: {
postComment() {
this.submit = true;
this.$store
.dispatch("ADD_COMMENT", this.comment)
.then((response) => {
this.submit = false;
if (response.data) console.log("success");
})
.catch((err) => {
console.log(err);
this.submit = false;
});
},
},
computed: {
isValid() {
return this.comment.content !== "" && this.comment.author !== "";
},
},
};
</script>
<style scoped>
.has-margin-top {
margin-top: 15px;
}
</style>
<template> <div id="commentForm" class="box has-shadow has-background-white"> <form @keyup.enter="postComment"> <div class="field has-margin-top"> <div class="field has-margin-top"> <label class="label">Your name</label> <div class="control"> <input type="text" placeholder="Your name" class="input is-medium" v-model="comment.author" /> </div> </div> <div class="field has-margin-top"> <label class="label">Your comment</label> <div class="control"> <textarea style="height: 100px" name="comment" class="input is-medium" autocomplete="true" v-model="comment.content" placeholder="lorem ipsum" ></textarea> </div> </div> <div class="control has-margin-top"> <button style="background-color: #47b784" :class="{ 'is-loading': submit }" class="button has-shadow is-medium has-text-white" :disabled="!isValid" @click.prevent="postComment" type="submit" > Submit </button> </div> </div> </form> <br /> </div> </template> <script> export default { name: "NewComment", data() { return { submit: false, comment: { content: "", author: "", }, }; }, methods: { postComment() { this.submit = true; this.$store .dispatch("ADD_COMMENT", this.comment) .then((response) => { this.submit = false; if (response.data) console.log("success"); }) .catch((err) => { console.log(err); this.submit = false; }); }, }, computed: { isValid() { return this.comment.content !== "" && this.comment.author !== ""; }, }, }; </script> <style scoped> .has-margin-top { margin-top: 15px; } </style>
<template>
  <div id="commentForm" class="box has-shadow has-background-white">
    <form @keyup.enter="postComment">
      <div class="field has-margin-top">
        <div class="field has-margin-top">
          <label class="label">Your name</label>
          <div class="control">
            <input
              type="text"
              placeholder="Your name"
              class="input is-medium"
              v-model="comment.author"
            />
          </div>
        </div>
        <div class="field has-margin-top">
          <label class="label">Your comment</label>
          <div class="control">
            <textarea
              style="height: 100px"
              name="comment"
              class="input is-medium"
              autocomplete="true"
              v-model="comment.content"
              placeholder="lorem ipsum"
            ></textarea>
          </div>
        </div>
        <div class="control has-margin-top">
          <button
            style="background-color: #47b784"
            :class="{ 'is-loading': submit }"
            class="button has-shadow is-medium has-text-white"
            :disabled="!isValid"
            @click.prevent="postComment"
            type="submit"
          >
            Submit
          </button>
        </div>
      </div>
    </form>
    <br />
  </div>
</template>
    
    <script>
export default {
  name: "NewComment",
  data() {
    return {
      submit: false,
      comment: {
        content: "",
        author: "",
      },
    };
  },
  methods: {
    postComment() {
      this.submit = true;
      this.$store
        .dispatch("ADD_COMMENT", this.comment)
        .then((response) => {
          this.submit = false;
          if (response.data) console.log("success");
        })
        .catch((err) => {
          console.log(err);
          this.submit = false;
        });
    },
  },
  computed: {
    isValid() {
      return this.comment.content !== "" && this.comment.author !== "";
    },
  },
};
</script>
    
    <style scoped>
.has-margin-top {
  margin-top: 15px;
}
</style>

现在,打开app.js文件并添加以下代码以注册您先前创建的Vue组件:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// resource/js/app.js
require("./bootstrap");
window.Vue = require("vue");
import store from "./store/index";
Vue.component("comment", require("./components/Comment"));
Vue.component("comments", require("./components/Comments"));
Vue.component("new-comment", require("./components/NewComment"));
const app = new Vue({
el: "#app",
store
});
// resource/js/app.js require("./bootstrap"); window.Vue = require("vue"); import store from "./store/index"; Vue.component("comment", require("./components/Comment")); Vue.component("comments", require("./components/Comments")); Vue.component("new-comment", require("./components/NewComment")); const app = new Vue({ el: "#app", store });
// resource/js/app.js

require("./bootstrap");
window.Vue = require("vue");
import store from "./store/index";

Vue.component("comment", require("./components/Comment"));
Vue.component("comments", require("./components/Comments"));
Vue.component("new-comment", require("./components/NewComment"));

const app = new Vue({
    el: "#app",
    store
});

小结

您刚刚了解了如何使用Laravel为您的站点构建实时评论系统。

我们已经讨论了创建和管理评论系统的好处,以帮助您在社区或博客中建立信任。我们还探索了如何利用不同的评论功能,从头开始开发设计良好的实时评论系统。

您可以在此Github repo中克隆此项目的源代码。

评论留言