VUE+Java实现评论回复功能

背景

最近需要做一个多级评论的功能,技术路线:VUE(Element)+Java(SpringBoot)

效果

后台

SQL

Java

Controller

/**
  * 根据关联id获取评论信息
  * @param relationId 关联id
  * @param type 类型
  * @return: com.harvey.result.ResultSupport<java.lang.Object>
  * @date: 2020/12/10 14:37
  */
 @GetMapping("findList")
 public ResultSupport<Object> findList(@RequestParam("relationId") String relationId, @RequestParam("type") String type){
     log.debug("接口[findList]的参数:relationId={}, type={}", relationId, type);
     ResultSupport<Object> result = ResultSupport.createMisResp();
     try {
         List<Comment> commentList = commentService.findList(relationId, type);
         ResultSupportUtils.fillResultSupport(result, commentList);
     } catch (Exception e) {
         log.error("[findList]接口执行异常", e);
         ResultSupportUtils.fillError(result,"系统出现异常!");
     }
     log.debug("接口[findList]的结果:result={}", result);
     return result;
 }

 /**
  * 保存评论
  * @param comment 参数
  * @return: com.tortoise.common.result.ResultSupport<java.lang.Object>
  * @date: 2020/12/10 14:37
  */
 @PostMapping("save")
 public ResultSupport<Object> save(@RequestBody Comment comment, HttpServletRequest request){
     log.debug("接口[save]的参数:comment={}", comment);
     ResultSupport<Object> result = ResultSupport.createMisResp();
     String token = request.getHeader("authorization");
     if (StrUtil.isEmpty(token)) {
         result.setSuccess(false);
         result.setMessage("token无效!");
         return result;
     }
     if (BeanUtil.isEmpty(comment)){
         result.setSuccess(false);
         result.setMessage("参数无效!");
         return result;
     }
     try {
         commentService.save(comment, token);
     } catch (Exception e) {
         log.error("[save]接口执行异常", e);
         ResultSupportUtils.fillError(result,"系统出现异常!");
     }
     log.debug("接口[save]的结果:result={}", result);
     return result;
 }

Service

/**
  * 根据关联id获取绩效信息
  */
 public List<Comment> findList(String relationId, String type) {
     return commentMapper.findList(relationId, type);
 }

 /**
  * 保存评论
  * @param comment 参数
  * @param token 用户token
  * @return:
  * @date: 2020/12/10 14:37
  */
 @Transactional(rollbackFor = Exception.class)
 public void save(Comment comment, String token) {
     SysUser user = UserUtils.getUser(token);
     comment.preInsert(user.getId());
     comment.setDelFlag("0");
     commentMapper.save(comment);
 }

Mapper

/**
 * 根据关联id获取绩效信息
 */
List<Comment> findList(@Param("relationId") String relationId, @Param("type") String type);

/**
 * 根据id获取子评论内容
 */
List<Comment> selectByParentId(@Param("parentId") String parentId);

/**
 * 保存评论
 */
void save(Comment comment);

XML

