可以看到最后实现的样子是对文章直接发起的评论会在显示在第一级,之后对评论的回复都会显示在第二级(包括对回复的回复的回复)
并且当我们发布了一条评论后,之刷新当前的评论区而不会刷新整个页面
表设计
存储评论需要存储的内容除了他的id,评论人信息,评论时间,评论内容,还需要存储关联的博客(评论和博客是多对一关系)以及该评论的父评论(也就是他是回复谁的评论)和直接子评论(回复当前评论的评论)
这里直接给出pojo类
@Data
public class BlogComment {
private Long commentId;
private Long blogId;
private String commentator;
private String email;
private String commentBody;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date commentCreateTime;
private Byte commentStatus;
private Byte isDeleted;
private Byte isAdmin;
private BlogComment parentComment;
private Blog blog;
private Long parentcommentId;
private List<BlogComment> replyComments;
public List<BlogComment> getReplyComments() {
return replyComments;
}
}
前端实现
<!-- Comment Area Start -->
<div class="comment_area section_padding_50 clearfix" id="comment-container">
<div th:fragment="commentList">
<h4 class="mb-30"><span th:text="${totalComment}">0</span> Comments</h4>
<ol>
<!-- Single Comment Area -->
<li class="single_comment_area" th:each="comment : ${comments}">
<div class="comment-wrapper d-flex" >
<!-- Comment Meta -->
<div class="comment-author">
<img src="img/blog-img/19.jpg" th:src="@{/img/blog-img/19.jpg}" alt="">
</div>
<!-- Comment Content -->
<div class="comment-content">
<span class="comment-date text-muted" th:text="${#dates.format(comment.commentCreateTime,'yyyy-MM-dd HH:mm')}">27 Aug 2018</span>
<h5 th:text="${comment.commentator}">Brandon Kelley</h5>
<p th:text="${comment.commentBody}">Neque porro qui squam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora.</p>
<a href="#">Like</a>
<a class="active" th:attr="data-commentid=${comment.commentId},data-commentnickname=${comment.commentator}" onclick="reply(this)">Reply</a>
</div>
</div>
<ol class="children" th:if="${#arrays.length(comment.replyComments)}>0">
<li class="single_comment_area" th:each="reply : ${comment.replyComments}">
<div class="comment-wrapper d-flex">
<!-- Comment Meta -->
<div class="comment-author">
<img src="img/blog-img/18.jpg" th:src="@{/img/blog-img/18.jpg}" alt="">
</div>
<!-- Comment Content -->
<div class="comment-content">
<span class="comment-date text-muted" th:text="${#dates.format(reply.commentCreateTime,'yyyy-MM-dd HH:mm')}">27 Aug 2018</span>
<span th:text="|@ ${reply.parentComment.commentator}|" class="m-teal">@ 小白</span><h5 th:text="${reply.commentator}">Brandon Kelley</h5>
<p th:text="${reply.commentBody}">Neque porro qui squam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora.</p>
<a href="#" >Like</a>
<a class="active" th:attr="data-commentid=${reply.commentId},data-commentnickname=${reply.commentator}" onclick="reply(this)">Reply</a>
</div>
</div>
</li>
</ol>
</li>
</ol>
</div>
</div>
<!-- Leave A Comment -->
<div class="leave-comment-area section_padding_50 clearfix">
<div class="comment-form">
<h4 class="mb-30">Leave A Comment</h4>
<!-- Comment Form -->
<div class="ui form">
<input type="hidden" name="blog.id" th:value="${blogDetailVO.blogId}">
<input type="hidden" name="parent.commentid" value="-1">
<div class="form-group">
<input type="text" class="form-control" name="nickname" id="contact-name" placeholder="Name">
</div>
<div class="form-group">
<input type="email" class="form-control" name="email" id="contact-email" placeholder="Email">
</div>
<div class="form-group">
<textarea class="form-control" name="content" id="message" cols="30" rows="10" placeholder="Message"></textarea>
</div>
<button type="button" id="commentpost-btn" class="btn contact-btn">Post Comment</button>
</div>
</div>
</div>
</div>
</div>
</div>
将整个评论区域用
包裹,可以达到只更新这部分的效果js部分:
先检查评论内容的有效性再将评论提交到后台,使用的是load就可以达到只更新部分,提交结束后清空评论区的内容
<script type="text/javascript">
$(function () {
$("#comment-container").load("[[@{/comments/{id}(id=${blogDetailVO.blogId})}]]");
});
$('#commentpost-btn').click(function () {
var search_str = /^[\w\-\.]+@[\w\-\.]+(\.\w+)+$/;
var i = 0;
var commentator = $("[name='nickname']").val()
var email = $("[name='email']").val()
var commentBody = $("[name='content']").val()
if(commentator.length<1){
$("[name='nickname']").attr("placeholder", "请输入");
i++;
}
if(!search_str.test(email)){
$("[name='email']").attr("placeholder", "请输入正确邮箱格式");
$("[name='email']").val('')
i++;
}
if(commentBody.length<1){
$("[name='content']").attr("placeholder", "请输入");
i++;
}
if(i == 0){
postData();
}
})
function postData() {
$("#comment-container").load("[[@{/comments}]]",{
"parentcommentId" : $("[name='parent.commentid']").val(),
"blogId" : $("[name='blog.id']").val(),
"commentator": $("[name='nickname']").val(),
"email" : $("[name='email']").val(),
"commentBody" : $("[name='content']").val()
},function (responseTxt, statusTxt, xhr) {
$(window).scrollTo($('#comment-container'),500);
clearContent();
});
}
function clearContent() {
$("[name='content']").val('');
$("[name='parentComment.id']").val(-1);
$("[name='nickname']").val(''), $("[name='email']").val(''),
$("[name='content']").attr("placeholder", "Message");
}
function reply(obj) {
var commentId = $(obj).data('commentid');
var commentNickname = $(obj).data('commentnickname');
$("[name='content']").attr("placeholder", "@"+commentNickname).focus();
$("[name='parent.commentid']").val(commentId);
$(window).scrollTo($('#comment-form'),500);
}
</script>
后端部分
1. 获取
因为在前端展示的时候我们要把直接回复文章的评论放在第一级,对于回复该评论的回复都放在第二级,但是对照表结构如果直接取会发现有第三第四级的出现,现在的任务就是把第三第四级都放在第二级
controller层
@GetMapping("/comments/{blogId}")
public String comments(@PathVariable Long blogId, Model model) {
List<BlogComment> comments = commentService.listCommentByBlogId(blogId);
int totalComment = 0;
for (BlogComment comment : comments) {
totalComment++;
List<BlogComment> replyComments = comment.getReplyComments();
for (BlogComment replyComment : replyComments) {
totalComment++;
}
}
model.addAttribute("totalComment", totalComment);
model.addAttribute("comments", comments);
return "blog/single :: commentList";
}
Service层
先把直接回复博客的评论放到第1层,再对直接回复博客的评论的子评论进行递归查找处理,把他们都放在第二层然后返回
@Service
@Transactional
public class CommentServiceImpl implements CommentService {
@Autowired
private BlogCommentMapper blogCommentMapper;
//存放迭代找出的所有子代的集合
private List<BlogComment> tempReplys = new ArrayList<>();
@Override
public List<BlogComment> listCommentByBlogId(Long blogId) {
//先找所以最上级的评论
List<BlogComment> comments = blogCommentMapper.findByBlogIdAndParentCommentNull(blogId);
return eachComment(comments);
}
@Override
public void saveComment(BlogComment blogComment) {
Long parentCommentId = blogComment.getParentcommentId();
if (parentCommentId != -1) {
blogComment.setParentComment(blogCommentMapper.getCommentByCommentId(parentCommentId));
} else {
blogComment.setParentComment(null);
}
blogComment.setCommentCreateTime(new Date());
blogCommentMapper.save(blogComment);
}
/**
* 循环每个顶级的评论节点
* @param comments
* @return
*/
private List<BlogComment> eachComment(List<BlogComment> comments) {
List<BlogComment> commentsView = new ArrayList<>();
for (BlogComment comment : comments) {
BlogComment c = new BlogComment();
BeanUtils.copyProperties(comment,c);
commentsView.add(c);
}
//合并评论的各层子代到第一级子代集合中
combineChildren(commentsView);
return commentsView;
}
private void combineChildren(List<BlogComment> comments) {
for (BlogComment comment : comments) {
List<BlogComment> replys1 = blogCommentMapper.findReplyCommentsByParentCommentId(comment.getCommentId());
for (BlogComment reply1 : replys1) {
//循环迭代,找出子代,存放在tempReplys中
reply1.setParentComment(comment);
recursively(reply1);
}
//修改顶级节点的reply集合为迭代处理后的集合
comment.setReplyComments(tempReplys);
//清除临时存放区
tempReplys = new ArrayList<>();
}
}
/**
* 递归迭代,剥洋葱
* @param comment 被迭代的对象
* @return
*/
private void recursively(BlogComment comment) {
tempReplys.add(comment);//顶节点添加到临时存放集合
if (blogCommentMapper.findReplyCommentsByParentCommentId(comment.getCommentId()).size()>0) {
List<BlogComment> replys = blogCommentMapper.findReplyCommentsByParentCommentId(comment.getCommentId());
for (BlogComment reply : replys) {
reply.setParentComment(comment);
tempReplys.add(reply);
if (blogCommentMapper.findReplyCommentsByParentCommentId(reply.getCommentId()).size()>0) {
recursively(reply);
}
}
}
}
@Override
public Boolean deleteBatch(Integer[] ids) {
return blogCommentMapper.deleteBatch(ids) > 0;
}
@Override
public PageResult getAll(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<BlogComment> blogCategories = blogCommentMapper.getAllComment();
int totalCategories = blogCommentMapper.getTotalComments();
PageResult pageResult = new PageResult(blogCategories, totalCategories, 10, pageNum);
return pageResult;
}
@Override
public int getTotalComments() {
return blogCommentMapper.getTotalComments();
}
}