效果:
实体层
省略了get set方法
@Entity
@Table(name = "t_comment")
public class Comment {
@Id
@GeneratedValue
private Long id;
private String nickname;
private String email;
private String content;
private String avatar;
@Temporal(TemporalType.TIMESTAMP)
private Date createTime;
@ManyToOne
private Blog blog;
@OneToMany(mappedBy = "parentComment")
private List<Comment> replyComments = new ArrayList<>();
@ManyToOne
private Comment parentComment;
private boolean adminComment;
}
业务层:
@Service
public class CommentServiceImpl implements CommentService {
@Autowired
private CommentRepository commentRepository;
@Override
public List<Comment> listCommentByBlogId(Long blogId) {
Sort sort = new Sort("createTime");
List<Comment> comments = commentRepository.findByBlogIdAndParentCommentNull(blogId,sort);
return eachComment(comments);
}
@Transactional
@Override
public Comment saveComment(Comment comment) {
Long parentCommentId = comment.getParentComment().getId();
if (parentCommentId != -1) {
comment.setParentComment(commentRepository.findOne(parentCommentId));
} else {
comment.setParentComment(null);
}
comment.setCreateTime(new Date());
return commentRepository.save(comment);
}
/**
* 循环每个顶级的评论节点
* @param comments
* @return
*/
private List<Comment> eachComment(List<Comment> comments) {
List<Comment> commentsView = new ArrayList<>();
for (Comment comment : comments) {
Comment c = new Comment();
BeanUtils.copyProperties(comment,c);
commentsView.add(c);
}
//合并评论的各层子代到第一级子代集合中
combineChildren(commentsView);
return commentsView;
}
/**
*
* @param comments root根节点,blog不为空的对象集合
* @return
*/
private void combineChildren(List<Comment> comments) {
for (Comment comment : comments) {
List<Comment> replys1 = comment.getReplyComments();
for(Comment reply1 : replys1) {
//循环迭代,找出子代,存放在tempReplys中
recursively(reply1);
}
//修改顶级节点的reply集合为迭代处理后的集合
comment.setReplyComments(tempReplys);
//清除临时存放区
tempReplys = new ArrayList<>();
}
}
//存放迭代找出的所有子代的集合
private List<Comment> tempReplys = new ArrayList<>();
/**
* 递归迭代,剥洋葱
* @param comment 被迭代的对象
* @return
*/
private void recursively(Comment comment) {
tempReplys.add(comment);//顶节点添加到临时存放集合
if (comment.getReplyComments().size()>0) {
List<Comment> replys = comment.getReplyComments();
for (Comment reply : replys) {
tempReplys.add(reply);
if (reply.getReplyComments().size()>0) {
recursively(reply);
}
}
}
}
}
控制层:
@Controller
public class CommentController {
@Autowired
private CommentService commentService;
@Autowired
private BlogService blogService;
@Value("${comment.avatar}")//comment.avatar: /images/avatar.png
private String avatar;
@GetMapping("/comments/{blogId}")
public String comments(@PathVariable Long blogId, Model model) {
model.addAttribute("comments", commentService.listCommentByBlogId(blogId));
return "blog :: commentList";
//<div th:fragment="commentList">
}
// <h3 class="ui dividing header">评论</h3>
// <div class="comment" th:each="comment : ${comments}">
// <a class="avatar">
// <img src="https://unsplash.it/100/100?image=1005" th:src="@{${comment.avatar}}">
// </a>
// <div class="content">
// <a class="author" >
// <span th:text="${comment.nickname}">Matt</span>
@PostMapping("/comments")
public String post(Comment comment, HttpSession session) {
Long blogId = comment.getBlog().getId();
comment.setBlog(blogService.getBlog(blogId));
User user = (User) session.getAttribute("user");
if (user != null) {
comment.setAvatar(user.getAvatar());
comment.setAdminComment(true);
} else {
comment.setAvatar(avatar);
}
commentService.saveComment(comment);
return "redirect:/comments/" + blogId;
}
}
前端:
<!--留言区域列表-->
<div id="comment-container" class="ui teal segment">
<div th:fragment="commentList">
<div class="ui threaded comments" style="max-width: 100%;">
<h3 class="ui dividing header">评论</h3>
<div class="comment" th:each="comment : ${comments}">
<a class="avatar">
<img src="https://unsplash.it/100/100?image=1005" th:src="@{${comment.avatar}}">
</a>
<div class="content">
<a class="author" >
<span th:text="${comment.nickname}">Matt</span>
<div class="ui mini basic teal left pointing label m-padded-mini" th:if="${comment.adminComment}">博主</div>
</a>
<div class="metadata">
<span class="date" th:text="${#dates.format(comment.createTime,'yyyy-MM-dd HH:mm')}">Today at 5:42PM</span>
</div>
<div class="text" th:text="${comment.content}">
How artistic!
</div>
<div class="actions">
<a class="reply" data-commentid="1" data-commentnickname="Matt" th:attr="data-commentid=${comment.id},data-commentnickname=${comment.nickname}" onclick="reply(this)">回复</a>
</div>
</div>
<div class="comments" th:if="${#arrays.length(comment.replyComments)}>0">
<div class="comment" th:each="reply : ${comment.replyComments}">
<a class="avatar">
<img src="https://unsplash.it/100/100?image=1005" th:src="@{${reply.avatar}}">
</a>
<div class="content">
<a class="author" >
<span th:text="${reply.nickname}"></span>
<div class="ui mini basic teal left pointing label m-padded-mini" th:if="${reply.adminComment}"></div>
<span th:text="|@ ${reply.parentComment.nickname}|" class="m-teal"></span>
</a>
<div class="metadata">
<span class="date" th:text="${#dates.format(reply.createTime,'yyyy-MM-dd HH:mm')}"></span>
</div>
<div class="text" th:text="${reply.content}">
How artistic!
</div>
<div class="actions">
<a class="reply" data-commentid="1" data-commentnickname="Matt" th:attr="data-commentid=${reply.id},data-commentnickname=${reply.nickname}" onclick="reply(this)">回复</a>
</div>
</div>
</div>
</div>
</div>
<div id="comment-form" class="ui form">
<input type="hidden" name="blog.id" th:value="${blog.id}">
<input type="hidden" name="parentComment.id" value="-1">
<div class="field">
<textarea name="content" placeholder="请输入评论信息..."></textarea>
</div>
<div class="fields">
<div class="field m-mobile-wide m-margin-bottom-small">
<div class="ui left icon input">
<i class="user icon"></i>
<input type="text" name="nickname" placeholder="姓名" th:value="${session.user}!=null ? ${session.user.nickname}">
</div>
</div>
<div class="field m-mobile-wide m-margin-bottom-small">
<div class="ui left icon input">
<i class="mail icon"></i>
<input type="text" name="email" placeholder="邮箱" th:value="${session.user}!=null ? ${session.user.email}">
</div>
</div>
<div class="field m-margin-bottom-small m-mobile-wide">
<button id="commentpost-btn" type="button" class="ui teal button m-mobile-wide"><i class="edit icon"></i>发布</button>
</div>
</div>
javascript部分
//评论表单验证
$('.ui.form').form({
fields: {
title: {
identifier: 'content',
rules: [{
type: 'empty',
prompt: '请输入评论内容'
}
]
},
content: {
identifier: 'nickname',
rules: [{
type: 'empty',
prompt: '请输入你的大名'
}]
},
type: {
identifier: 'email',
rules: [{
type: 'email',
prompt: '请填写正确的邮箱地址'
}]
}
}
});
$(function () {
$("#comment-container").load(/*[[@{/comments/{id}(id=${blog.id})}]]*/"comments/6");
});
$('#commentpost-btn').click(function () {
var boo = $('.ui.form').form('validate form');
if (boo) {
console.log('校验成功');
postData();
} else {
console.log('校验失败');
}
});
function postData() {
$("#comment-container").load(/*[[@{/comments}]]*/"",{
"parentComment.id" : $("[name='parentComment.id']").val(),
"blog.id" : $("[name='blog.id']").val(),
"nickname": $("[name='nickname']").val(),
"email" : $("[name='email']").val(),
"content" : $("[name='content']").val()
},function (responseTxt, statusTxt, xhr) {
// $(window).scrollTo($('#comment-container'),500);
clearContent();
});
}
function clearContent() {
$("[name='content']").val('');
$("[name='parentComment.id']").val(-1);
$("[name='content']").attr("placeholder", "请输入评论信息...");
}
function reply(obj) {
var commentId = $(obj).data('commentid');
var commentNickname = $(obj).data('commentnickname');
$("[name='content']").attr("placeholder", "@"+commentNickname).focus();
$("[name='parentComment.id']").val(commentId);
$(window).scrollTo($('#comment-form'),500);
}