VUE+Java实现评论回复功能

背景

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

效果

VUE+Java实现评论回复功能_第1张图片

后台

  • SQL

VUE+Java实现评论回复功能_第2张图片

  • Java

Controller

/**
  * 根据关联id获取评论信息
  * @param relationId 关联id
  * @param type 类型
  * @return: com.harvey.result.ResultSupport
  * @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
  * @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>

	
	<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>

	
	<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="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>

你可能感兴趣的:(Java,vue,java,elementui)