拾柴网,也可以简单的评论功能啦,流方式返回数据,二维数组

机缘

积累经验,0搭建前后端,程序小白搭建开发


成就

  1. 项目中,发表项目后,进行一级评论的总数统计,也就是话题总数
  2. 实现一级和二级的评论以及回复
  3. 二维数组,数据拼装,单表查询

后端:

        DTO对象:

        接收前端传来的评论数据,以及后端数据拼装,进行入库的对象

package com.admin.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommentDTO {

    /**
     * 用户id
     */
    private Integer userId;

    /**
     * 项目id
     */
    private Integer projectId;

    /**
     * 用户头像
     */
    private String userAvatar;

    /**
     * 用户昵称
     */
    private String userNickName;

    /**
     * 评论内容
     */
    private String content;

    /**
     * 评论时间
     */
    private LocalDateTime contentTime;

    /**
     * 回复评论Id
     */
    private Integer replyId;

    /**
     * 回复的昵称
     */
    private String replyNickName;
}

        VO对象:

                返回去给前端的数据、或者是拼装返回的数据:

package com.admin.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor

public class CommentVO {
    /**
     * 评论的Id
     */
    private Integer id;
    /**
     * 用户id
     */
    private Integer userId;

    /**
     * 项目id
     */
    private Integer projectId;

    /**
     * 用户头像
     */
    private String userAvatar;

    /**
     * 用户昵称
     */
    private String userNickName;

    /**
     * 评论内容
     */
    private String content;

    /**
     * 是否显示回复二级评论 0不显示 1显示
     */
    private Integer isReplying;

    /**
     * 评论时间
     */
    @JsonFormat(pattern = "yyyy.MM.dd")
    private LocalDateTime contentTime;


    /**
     * 用于存储对应的二级评论
     */
    private List<CommentSecondVO> commentSecondList;
}
package com.admin.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@AllArgsConstructor
@NoArgsConstructor
/**
 * 存储二级评论的内容。对象
 */
public class CommentSecondVO {
    /**
     * 评论的Id
     */
    private Integer id;
    /**
     * 用户id
     */
    private Integer userId;

    /**
     * 项目id
     */
    private Integer projectId;

    /**
     * 用户头像
     */
    private String userAvatar;

    /**
     * 用户昵称
     */
    private String userNickName;

    /**
     * 评论内容
     */
    private String content;

    /**
     * 是否显示回复二级评论 0不显示 1显示
     */
    private Integer isReplying;

    /**
     * 评论时间
     */
    @JsonFormat(pattern = "yyyy.MM.dd")
    private LocalDateTime contentTime;

    /**
     * 回复评论Id
     */
    private Integer replyId;

    /**
     * 回复的昵称
     */
    private String replyNickName;

}

        Controller控制层:

package com.admin.controller.user;

import com.admin.dto.CommentDTO;
import com.admin.result.Result;
import com.admin.service.CommentService;
import com.admin.vo.CommentVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@Api(tags = "评论相关模块")
@Slf4j
@RequestMapping("/user/comment")
public class CommentController {
    @Autowired
    private CommentService commentService;

    @ApiOperation("新增评论")
    @PostMapping("/add")
    public Result<Boolean> add(@RequestBody CommentDTO commentDTO){
        log.info("接受添加评论的信息:{}",commentDTO);
       Boolean b =  commentService.add(commentDTO);
       return Result.success(b);
    }

    @ApiOperation("获取项目的所有评论")
    @GetMapping("/getProjectComments/{projectId}")
    public Result<List<CommentVO>> getProjectComments(@PathVariable Integer projectId){
        log.info("项目的Id:{}",projectId);
        List<CommentVO> commentVOList =  commentService.getProjectComments(projectId);
        return Result.success(commentVOList);
    }

    @ApiOperation("获取项目话题一级评论的总数")
    @GetMapping("/getCommentTalkTotal/{projectId}")
    public Result<Integer> getCommentTalkTotal(@PathVariable Integer projectId){
        log.info("获取项目一级评论的总数的项目的Id:{}",projectId);
        Integer result =   commentService.getCommentTalkTotal(projectId);
        return Result.success(result);
    }

}

    服务层和实现层:

        服务层:

package com.admin.service;

import com.admin.dto.CommentDTO;
import com.admin.vo.CommentVO;

