寫在前面
這篇文章嚴格來講是將已有的仿簡書二級評論系統和 Laravel、Vue 進行結合並改進,例如新增郵件通知。前人栽樹後人乘涼,評論系統的資料結構和 Vue 模板詳情見下面這篇文章,本文不在贅述。
專案原始碼
前端評論模板:https://gitee.com/bluish_space/lvblog/blob...
前端 Vuex 模組
https://gitee.com/bluish_space/lvblog/blob...
後端資料處理https://gitee.com/bluish_space/lvblog/blob...
後端資料轉換器
https://gitee.com/bluish_space/lvblog/blob...
前端模板
<script>
//登入的事件匯流排
import {EventBus} from '../../event-bus.js';
export default {
name: "comment",
data() {
return {
loader:'',
tar:'',
inputComment: '',
inputReply:'',
showItemId: '',
//釋出評論
//釋出評論佔用訊號量,PV操作,單執行緒
comment_buss: 1,
isReply: 0,
idReply: 0,
idComment:0,
//刪除評論
//刪除評論佔用訊號量,PV操作
delete_buss: 1,
interval:'',
jumped:0,
}
},
created(){
//從 Vuex 獲取評論的資料
this.$store.dispatch('loadComments',{
art_id: this.$route.params.art_id,
});
//監聽評論資料的載入情況
this.$watch(this.$store.getters.getCommentsLoadStatus, function () {
if (this.$store.getters.getCommentsLoadStatus() == 3) {
console.log('comment.vue:評論模組未能成功載入!')
}
});
},
mounted(){
//郵件通知時用於跳轉到指定評論或回覆的錨點的方法
this.anchor();
},
watch: {
// 如果路由有變化,會再次執行該 stopInterval 方法
//如果已經跳轉到相應錨點,清楚定時執行操作
"jumped": "stopInterval"
},
computed:{
//評論的計算屬性
comments(){
return this.$store.getters.getComments.data;
},
//使用者的計算屬性,判斷是否有使用者登入
user(){
return this.$store.getters.getUser;
},
//當前評論所屬文章的計算屬性
article(){
return this.$store.getters.getArticle.data;
}
},
methods: {
//清楚定時執行器
stopInterval(){
window.clearInterval(this.interval);
},
anchor(){
if(this.$route.query.reply !== undefined){
//判斷是評論還是回覆
let type = this.$route.query.reply;
//定位目標評論或回覆的位置
let location = this.$route.query.location;
//拼接錨點
let anchor = '#'+type+location;
let jump = '';
this.$nextTick(()=> {
this.interval = setInterval(()=> {
jump = document.querySelectorAll(anchor);
if(jump.length!=0) {
// 滾動到目標位置
document.querySelector(anchor).scrollIntoView(true);
this.jumped ++;
}
})
},500);
}
},
/**
* 點贊
*/
likeClick(item) {
if(!this.$store.getters.getUser){
this.login();
}
if (!item.isLike) {
this.$store.dispatch('likeComment',{
comment_id:item.id
});
this.$watch(this.$store.getters.getCommentLikeStatus, function () {
if (this.$store.getters.getCommentLikeStatus() == 2) {
this.$set(item, "isLike", true);
item.likeNum++;
}
if (this.$store.getters.getCommentLikeStatus() == 3) {
this.$message.warning('點贊失敗了,請稍後重試!');
}
});
} else {
this.$message.info('你已經贊過了哦~');
}
},
/**
* 點選取消按鈕
*/
cancel() {
this.showItemId = '';
this.inputComment = this.inputReply = '';
++this.comment_buss;
},
/**
* 提交評論
*/
commitComment() {
if(!this.$store.getters.getUser){
this.login();
return false;
}
--this.comment_buss;
if(this.comment_buss<0){
this.message.warning('有其他程式在執行評論操作,請稍候重試!')
}else{
if(this.isReply == 1){
if(this.inputReply == ''){
this.$message.warning('回覆內容不能為空');
return false;
}
this.$store.dispatch('postReply',{
comment_id:this.idComment,
contents:this.inputReply,
toUser : this.idReply,
art_id : this.$route.params.art_id,
});
this.loader = this.$loading({
lock: true,
text: '釋出回覆中...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
this.$watch(this.$store.getters.getReplyPostStatus, function () {
if (this.$store.getters.getReplyPostStatus() == 2) {
this.loader.close();
this.isReply = 0;
this.idReply = 0;
this.cancel();
this.$message.success('回覆成功!');
}
if (this.$store.getters.getReplyPostStatus() == 3) {
this.loader.close();
this.$message.warning('回覆失敗了,請稍後重試!');
}
});
}else{
if(this.inputComment == ''){
this.$message.warning('評論內容不能為空');
return false;
}
this.$store.dispatch('postComment',{
art_id:this.$route.params.art_id,
contents:this.inputComment
});
this.loader = this.$loading({
lock: true,
text: '釋出評論中...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
this.$watch(this.$store.getters.getCommentPostStatus, function () {
if (this.$store.getters.getCommentPostStatus() == 2) {
this.loader.close();
this.$message.success('評論成功!');
}
if (this.$store.getters.getCommentPostStatus() == 3) {
this.loader.close();
this.$message.warning('評論失敗了,請稍後重試!');
}
});
}
++this.comment_buss;
}
},
deleteComment(item, reply){
if(!this.$store.getters.getUser){
this.login();
return false;
}
--this.delete_buss;
if(this.delete_buss < 0){
this.$message.error('有其他程式在執行刪除操作,請稍後重試!')
}else{
if (reply) {
this.$confirm('此操作將永久刪除此回覆, 是否繼續?', '提示', {
confirmButtonText: '確定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$store.dispatch('deleteReply', {
reply_id: reply.id,
art_id : this.$route.params.art_id,
});
this.loader = this.$loading({
lock: true,
text: '刪除中...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
this.$watch(this.$store.getters.getReplyDeleteStatus, function () {
if (this.$store.getters.getReplyDeleteStatus() == 2) {
this.loader.close();
this.$message.success('已刪除');
}
if (this.$store.getters.getReplyDeleteStatus() == 3) {
this.loader.close();
this.$message.error('刪除回覆失敗了!')
}
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消刪除'
});
});
} else {
this.$confirm('此操作將永久刪除此評論, 是否繼續?', '提示', {
confirmButtonText: '確定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$store.dispatch('deleteComment',{
comment_id:item.id,
art_id : this.$route.params.art_id,
});
this.loader = this.$loading({
lock: true,
text: '刪除中...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
this.$watch(this.$store.getters.getCommentDeleteStatus,function () {
if(this.$store.getters.getCommentDeleteStatus() == 2){
this.loader.close();
this.$message.success('已刪除');
}
if(this.$store.getters.getCommentDeleteStatus() == 3){
this.loader.close();
this.$message.error('刪除評論失敗了!')
}
});
}).catch(() => {
this.$message({
type: 'info',
message: '已取消刪除'
});
});
}
++this.delete_buss;
}
},
/**
* 點選評論按鈕顯示輸入框
* item: 當前大評論
* reply: 當前回覆的評論
*/
showCommentInput(item, reply) {
if(!this.$store.getters.getUser){
this.login();
return false;
}else{
this.idComment = item.id;
this.isReply = 1;
if (reply) {
this.idReply = reply.fromId;
this.inputReply = "@" + reply.fromName + " "
} else {
this.idReply = item.fromId;
this.inputReply = ''
}
this.showItemId = item.id
}
},
login(){
EventBus.$emit('prompt-login');
},
validateReply(reply){
if (this.$store.getters.getUser){
return reply.fromId != this.$store.getters.getUser.id;
}else{
return true;
}
}
}
}
</script>
例項:
後臺處理
資料結構
評論表
Schema::create('comments', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('article_id')->unsigned()->index()->comment('文章id');
$table->bigInteger('fromId')->unsigned()->index()->comment('評論者id');
$table->integer('type')->comment('評論型別');
$table->string('fromName')->comment('評論者暱稱');
$table->string('fromAvatar')->comment('評論者頭像');
$table->bigInteger('likeNum')->comment('點贊次數');
$table->string('contents')->comment('評論內容');
$table->timestamps();
});
回覆表
Schema::create('replies', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('comment_id')->index()->unsigned()->comment('評論id');
$table->bigInteger('fromId')->index()->unsigned()->comment('評論者id');
$table->string('fromName')->comment('評論者暱稱');
$table->string('fromAvatar')->comment('評論者頭像');
$table->bigInteger('toId')->comment('被評論者id');
$table->string('toName')->comment('評論者暱稱');
$table->string('toAvatar')->comment('評論者暱稱');
$table->string('contents')->comment('評論內容');
$table->timestamps();
});
資料處理
以回覆資料的處理為例,其它見原始碼
public function replyStore(ReplyRequest $request,$comment)
{
if ($comment = Comment::find($comment)){
//通過 Authorization Token 確定當前登入的使用者,等價於 Auth::guard('api')->user(),獲取登入使用者資訊
$fromUser = $this->user;
$toUser = User::find($request->toUser);
$data = new Reply();
$data -> comment_id = $comment->id;
$data -> fromId = $fromUser->id;
$data -> fromName = $fromUser->name;
$data -> fromAvatar = $fromUser->avatar;
$data -> toId = $toUser->id;
$data -> toName = $toUser->name;
$data -> toAvatar = $toUser->avatar;
// 使用 Str:after 方法去掉 '@' 符號
$data -> contents = Str::after(Str::after($request->contents,'@'.$toUser->name.' '),'@'.$toUser->name);
if($data->save()) {
return response()->json(['message' => '回覆成功'], 201);
}else{
return response()->json(['message' => '回覆失敗'], 500);
}
}else{
return response()->json(['message' => '目標評論不存在'], 404);
}
}
郵件通知
觀察者
評論的觀察者
public function created(Comment $comment)
{
//
$article = $comment->article;
$user =$article->user;
// 如果要通知的人是當前使用者,就不必通知了!
if ($user ->id != auth('api')->user()->id) {
$user->increment('notification_count');
$user->notify(new ArticleReplied($comment));
}
}
回覆的觀察者
public function created(Reply $reply)
{
//
// 如果要通知的人是當前使用者,就不必通知了!
if ($reply ->toId != auth('api')->user()->id) {
$reply->toUser->increment('notification_count');
$reply->toUser->notify(new CommentReplied($reply));
}
}
郵件通知
開啟郵件通知頻道,完成郵件通知方法
評論的郵件通知方法:
public function toMail($notifiable)
{
// Log::debug($this);
$url = env('APP_URL').'/art/' . $this->comment->article_id . '?reply=comment&location=' . $this->comment->id;
return (new MailMessage)
->line('你的文章有了新評論!')
->action('檢視評論', $url);
}
回覆的郵件通知方法
public function toMail($notifiable)
{
$url = env('APP_URL').'/art/' . $this->reply->comment->article_id . '?reply=reply&location=' . $this->reply->id;
return (new MailMessage)
->line('你在文章下的評論有了新回覆!')
->action('檢視回復', $url);
}
這樣我們在收到評論或回覆時就能夠及時收到郵件通知啦!
更多資訊
關注下我的微信公眾號就是對我創作的最大支援~