在前面,我们已经是实现了用户评论功能以及评论的实时显示,现在,我们来实现一下,用户的回复功能。实现效果如下所示:
回复评论的流程
我们梳理一下评论回复流程:
- 用户点击评论,
input
框获取焦点,input
框的placeholder
变为回复 @nickname
- 用户输入评论内容,然后提交,向服务器发出请求
- 在页面渲染用户评论内容
细心的同学可能会发现,当回复成功后,顶部的xx条评论
并没有进行更新,因为在页面显示时,我并没有将回复当作是评论来处理,所以数量并没有增加
回复功能逻辑实现
评论对象事件绑定
li
class="comment_li"
v-for="(item,index) in commentList"
:key="item.id"
@click="replyUser(item,index,-1)"
>...li>
复制代码
从上面的代码,可以看出,我们为每个评论对象绑定了一个replyUser(item,index,-1)
的事件。那我们现在来看一下,我们在replyUser
函数中做了什么事情
replyUser(item, index, index2) {
item.index = index; // 为每个评论对象绑定一个index属性,这个index的作用是为了帮助我们能更好的在commmentList中定位到该评论对象
item.index2 = index2; // index2的作用是为了帮助我们区分回复的级别。-1代表回复的是根评论,其他值表示回复的是别人的回复。
this.replyUserComment = item;//replyUserComment的作用是为了保存当前的评论对象,在data中定义
this.commentPlaceholder = `回复 @${item.nickname}:`;//修改placeHolder
this.$refs.content.focus(); //input框获取焦点
},
复制代码
经过上面的处理,我们已经取得了我们回复的评论对象。
对评论内容的处理
div class="comment_input_box" v-show="commentPop">
input
:placeholder="commentPlaceholder"
class="comment_input"
v-model="comment_text"
ref="content"
@click="checkComment"
/>
div class="comment_input_right" @click="checkComment">
i class="iconfont icon-fasong comment_i" :class="canSend?'comment_i_active':''">i>
div>
div>
复制代码
在这里,为input
框绑定了一个checkComment
事件,在实现简单评论功能时,它的逻辑是下面这样的:
checkComment() {
if (this.comment_text == "") {
Toast("评论内容不能为空");
} else {
if (!this.isLogin) {
this.$router.push({
path: "/login"
});
return;
}
let to_user_id = ''; //因为评论是根评论,所以to_user_id和father_comment_id都为空
let father_comment_id = '';
const comment = this.comment_text;
const video_id = this.video_id;
const id = Date.now();
const newComment = {
avatar: this.userInfo.avatar,
comment,
id,
create_time: "刚刚",
nickname: this.userInfo.nickname
};
sendComment({ to_user_id, father_comment_id, comment, video_id }).then(
val => {
this.comment_text = "";
this.hasSend = true;
this.commentList.unshift(newComment);
}
);
}
},
复制代码
- 因为我们一开始实现的评论功能是发布的根评论,没有要回复谁,所以
to_user_id
和father_comment_id
我们置空。 - 现在,我们要进行的操作是回复用户评论。我们需要明确回复的是哪个用户,所回复的是哪条评论。由刚才的点击操作,我们已经可以明确这两个信息了。因此,我们要修改一下上面的逻辑:
checkComment() {
if (this.comment_text == "") {
Toast("评论内容不能为空");
} else {
if (!this.isLogin) {
this.$router.push({
path: "/login"
});
return;
}
let to_user_id = ''; //因为评论是根评论,所以to_user_id和father_comment_id都为空
let father_comment_id = '';
const comment = this.comment_text;
const video_id = this.video_id;
const id = Date.now();
const newComment = {
avatar: this.userInfo.avatar,
comment,
id,
create_time: "刚刚",
nickname: this.userInfo.nickname
};
----------------------------------------------------
----------------------------------------------------
if(this.replyUserComment){//如果评论对象不为null
to_user_id = this.replyUserComment.from_user_id; // 让回复对象指向评论者
father_comment_id = this.replyUserComment.id //让新评论的father_comment_id指向评论的id
}
-------------------------------------------------------
-------------------------------------------------------
sendComment({ to_user_id, father_comment_id, comment, video_id }).then(
val => {
this.comment_text = "";
this.hasSend = true;
this.commentList.unshift(newComment);
}
);
}
},
复制代码
实时渲染回复内容
现在已经实现了回复保存到数据库的功能。但是同样没有实时渲染到页面上。
因此,我们要向之前一样,生成一个新的评论对象,并将这个对象插入到数组中去。但回复对象的插入与根评论的插入逻辑上存在差异,我们先来看下面bilibili
的评论图:
回复也可以当作评论进行回复。在项目中,我们将回复划分为两类,一类是一级回复,一类是二级回复。
对于评论和回复来说,一般采用的都是如下的嵌套的数据格式:
{
avatar:'xxxxxxxxxxxx',
nickname:'xxxxxxxxxxxxx',
comment:'xxxxxxxxxxxxxx',
crete_time:'xxxxxxxxxxxxx',
child_comments:[
{
avatar:'xxxxxxxxxxxx',
nickname:'xxxxxxxxxxxxx',
comment:'xxxxxxxxxxxxxx',
crete_time:'xxxxxxxxxxxxx',
}
......
]
}
复制代码
- 对于一级回复,我们是直接插入到头部。
- 对于二级回复,我们是直接插入到尾部。
在前面replyUserComment
回调函数中,我们进行了如下处理:
replyUser(item, index, index2) {
item.index = index; // 为每个评论对象绑定一个index属性,这个index的作用是为了帮助我们能更好的在commmentList中定位到该评论对象
item.index2 = index2; // index2的作用是为了帮助我们区分回复的级别。-1代表回复的是根评论,其他值表示回复的是别人的回复。
this.replyUserComment = item;//replyUserComment的作用是为了保存当前的评论对象,在data中定义
this.commentPlaceholder = `回复 @${item.nickname}:`;//修改placeHolder
this.$refs.content.focus(); //input框获取焦点
},
复制代码
有了上面的图,我们就能很好理解了。于是我们对于一级回复,很容写出下面的代码:
let index = this.replyUserComment.index;
let index2 = this.replyUserComment.index2;
if (this.replyUserComment.index2 == -1) {
//将一级回复插入到子评论的头部
this.commentList[index].child_comments.unshift(newComment);
} else {
//将二级回复插入到子评论的尾部
this.commentList[index].child_comments.push(newComment)
}
复制代码
完整代码
replyUser(item, index, index2) {
item.index = index;
item.index2 = index2;
this.replyUserComment = item;
this.commentPlaceholder = `回复 @${item.nickname}:`;
this.$refs.content.focus();
},
checkComment() {
if (this.comment_text == "") {
Toast("评论内容不能为空");
} else {
if (!this.isLogin) {
this.$router.push({
path: "/login"
});
return;
}
let father_comment_id = ""; // 默认父评论为null
let to_user_id = "";
let video_id = this.video_id;
let comment = this.comment_text;
const id = Date.now();
const newComment = {
avatar: this.userInfo.avatar,
comment,
id,
create_time: "刚刚",
nickname: this.userInfo.nickname
};
if (this.replyUserComment) {
// 说明不是根评论,而是子评论
father_comment_id = this.replyUserComment.id; // 让father_comment_id 指向这个评论
to_user_id = this.replyUserComment.from_user_id; // 让to_user_id指向父级评论的from_user_id
}
sendComment({ to_user_id, father_comment_id, comment, video_id }).then(
val => {
this.comment_text = "";
this.hasSend = true;
if (!this.replyUserComment) {
this.commentList.unshift(newComment);
} else {
let index = this.replyUserComment.index;
let index2 = this.replyUserComment.index2;
if (this.replyUserComment.index2 == -1) {
//回复一级人
this.commentList[index].child_comments.unshift(newComment);
} else {
//回复二级人
this.commentList[index].child_comments.push(newComment)
}
}
}
);
}
},
复制代码
到目前为止,用户的回复功能也是实现了,接下来,就相对轻松了,还剩下,评论功能的点赞。
未完待续….
GankRobot转载声明
原文出处:掘金前端
原文作者:阳光。