import java.util.List;

public interface CommentService {
    /**
     * 新增评论
     * @param commentDTO
     * @return
     */
    Boolean add(CommentDTO commentDTO);

    /**
     * 获取项目的评论
     * @param projectId
     * @return
     */
    List<CommentVO> getProjectComments(Integer projectId);

    /**
     * 获取项目话题一级评论的总数
     * @param projectId
     * @return
     */
    Integer getCommentTalkTotal(Integer projectId);
}

        实现层:

package com.admin.service.Impl;

import com.admin.dto.CommentDTO;
import com.admin.entity.Comment;
import com.admin.exception.BaseException;
import com.admin.mapper.CommentMapper;
import com.admin.service.CommentService;
import com.admin.vo.CommentSecondVO;
import com.admin.vo.CommentVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Service
public class CommentServiceImpl implements CommentService {
    @Autowired
    private CommentMapper commentMapper;

    /**
     * 新增评论
     * @param commentDTO
     * @return
     */
    @Override
    public Boolean add(CommentDTO commentDTO) {
        try {
            commentDTO.setContentTime(LocalDateTime.now());
            Comment comment = new Comment();
            BeanUtils.copyProperties(commentDTO,comment);
            Boolean b = commentMapper.add(comment);
            return b;
        }catch (Exception e){
            log.error(String.valueOf(e));
            throw new BaseException("评论失败,请联系管理员");
        }
    }

    /**
     * 获取项目的评论
     * @param projectId
     * @return
     */
    @Override
    public List<CommentVO> getProjectComments(Integer projectId) {
        //1.查询项目的所有评论
        List<Comment> commentList =  commentMapper.getProjectComments(projectId);

        //2.拆分哪些是一级评论集合,哪些是二级评论
            //一级评论集合
        List<Comment> commentFirstList = commentList.stream()
                .filter(comment -> comment.getReplyId() == null)
                .collect(Collectors.toList());
            //二级评论集合
        List<Comment> commentSecondList = commentList.stream()
                .filter(comment -> comment.getReplyId() != null)
                .collect(Collectors.toList());

        //3.对象数据拷贝VO
        List<CommentVO> CommentVOList = commentFirstList.stream().map(o -> {
            CommentVO commentVO = new CommentVO();
            BeanUtils.copyProperties(o, commentVO);
            //用来是否显示回复二级评论属性
            commentVO.setIsReplying(0);
            return commentVO;
        }).collect(Collectors.toList());
        List<CommentSecondVO> CommentSecondVOList = commentSecondList.stream().map(o -> {
            CommentSecondVO commentSecondVO = new CommentSecondVO();
            BeanUtils.copyProperties(o, commentSecondVO);
            commentSecondVO.setIsReplying(0);
            return commentSecondVO;
        }).collect(Collectors.toList());

        //4.二级评论建立 哈希值, k:是回复的评论id replyId v:数组
        if(CommentSecondVOList!=null && CommentSecondVOList.size()>0){
            Map<Integer, List<CommentSecondVO>> commentSecondVOMap = CommentSecondVOList.stream()
                    .collect(Collectors.groupingBy(CommentSecondVO::getReplyId));
            //5.去寻找一级评论的id,将二级评论放到CommentVO里面的二级评论集合里面
            for(CommentVO commentVO :CommentVOList){
                List<CommentSecondVO> commentSecondVOS = commentSecondVOMap.get(commentVO.getId());
                if(commentSecondVOS!=null && commentSecondVOS.size()>0){
                    commentVO.setCommentSecondList(commentSecondVOS);
                }
            }
        }
        //6.返回前端
        return CommentVOList;
    }

    /**
     * 获取项目话题一级评论的总数
     * @param projectId
     * @return
     */
    @Override
    public Integer getCommentTalkTotal(Integer projectId) {
        Integer result =   commentMapper.getCommentTalkTotal(projectId);
        return result;
    }
}

注意这个接口:

        Mapper层:

package com.admin.mapper;

import com.admin.entity.Comment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface CommentMapper {
    /**
     * 新增评论
     * @param comment
     * @return
     */
    Boolean add(Comment comment);

    /**
     * 查询项目的所有评论
     * @param projectId
     * @return
     */
    @Select("select * from comment where projectId = #{projectId} order by contentTime desc")
    List<Comment> getProjectComments(Integer projectId);

    /**
     * 获取项目话题一级评论的总数
     * @param projectId
     * @return
     */
    @Select("SELECT COUNT(*) from comment where projectId = #{projectId} and replyId is NULL")
    Integer getCommentTalkTotal(Integer projectId);
}

