在前面,我们已经是实现了用户评论功能以及评论的实时显示,现在,我们来实现一下,用户的回复功能。实现效果如下所示:

回复评论的流程

我们梳理一下评论回复流程:

  1. 用户点击评论,input框获取焦点,input框的placeholder变为回复 @nickname
  2. 用户输入评论内容,然后提交,向服务器发出请求
  3. 在页面渲染用户评论内容

细心的同学可能会发现,当回复成功后,顶部的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);
          }
        );
      }
    },
复制代码
  1. 因为我们一开始实现的评论功能是发布的根评论,没有要回复谁,所以to_user_idfather_comment_id我们置空。
  2. 现在,我们要进行的操作是回复用户评论。我们需要明确回复的是哪个用户,所回复的是哪条评论。由刚才的点击操作,我们已经可以明确这两个信息了。因此,我们要修改一下上面的逻辑:
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转载声明

原文出处:掘金前端

原文作者:阳光。

原文地址:https://juejin.im/post/5d610d5f6fb9a06af13d7406