【项目实战】基于Vue3+Vant3造一个网页版的类掘金app项目 - 回复评论

175 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

前言

大家好,截止目前关于沸点这一模块我们已经实现了:“沸点评论展示”、“发布沸点评论”、“沸点评论排序”以及对一些通用模块(评论模块)的封装。还剩下几个功能点尚未实现:

  • 回复评论
  • 点赞沸点内容和评论内容
  • 评论内容超出5条后显示查看更多按钮,点击后跳转到新页面
  • 发布或回复评论时表情及图片处理 本次的分享我们将实现回复评论功能

评论组件封装

在前面的分享中,当我们点击沸点内容的评论图标时会展示出一个评论文本框,通过文本框我们可以对沸点内容进行评论。而在评论列表中的每个评论内容模块也都有一个评论的图标,点击后可以对评论内容进行回复,接下来我们就实现一下该功能。

image.png 如上图所示是掘金官方回复评论功能模块,我们会发现这个回复评论模块跟发布沸点评论内容模块是一样的,这就又涉及到了代码重用问题,因此在实现回复功能前,我们先将之前已经实现了的发布评论模块进行抽取封装:

  • 新建Reply.vue组件
  • 将Fire.vue中发布评论模块相关代码(HTML,js,css)提取出来放到Reply.vue组件中
  • 原有的响应式属性及方法保持不变,同时新增3个props属性:
    • itemId:当前内容的唯一id(沸点内容ID【msg_id】或评论内容ID【comment_id】)
    • isShow:用于区分是沸点模块的评论还是评论模块的评论
    • msg_id:沸点内容的id
  • 给div添加id属性,值为reply+itemId,用于后期控制文本框样式用
  • 修改publisComment方法,根据msg_id值是否为空进行不同的接口调用:
    • 如果msg_id为空说明是在沸点内容模块,需要调用对应的发表评论的接口publish
    • 如果msg_id不为空则是在评论内容模块,需要调用对应的回复评论的接口reply
  • 最后在发布成功后还需要返回给父组件一个自定义事件publised,告诉父组件已经完成评论,可以重新加载评论数据 Reply.vue核心代码如下:
<template>
  <div class="comment-reply" :id="`reply${itemId}`" v-show="isShow">
    <div class="reply-input">
      <van-field
        @focus="inputFocus"
        @blur="inputBlur"
        v-model="reply_content"
        placeholder="输入评论(Enter换行,Ctrl + Enter发送)"
      ></van-field>
    </div>
    <div class="reply-opt" v-show="showBtn">
      <span> <van-icon class="reply-icon" name="smile-o"></van-icon>表情 </span>
      <span> <van-icon class="reply-icon" name="photo-o"></van-icon>图片 </span>
      <van-button
        style="float: right"
        :disabled="reply_content === ''"
        type="primary"
        size="small"
        @click="publishComment"
        >发表评论</van-button
      >
      <span style="float: right; margin-top: 9px; color: #8c8c8c"
        >Ctrl + Enter</span
      >
    </div>
  </div>
</template>
import { reactive, toRefs } from "vue";
import api from "../api/juejinapi";
export default {
  props: {
    itemId: {
      type: String,
      required: true,
    },
    isShow: {
      type: Boolean,
      default: false,
    },
    msg_id: {
      type: String,
      default: "",
    },
  },
  setup(props, ctx) {
    const state = reactive({ showBtn: false, reply_content: "" });
    const { itemId, msg_id } = props;
    const inputFocus = () => {
      state.showBtn = true;
      const el_input = document.querySelector(`#reply${itemId}`),
        el_con = el_input.querySelector(".van-field__control ");

      el_con.classList.add("active");
    };
    const inputBlur = () => {
      const el_input = document.querySelector(`#reply${itemId}`),
        el_con = el_input.querySelector(".van-field__control ");
      !state.reply_content ? (state.showBtn = false) : null;
      !state.reply_content ? el_con.classList.remove("active") : null;
    };

    const publishComment = async () => {
      let res = null;
      if (msg_id) {
        res = await api.reply(state.reply_content, [], itemId, msg_id);
        ctx.emit("published", { res, itemId: msg_id });
      } else {
        res = await api.publish(state.reply_content, [], itemId);
        ctx.emit("published", { res, itemId });
      }

      state.reply_content = "";
    };

    return {
      ...toRefs(state),
      inputFocus,
      inputBlur,
      publishComment,
    };
  },
};
</script>

发表及回复评论

上面我们已经将发表评论内容封装成了通用组件,因此不管是发表评论还是回复评论直接调用该组件即可,唯一有点复杂的就是在评论列表中最多可能有5条评论内容,我们需要控制点击哪个评论就显示对应的评论文本框,而不是将5条评论的文本框全部显示出来,大概思路如下:

  • 添加响应式属性:current_comment用于标识当前点击的是哪条评论
  • 给每个comment标签添加click事件并绑定方法replyHandle,用于给current_comment赋值为当前点击的评论内容的id
  • 控制图标后面的文本内容:
    • 默认如果回复数为0则显示回复两个字,点击后回复文本框出现,同时文字变为:取消回复
    • 点击取消回复后,隐藏回复文本框
  • 给评论组件绑定自定义事件published,发布成功后重新加载评论数据 Fire.vue核心代码及效果图如下:
 <!--沸点内容区块-->
<div class="comment-list-reply">
  <!--原有代码省略...-->
  <reply
    :itemId="msg.msg_id"
    @published="publishComment"
    :isShow="true"
  />
</div>

 <!--评论内容区块-->
 <div
    class="comment"
    @click="
      replyHandle(
        rep.comment_id,
        current_comment === rep.comment_id
      )
    "
  >
    <van-icon name="chat-o" />{{
      rep.comment_info.reply_count === 0
        ? current_comment === rep.comment_id
          ? "取消回复"
          : "回复"
        : rep.comment_info.reply_count
    }}
  </div>
   <reply
      :itemId="rep.comment_id"
      @published="publishComment"
      :isShow="current_comment === rep.comment_id"
      :msg_id="msg.msg_id"
    />
const publishComment = (obj) => {
  showComment(obj.itemId, false, 0);
};

const replyHandle = (comment_id, isShow) => {
    state.current_comment = !isShow ? comment_id : "";
};

test.gif

总结

如上效果图,通过对评论区块的抽取和封装,我们实现了对沸点内容发布评论的改造,以及对评论内容回复功能的实现。本次分享内容的主要核心点在于评论组件的封装,实现封装后不管是发表评论还是回复评论也都自然而然的实现了。好了本次分享就先到这里了,喜欢的朋友欢迎点赞关注加评论哦!