前端:

        评论的组件:

<template>
  <div class="commentList">
    <div class="commentInput">
      <textarea v-model="commentText" placeholder="输入您的评论..." maxlength="450"/>
      <div class="commentInput_btn">
        <div class="commentInput_btn_emoji">
          <el-dropdown trigger="click">
          <span class="el-dropdown-link" style="font-size: 30px">
          {{emojiJson[0]}}
          </span>
            <el-dropdown-menu slot="dropdown">
              <div class="emoji_dialog">
                <i style="cursor: pointer" v-for="(item,index) in emojiJson" :key="index" @click="addEmoji(item,'first')">
                  {{item}}
                </i>
              </div>
            </el-dropdown-menu>
          </el-dropdown>
        </div>
        <!-- 您可以添加更多的表情 -->
        <el-button type="danger" size="medium" @click="submitComment('first')">评论</el-button>
      </div>
    </div>


    <div class="comments" v-loading="loading">
      <div v-for="(comment, index) in commentList" :key="index">
<!--        一级评论内容-->
        <div class="comment_list">
          <div class="comment_user_cover">
            <el-avatar :src="comment.userAvatar" width="50" height="50"/>
          </div>
          <div class="comment_userInfo_content">
            <div>
              {{comment.userNickName}} {{comment.contentTime}}
            </div>
            <div style="font-size: 20px;color: black">
              {{ comment.content }}
            </div>
          </div>
          <div class="comment_reply_btn">
            <div style="cursor: pointer" @click="replyComment(comment,index)">
              <i class="el-icon-chat-dot-square"></i>
              <span v-if="comment.isReplying == 0">回复</span>
              <span v-else-if="comment.isReplying == 1">收起</span>
            </div>
          </div>
        </div>

<!--        一级级评论盒子-->
        <div v-if="comment.isReplying == 1" class="reply_box">
          <div style="width: 90%">
            <textarea v-model="commentSecondText" :placeholder="placeholderReply"/>
            <div class="commentInput_btn">
              <div class="commentInput_btn_emoji">
                <el-dropdown trigger="click">
          <span class="el-dropdown-link" style="font-size: 30px">
          {{emojiJson[0]}}
          </span>
                  <el-dropdown-menu slot="dropdown">
                    <div class="emoji_dialog">
                      <i style="cursor: pointer" v-for="(item,index) in emojiJson" :key="index" @click="addEmoji(item,'second')">
                        {{item}}
                      </i>
                    </div>
                  </el-dropdown-menu>
                </el-dropdown>
              </div>
              <!-- 您可以添加更多的表情 -->
              <el-button type="danger" size="medium" @click="submitComment('second',comment)">评论</el-button>
            </div>
          </div>
        </div>

<!--      二级评论内容  -->
          <div class="comment_second_list">
            <div  v-for="(commentSecond,index) in comment.commentSecondList" :key="index">
              <div class="comment_second_content">
                <div class="comment_user_cover">
                  <el-avatar :src="commentSecond.userAvatar" width="50" height="50"/>
                </div>
                <div class="comment_second_userInfo_content">
                  <div>
                    {{commentSecond.userNickName}} 回复 {{commentSecond.replyNickName}} {{commentSecond.contentTime}}
                  </div>
                  <div style="font-size: 20px;color: black">
                    {{ commentSecond.content }}
                  </div>
                </div>
                <div class="comment_reply_btn">
                  <div style="cursor: pointer" @click="replyComment(commentSecond,index)">
                    <i class="el-icon-chat-dot-square"></i>
                    <span v-if="commentSecond.isReplying == 0">回复</span>
                    <span v-else-if="commentSecond.isReplying == 1">收起</span>
                  </div>
                </div>
              </div>
              <!--       二级评论盒子-->
              <div v-if="commentSecond.isReplying === 1" class="reply_box">
                <div style="width: 80%">
                  <textarea v-model="commentSecondText" :placeholder="placeholderReply"/>
                  <div class="commentInput_btn">
                    <div class="commentInput_btn_emoji">
                      <el-dropdown trigger="click">
                        <span class="el-dropdown-link" style="font-size: 30px">
                        {{emojiJson[0]}}
                        </span>
                        <el-dropdown-menu slot="dropdown">
                          <div class="emoji_dialog">
                            <i style="cursor: pointer" v-for="(item,index) in emojiJson" :key="index" @click="addEmoji(item,'second')">
                              {{item}}
                            </i>
                          </div>
                        </el-dropdown-menu>
                      </el-dropdown>
                    </div>
                    <!-- 您可以添加更多的表情 -->
                    <el-button type="danger" size="medium" @click="submitComment('second',commentSecond)">评论</el-button>
                  </div>
                </div>
              </div>
            </div>
        </div>


      </div>
    </div>
  </div>