<sql id="commentColumns">
        a.id AS "id",
        a.user_id AS "userId",
        u.name AS "userName",
        a.relation_id AS "relationId",
        a.type AS "type",
        a.reply_user_id AS "replyUserId",
        r.name AS "replyUserName",
        a.parent_id AS "parentId",
        a.content AS "content",
        u.photo AS "photo",
        a.del_flag AS "delFlag",
        a.create_by AS "createBy",
        a.create_date AS "createDate"
    </sql>

    <sql id="commentJoins">
        LEFT JOIN sys_user u ON a.user_id = u.id AND u.del_flag = '0'
        LEFT JOIN sys_user r ON a.reply_user_id = r.id AND r.del_flag = '0'
    </sql>
    <!-- 保存评论 -->
    <insert id="save">
        INSERT INTO comment(
            id,
            user_id,
            relation_id,
            type,
            reply_user_id,
            parent_id,
            content,
            del_flag,
            create_by,
            create_date
        ) VALUES (
            #{id},
            #{userId},
            #{relationId},
            #{type},
            #{replyUserId},
            #{parentId},
            #{content},
            #{delFlag},
            #{createBy},
            #{createDate}
        )
    </insert>

    <resultMap id="commentResultMap" type="com.harvey.entity.Comment">
        <id column="id" property="id" />
        <result column="userId" property="userId" />
        <result column="userName" property="userName" />
        <result column="relationId" property="relationId" />
        <result column="type" property="type" />
        <result column="replyUserId" property="replyUserId" />
        <result column="replyUserName" property="replyUserName" />
        <result column="parentId" property="parentId" />
        <result column="content" property="content" />
        <collection property="children" column="{parentId=id}" select="selectByParentId" ofType="com.harvey.Comment"/>
    </resultMap>

    <!-- 根据关联id获取评论信息 -->
    <select id="findList" resultMap="commentResultMap">
        SELECT
        <include refid="commentColumns"/>
        FROM
            comment a
        <include refid="commentJoins"/>
        WHERE
            a.relation_id = #{relationId}
            AND a.type = #{type}
            AND a.parent_id = '0'
        ORDER BY
            a.create_date DESC
    </select>

    <!-- 根据id获取子评论内容 -->
    <select id="selectByParentId" resultType="com.harvey.entity.Comment">
        SELECT
        <include refid="commentColumns"/>
        FROM
        comment a
        <include refid="commentJoins"/>
        WHERE
        a.parent_id = #{parentId}
        ORDER BY
        a.create_date DESC
    </select>

前端

把评论抽成组件,方便其他模块引用

<!--评论模块-->
<template>
  <el-drawer
    title="评论"
    :visible.sync="drawer"
    direction="rtl"
    :before-close="handleClose"
    :modal="false"
    :withHeader="true"
    @open="getCommentList"
    @close="close"
    size="320px"
  >
    <div class="container">
      <div class="write-reply" @click="showCommentInputMajor()">
        <div style="margin-top: 10px;">
          <el-input
            class="gray-bg-input"
            v-model="majorComment"
            type="textarea"
            :rows="3"
            autofocus
            placeholder="写下你的评论"
          >
          </el-input>
          <div style="text-align: right;margin-top: 10px;">
            <el-button @click="reset" size="small" round>重置</el-button>
            <el-button
              type="primary"
              round
              @click="commitMajorComment"
              size="small"
              >确定</el-button
            >
          </div>
        </div>
      </div>
      <div class="comment" v-for="item in commentList" :key="item.id">
        <div class="info">
          <img
            class="avatar"
            :src="fileUrl + item.photo"
            width="36"
            height="36"
          />
          <div class="right">
            <div class="name">{{ item.userName }}</div>
            <div class="date">{{ formatDate(item.createDate) }}</div>
          </div>
        </div>
        <div class="content">{{ item.content }}</div>
        <div class="control">
          <!-- <span
          class="like"
          :class="{ active: item.isLike }"
          @click="likeClick(item)"
        >
          <i class="iconfont icon-like"></i>
          <span class="like-num">{{
            item.likeNum > 0 ? item.likeNum + "人赞" : "赞"
          }}</span>
        </span> -->
          <span
            class="comment-reply"
            @click="showCommentInput(item, item, 'major')"
          >
            <i class="iconfont icon-iconcomment"></i>
            <span>回复</span>
          </span>
        </div>
        <div class="reply">
          <div class="item" v-for="reply in item.children" :key="reply.id">
            <div class="reply-content">
              <span class="from-name">{{ reply.userName }}</span
              ><span>: </span>
              <span class="to-name">@{{ reply.replyUserName }}</span>
              <span>{{ reply.content }}</span>
            </div>
            <div class="reply-bottom">
              <span>{{ formatDate(reply.createDate) }}</span>
              <span
                class="reply-text"
                @click="showCommentInput(item, reply, 'child')"
              >
                <i class="iconfont icon-iconcomment"></i>
                <span>回复</span>
              </span>
            </div>
          </div>
          <transition name="fade">
            <div class="input-wrapper" v-if="showItemId === item.id">
              <el-tag
                type="info"
                effect="dark"
                v-if="inputLabel != undefined && inputLabel"
                >{{ inputLabel }}</el-tag
              >
              <el-input
                class="gray-bg-input"
                v-model="inputComment"
                type="textarea"
                :rows="3"
                autofocus
                placeholder="写下你的评论"
              >
              </el-input>
              <div class="btn-control">
                <el-button @click="cancel" size="small" round>取消</el-button>
                <el-button
                  type="primary"
                  round
                  @click="commitComment"
                  size="small"
                  >确定</el-button
                >
              </div>
            </div>
          </transition>
        </div>
      </div>
    </div>
  </el-drawer>