</template>

<script>
import {emojiData} from "@/api/data/emoji";
import {getToken} from "@/utils/http/auth";
import {message} from "@/utils/Message";
import {addComment, getProjectComments} from "@/api/user/comment";
export default {
  name: "CommentList",
  props:{
    projectId:{
      type:Number,
      default:null,
    }
  },
  created() {
    this.getComment(this.projectId)
    //从vuex 获取数据
    const userInfo  = this.$store.getters.userInfo_data;
    this.firstComment.userId =userInfo.id
    this.secondComment.userId = userInfo.id
    if(userInfo.head !=null && userInfo.head !=""){
      this.firstComment.userAvatar =userInfo.head
      this.secondComment.userAvatar = userInfo.head
    }
    this.firstComment.userNickName =userInfo.nickName
    this.secondComment.userNickName = userInfo.nickName

  },
  data() {
    return {
      //二级评论提示
      placeholderReply:"",
      //表情数据
      emojiJson:emojiData.data.split(","),
      commentText: "",
      commentSecondText:"",
      // 一级评论的对象
      firstComment:{
        userId:null,
        projectId:null,
        userAvatar:"https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg",
        userNickName:"",
        content:"",
      },
      // 二级评论的对象
      secondComment:{
        userId:null,
        projectId:null,
        userAvatar:"https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg",
        userNickName:"",
        content:"",
        //回复评论的id
        replyId:null,
        //回复评论的昵称
        replyNickName:"",
      },
      //所有评论集合
      commentList: [],
      //笼罩层
      loading:true,
      //存储回复评论的Id, 点击的时候的变化
      temporaryId:null,
    };
  },
  methods: {
    /**
     * 回复评论
     * @param comment
     * @param index
     */
    replyComment(comment,index){
      //每次只能回复一个评论  点击回复又马上收起
        if(this.temporaryId === comment.id){
          console.log("1")
          if(comment.isReplying === 0){
            comment.isReplying = 1;
          }else if(comment.isReplying === 1){
            comment.isReplying = 0;
          }
        }else {
          //点击回复完评论,没有回复这个评论,又去点击另外一个评论进行回复
          if(comment.isReplying === 0){
            //当点击回复一级评论的时候,全部的二级评论回复状态给收起来,所有的一级评论也给我收起来,最后再展开回复当前的评论
            if(comment.replyId === undefined){
              //当点击回复一级评论的时候,全部的二级评论回复状态给收起来,所有的一级评论也给我收起来,
              this.commentList.forEach(comments=>{
                comment.isReplying = 0
                if(comments.commentSecondList !== null && comments.commentSecondList.length>0){
                  comments.commentSecondList.forEach(item=>{
                    item.isReplying=0;
                  })
                }
              })
            }else {
              //当点击回复二级评论的时候
              this.commentList.forEach(comment=>{
                comment.isReplying = 0
              })
            }
            comment.isReplying = 1;
          }
        }
      this.placeholderReply = "回复:"+comment.userNickName;
      this.temporaryId = comment.id
    },



    /**
     * 插入表情
     * @param emoji
     * @param type
     */
    addEmoji(emoji,type) {
      switch (type) {
        case "first":
          this.commentText +=emoji;
          break;
        case "second":
          this.commentSecondText += emoji;
          break;
      }
    },

    /**
     * 提交评论
     */
    submitComment(type,comment) {
      const token =   getToken();
      if(token){
        switch (type){
          case "first":
            if (this.commentText.trim()!== "") {
              this.firstComment.projectId = this.projectId
              this.firstComment.content=this.commentText
              this.addCommentMethod(this.firstComment)
              this.commentText =""
            }else {
              message(true,"不能评论空内容!!!","error",true)
            }
            console.log(this.firstComment,"nickName")
            break;
          case "second":
            if (this.commentSecondText.trim()!== "") {
              this.secondComment.projectId = this.projectId
              //这里做一判断,如果是在二级评论下回复的,它还是属于第一级评论下的二级评论
              if(comment.replyId !== undefined && comment.replyId !==null){
                this.secondComment.replyId = comment.replyId
              }else {
                this.secondComment.replyId = comment.id
              }
              this.secondComment.replyNickName = comment.userNickName
              this.secondComment.content = this.commentSecondText
              console.log(this.secondComment,"this.secondComment")
              this.addCommentMethod(this.secondComment)
              this.commentSecondText=""
            }else {
              message(true,"不能评论空内容!!!","error",true)
            }
            break;
        }
      }else {
        message(true,"温馨提示,没有登录,不能评论哦","error",true)
      }
    },

    /**
     * 新增评论接口
     * @param data
     */
    addCommentMethod(data){
      addComment(data).then(res=>{
        if(res.data.code == 200){
          message(true,"评论成功,文明评论哦","success",true)
          this.getComment(data.projectId)
          this.$emit("get-talk-comment-total",data.projectId)
        }else {
          message(true,res.data.msg,"error",true)
        }
      })
    },

    /**
     * 获取项目的所有评论数据
     * @param projectId
     */
    getComment(projectId){
      getProjectComments(projectId).then(res=>{
        if(res.data.code == 200){
          this.commentList = res.data.data
          this.loading=false
        }
      })
    }
  },
}
</script>