</template>

<script>
import * as commentApi from "@/api/comment-api";
import { DateUtil } from "@/utils/DateUtils";
import { UserUtil } from "@/utils/UserUtils";
import "@/assets/css/iconfont/iconfont.css";

export default {
  props: {
    drawer: {
      type: Boolean,
      required: true,
      default: false
    },
    relationId: {
      type: String,
      required: true,
      default: ""
    },
    commentType: {
      type: String,
      required: true,
      default: ""
    }
  },
  data() {
    return {
      fileUrl: process.env.VUE_APP_FDFST_FILE_URL,
      commentList: [],
      inputComment: "",
      showItemId: "",
      replyUserId: "",
      parentId: "",
      userInfo: UserUtil.getUserByStorage(),
      inputLabel: "",
      majorComment: ""
    };
  },
  methods: {
    formatDate(date) {
      return DateUtil.formatDate(date, "yyyy-MM-dd hh:mm");
    },
    handleClose(done) {
      done();
    },
    /**
     * 点赞
     */
    /* likeClick(item) {
      if (item.isLike === null) {
        item.likeNum++;
      } else {
        if (item.isLike) {
          item.likeNum--;
        } else {
          item.likeNum++;
        }
        item.isLike = !item.isLike;
      }
    }, */
    // 获取评论内容
    getCommentList() {
      commentApi.findList(this.relationId, this.commentType).then(res => {
        this.commentList = res.data;
      });
    },
    /**
     * 点击取消按钮
     */
    cancel() {
      this.showItemId = "";
    },

    /**
     * 提交评论
     */
    commitComment() {
      // 封装参数
      let param = {
        userId: this.userInfo.userId,
        relationId: this.relationId,
        type: this.commentType,
        replyUserId: this.replyUserId,
        parentId: this.parentId,
        content: this.inputComment
      };
      commentApi.saveComment(param).then(res => {
        if (res.success) {
          this.$message({
            message: "评论成功",
            type: "success"
          });
          this.getCommentList();
          this.inputComment = "";
        } else {
          this.$message.error("评论失败");
        }
      });
    },

    /**
     * 提交评论
     */
    commitMajorComment() {
      // 封装参数
      let param = {
        userId: this.userInfo.userId,
        relationId: this.relationId,
        type: this.commentType,
        replyUserId: this.replyUserId,
        parentId: this.parentId,
        content: this.majorComment
      };
      commentApi.saveComment(param).then(res => {
        if (res.success) {
          this.$message({
            message: "评论成功",
            type: "success"
          });
          this.getCommentList();
          this.majorComment = "";
        } else {
          this.$message.error("评论失败");
        }
      });
    },

    /**
     * 点击评论按钮显示输入框
     * item: 当前大评论
     * reply: 当前回复的评论
     */
    showCommentInput(item, reply, type) {
      if (reply) {
        this.inputLabel = "@" + reply.userName + " ";
        if (type === "major") {
          this.parentId = reply.id;
        }
        if (type === "child") {
          this.parentId = reply.parentId;
        }
        this.replyUserId = reply.userId;
        debugger;
      } else {
        this.inputLabel = "";
        this.parentId = "0";
        this.replyUserId = "";
      }
      this.inputComment = "";
      this.showItemId = item.id;
    },
    showCommentInputMajor() {
      this.inputLabel = "";
      this.parentId = "0";
      this.replyUserId = "";
    },
    reset() {
      this.inputComment = "";
      this.majorComment = "";
    },
    close() {
      this.$emit("commentClose", "0");
    }
  }
};
</script>

<style scoped lang="less">
/deep/.el-drawer__body {
  overflow: auto;
}
/deep/.el-drawer__header span:focus {
  outline: 0 !important;
}
.container {
  padding: 0 10px;
  box-sizing: border-box;
  .comment {
    display: flex;
    flex-direction: column;
    padding: 10px;
    border-bottom: 1px solid #f2f6fc;
    .info {
      display: flex;
      align-items: center;
      .avatar {
        border-radius: 50%;
      }
      .right {
        display: flex;
        flex-direction: column;
        margin-left: 10px;
        .name {
          font-size: 16px;
          color: #303133;
          margin-bottom: 5px;
          font-weight: 500;
        }
        .date {
          font-size: 12px;
          color: #909399;
        }
      }
    }
    .content {
      font-size: 16px;
      color: #303133;
      line-height: 20px;
      padding: 10px 0;
    }
    .control {
      display: flex;
      align-items: center;
      font-size: 14px;
      color: #909399;
      .like {
        display: flex;
        align-items: center;
        margin-right: 20px;
        cursor: pointer;
        &.active,
        &:hover {
          color: #409eff;
        }
        .iconfont {
          font-size: 14px;
          margin-right: 5px;
        }
      }
      .comment-reply {
        display: flex;
        align-items: center;
        cursor: pointer;
        &:hover {
          color: #333;
        }
        .iconfont {
          font-size: 16px;
          margin-right: 5px;
          margin-top: 4px;
        }
      }
    }
    .reply {
      margin: 10px 0;
      border-left: 2px solid #dcdfe6;
      .item {
        margin: 0 10px;
        padding: 10px 0;
        border-bottom: 1px dashed #ebeef5;
        .reply-content {
          display: flex;
          align-items: center;
          font-size: 14px;
          color: #303133;
          .from-name {
            color: #409eff;
          }
          .to-name {
            color: #409eff;
            margin-left: 5px;
            margin-right: 5px;
          }
        }
        .reply-bottom {
          display: flex;
          align-items: center;
          margin-top: 6px;
          font-size: 12px;
          color: #909399;
          .reply-text {
            display: flex;
            align-items: center;
            margin-left: 10px;
            cursor: pointer;
            &:hover {
              color: #333;
            }
            .icon-iconcomment {
              margin-right: 5px;
              margin-top: 4px;
              font-size: 13px;
            }
          }
        }
      }
      .write-reply {
        display: flex;
        align-items: center;
        font-size: 14px;
        color: #909399;
        padding: 10px;
        cursor: pointer;
        &:hover {
          color: #303133;
        }
        .el-icon-edit {
          margin-right: 5px;
        }
      }
      .fade-enter-active,
      fade-leave-active {
        transition: opacity 0.5s;
      }
      .fade-enter,
      .fade-leave-to {
        opacity: 0;
      }
      .input-wrapper {
        padding: 10px;
        .gray-bg-input,
        .el-input__inner {
          /*background-color: #67C23A;*/
        }
        .btn-control {
          display: flex;
          justify-content: flex-end;
          align-items: center;
          padding-top: 10px;
          .cancel {
            font-size: 16px;
            color: #606266;
            margin-right: 20px;
            cursor: pointer;
            &:hover {
              color: #333;
            }
          }
          .confirm {
            font-size: 16px;
          }
        }
      }
    }
  }
}
</style>

其他模块引用该评论组件

<template>
    <Comment
      :relationId="kpiPerformance.id"
      :commentType="'1'"
      :drawer="isComment"
      @commentClose="commentClick('0')"
    ></Comment>
</template>

<script>
import Comment from "@/components/Comment";