<style scoped>
.commentList{
  height: 100%;
  width: 100%;
  box-sizing: border-box;
}
.commentInput{
  margin-bottom: 50px;
}
.commentInput_btn{
  display: flex;
  flex-direction: row;
  height: 50px;
  padding-top: 10px;
  width: 120px;
  float: right;
}
.commentInput_btn_emoji{
  cursor: pointer;
  width: 70px;
  height: 100%;
}
.emoji_dialog{
  height: 100px;
  width: 400px;
  font-size: 20px;
  overflow-y: auto;
}

textarea {
  width: 100%;
  height: 100px;
  padding: 10px;
  box-sizing: border-box;
  border: 1px solid #ccc;
  font-size: 16px;
  resize: none;
}
.comments{
  height: calc(100% - 170px);
  overflow-y: auto;
}
.comment_list{
  margin-top: 20px;
  width: 100%;
  display: flex;
  flex-direction: row;
  height: 70px;
}
.comment_userInfo_content,.comment_reply_btn{
  height: 100%;
  display: flex;
  color: gray;
  font-size: 16px;
}
.comment_userInfo_content {
  width: 83%;
  flex-direction: column;
  justify-content: space-around;
  padding-left: 10px;
}
.comment_reply_btn{
  flex-direction: row;
  padding-top: 5px;
  cursor: pointer;
}
.reply_box{
  margin-top: 20px;
  width: 100%;
  display: flex;
  flex-direction: row;
  justify-content: center;
}
.comment_second_list{
  /*margin-top: 20px;*/
  width: 100%;
}
.comment_second_content{
  margin: 20px 0px;
  width: 100%;
  padding-left: 10%;
  display: flex;
  flex-direction: row;
}
.comment_second_userInfo_content{
  width: 81%;
  flex-direction: column;
  justify-content: space-around;
  padding-left: 10px;
}
</style>

        使用:

<!--            话题盒子-->
            <div class="item_content" v-if="showComment">
              <comment-list :project-id="projectAllData.projectBaseInfo.id" @get-talk-comment-total="getTalkCountTotal"/>
            </div>

憧憬

目前只有进行简单的评论,还没有进行过滤敏感词,以及删除评论功能,后续会进行更新。

总结

1、在分析自己要做成具体的样子,需要什么数据结构,完成这些数据的步骤

2、前端:自己模拟数据,画好

3、再写接口的同时,要分析步骤,罗列出来,再进行代码的编写

4、以前写的代码,偶尔进行回顾,不然会发现不是自己写的了,多练

开发过程中偶尔会有很多考虑不到的地方,希望小伙伴或者前辈能够在评论区给予建议!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值