export default {
    components: {
       Comment
    }
}
</script>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • vue实现评论列表功能

    具体代码如下所示: <!DOCTYPE html> <html> <head> <title>简易评论列表</title> <meta charset="utf-8"> <link rel="stylesheet" href="node_modules\bootstrap\dist\css\bootstrap.css" rel="external nofoll

  • Vue实现简单的发表评论功能

    本文实例为大家分享了Vue实现简单的发表评论功能的具体代码,供大家参考,具体内容如下 1.这是我在学习中的实例,有些的不足的地方,还望各位大佬指点,感谢哦~ 2.发表评论的效果图 点击"发表"之后的效果(每条评论之后点击"删除"可以删掉这一整条评论~) 3.完整代码展示(我html结构写的比较乱,这里提醒大家一下,没有定义类的div是可以删掉的,我是因为方便写样式所以多加了div) 还是要提醒一下,不要忘记引入vue.js,目录记得根据自己存放的位置改 <!D

  • Vue组件实现评论区功能

    本文实例为大家分享了Vue组件实现评论区的具体代码,供大家参考,具体内容如下 实现代码 <!DOCTYPE html> <html lang="en"> <head>     <meta charset="utf-8">     <meta name="viewport" content="width=device-width, inital-scale=1.0">

  • Vue.js实现文章评论和回复评论功能

    本来想把这个页面用jade渲染出来.评论部分用vue,但是想了想觉得麻烦,最后还是整个用vue的组件搞定他吧. 先上在线demo:http://jsbin.com/ceqifo/1/edit?js,output 再上效果图 可直接评论,点击别人的评论能回复别人的评论. html <div id="comment"> <article-content v-bind:article="article"></article-content&g

  • vue实现文章评论和回复列表

    本文实例为大家分享了vue实现文章评论和回复列表的具体代码,供大家参考,具体内容如下 效果预览: 父组件: <template>   <div class="comment-reply">     <div       v-for="(item, index) in articleLists"       :key="index"       class="article-list"     &g

  • vue组件实现发表评论功能

    本文实例为大家分享了vue组件实现发表评论的具体代码,供大家参考,具体内容如下 今天看了vue相关的视频,所以跟着做一个小demo把知识串联起来,内容很简单但是也算是学习道路上的一点进步. 1 思路分析 发表评论模块写入一个组件,提高复用性.关键点: 1).子组件通过localStorage向父组件传值2).子组件有自己的data存储user和content,即评论人和评论内容,也就是dom元素绑定的数据.3).点击‘发表评论’后,首先是将各条评论存入localStorage,然后通过在组件出绑

  • vue开发实现评论列表

    本文实例为大家分享了vue开发实现评论列表的具体代码,供大家参考,具体内容如下 index.html <!DOCTYPE html> <html>   <head>     <meta charset="utf-8">     <meta name="viewport" content="width=device-width,initial-scale=1.0">     <lin

  • vue实现评论列表

    本文实例为大家分享了vue实现评论列表的具体代码,供大家参考,具体内容如下 案例数据使用localStorage持久性存储 全局过滤器实现时间格式化 代码部分 <!DOCTYPE html> <html lang="zh">     <head>         <meta charset="UTF-8">         <meta name="viewport" content="

  • vue实现发表评论功能

    本文实例为大家分享了vue实现发表评论的具体代码,供大家参考,具体内容如下 <!DOCTYPE html> <html>     <head>         <meta charset="utf-8">         <link rel="stylesheet" href="bootstrap-3.3.7.css" />         <title></title

  • Vuepress 搭建带评论功能的静态博客的实现

    vuepress 是 Vue 驱动的静态站点生成工具 本文仅介绍,搭建静态博客的过程,具体教程及文档请点击进入 vuepress中文网 点击查看项目代码 vuepress初始化 下面初始化 # 将 github 新创建的仓库克隆到本地 git clone git@github.com:zhb333/readme-blog.git # 进入项目 cd readme-blog # npm 初始化, 按照提示回车 npm init # 安装 vuepress npm i vuepress -D # 安

随机推荐