文章目录
通常情况下,在博客网站中,无论是文章下的评论还是单独的留言,都会有多级的回复功能。这里我们主要介绍多级回复功能的留言实现。
新增留言
在这里的新增留言,由留言板块、姓名板块和邮箱板块组成,其中隐藏获取了用户的电脑和浏览器内核版本。
<!--新增留言-->
<div id="message-form" class="ui form">
<input type="hidden" name="parentMessage.id" value="-1">
<input type="hidden" id="win1_lei" name="window.name" value="unknown">
<input type="hidden" id="win2_lei" name="browser.name" value="unknown">
<!--留言区-->
<div class="field">
<textarea name="content" placeholder="请输入留言信息..."></textarea>
</div>
<div class="ui grid">
<!--输入姓名-->
<div class="five wide column">
<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>
<!--输入邮箱-->
<div class="five wide column">
<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>
<div class="right aligned six wide column">
<div class="field m-mobile-wide m-margin-bottom-small">
<button id="messagepost-btn" type="button" class="ui teal button m-mobile-wide"><i
class="edit icon"></i>发布
</button>
</div>
</div>
</div>
</div>
这里的js主要是:通过调用Browser这个函数,获取用户的浏览器和电脑内核信息。
<script>
var info = new Browser();
currentTimeHtml1 = ' ' + info.os + ' ' + info.osVersion + ' ';
currentTimeHtml2 = ' ' + info.browser + info.version + ' ';
document.getElementById("win1_lei").value = currentTimeHtml1;
document.getElementById("win2_lei").value = currentTimeHtml2;
</script>
这里是对评论表单的input进行规范
//评论表单验证
$('.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: '请填写正确的邮箱地址'
}]
}
}
});
$('#messagepost-btn').click(function () {
var boo = $('.ui.form').form('validate form');
if (boo) {
console.log('校验成功');
postData();
} else {
console.log('校验失败');
}
});
给后端发送评论的数据(分为两种:一种是单独的留言;另一种是回复留言的留言)
//发送请求给后端
function postData() {
$("#message-container").load(/*[[@{/messages}]]*/"", {
"parentMessage.id": $("[name='parentMessage.id']").val(),
// "blog.id" : $("[name='blog.id']").val(),
"nickname": $("[name='nickname']").val(),
"email": $("[name='email']").val(),
"content": $("[name='content']").val(),
"windowName": $("[name='window.name']").val(),
"browserName": $("[name='browser.name']").val()
}, function (responseTxt, statusTxt, xhr) {
// $(window).scrollTo($('#message-container'),500);
clearContent();
});
}
后端处理得到的留言(多层留言一会再说)
//新增留言
@PostMapping("/message")
public String post(Message message, HttpSession session, Model model){
User user = (User) session.getAttribute("user");
//设置头像
if(user != null){
message.setAvatar(user.getAvatar());
message.setAdminMessage(true);
} else {
message.setAvatar(avatar);
}
if(message.getParentMessage().getId()!=null){
message.setParentMessageId(message.getParentMessage().getId());
}
//System.out.println(message);
messageService.saveMessage(message);
List<Message> messages = messageService.listMessage();
model.addAttribute("messages",messages);
return "message :: messageList";
}
留言显示
留言显示的主要模块有:留言人的姓名,(被留言的人),留言的时间,电脑和浏览器内核,留言的内容,回复按钮等部分。
这里包含父级留言和子级留言。
<div class="ui bottom attached m-margin-top">
<div id="message-container" class="ui teal segment">
<div th:fragment="messageList">
<div class="ui threaded comments" style="max-width: 100%;">
<h3 class="ui dividing header">留言</h3>
<div class="comment" th:each="message : ${messages}">
<a class="avatar">
<img src="../static/images/me.jpg" th:src="@{${message.avatar}}">
</a>
<div class="content">
<a class="author">
<span th:text="${message.nickname}">Matt</span>
<div class="ui mini basic teal left pointing label m-padded-mini"
th:if="${message.adminMessage}">栈主
</div>
</a>
<div class="metadata">
<span class="date"
th:text="${#dates.format(message.createTime,'yyyy-MM-dd HH:mm')}">今天下午 5:42</span>
</div>
<div class="" aria-hidden="true" style="">
<span th:text="${message.windowName}"
style="background-color: #f0eff3; color: #888888; border-radius: 10%"> Windows10 </span>
<span th:text="${message.browserName}"
style="background-color: #f0eff3; color: #888888; border-radius: 10%"> Chrome 97.0.4692.71 </span>
</div>
<div class="text" th:text="${message.content}">太赞了!</div>
<div class="actions">
<a class="reply" data-messageid="1" data-messagenickname="Matt"
th:attr="data-messageid=${message.id},data-messagenickname=${message.nickname}"
onclick="reply(this)">回复</a>
<a class="delete" href="#" th:href="@{/messages/{id}/delete(id=${message.id})}"
onclick="return confirm('确定要删除该评论吗?三思啊! 删了可就没了!')" th:if="${session.user}">删除</a>
</div>
</div>
<!--子集留言-->
<div class="comments" th:if="${#arrays.length(message.replyMessages)}>0">
<div class="comment" th:each="reply : ${message.replyMessages}">
<a class="avatar">
<img src="../static/images/me.jpg" 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.adminMessage}">栈主
</div>
<span th:text="|@ ${reply.parentNickname}|" class="m-teal">@ 小白</span>
</a>
<div class="metadata">
<span class="date"
th:text="${#dates.format(reply.createTime,'yyyy-MM-dd HH:mm')}">今天下午 5:42</span>
</div>
<div class="" aria-hidden="true" style="">
<span th:text="${reply.windowName}"
style="background-color: #f0eff3; color: #888888; border-radius: 10%"> Windows10 </span>
<span th:text="${reply.browserName}"
style="background-color: #f0eff3; color: #888888; border-radius: 10%"> Chrome 97.0.4692.71 </span>
</div>
<div class="text" th:text="${reply.content}">太赞了!</div>
<div class="actions">
<a class="reply" data-messageid="1" data-messagenickname="Matt"
th:attr="data-messageid=${reply.id},data-messagenickname=${reply.nickname}"
onclick="reply(this)">回复</a>
<a class="delete" href="#"
th:href="@{/messages/{id}/delete(id=${reply.id})}"
onclick="return confirm('确定要删除该评论吗?三思啊! 删了可就没了!')"
th:if="${session.user}">删除</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
打开页面后,先初始化加载留言的内容。
// 初始化加载
$(function () {
$("#message-container").load(/*[[@{/messagecomment}]]*/"messagecomment");
});
调用后端,查询到留言
//查询留言
@GetMapping("/messagecomment")
public String messages(Model model) {
List<Message> messages = messageService.listMessage();
model.addAttribute("messages", messages);
return "message::messageList";
}
回复功能的js:
先从html中获取要回复留言的id和该发送者的name,作为parentid和parentname。
然后在要填写的留言内容上,添加“@+parentname”;并且记录parentid的id。
之后就和普通的发送留言一样,点击发布调取后端。
function reply(obj) {
var messageId = $(obj).data('messageid');
var messageNickname = $(obj).data('messagenickname');
$("[name='content']").attr("placeholder", "@" + messageNickname).focus();
$("[name='parentMessage.id']").val(messageId);
$(window).scrollTo(0, 500);
}
重点:多层留言功能实现
首先清楚我们向后端发送的数据种类,这里的parentmessage.id是看是否有父级,要是有父级的话,id为父级留言id,无则为null
然后主要是后端对多层留言的处理:
这里首先判断父级id,然后set进值。之后的service方法直接调用了message。
在service中,直接把message的信息存入数据库中即可。
@Override
public int saveMessage(Message message) {
message.setCreateTime(new Date());
return messageDao.saveMessage(message);
}
到这里都没有问题,就是简单的获取数据,存放在数据库中。
主要是对留言的展示处理:
调用后端查询留言,这里的messages用List存储,调用messageservice中的listmessage()方法。
//查询留言
@GetMapping("/messagecomment")
public String messages(Model model) {
List<Message> messages = messageService.listMessage();
model.addAttribute("messages", messages);
return "message::messageList";
}
这里的messageservice中的三个方法很重要,listMessage、combineChildren、recursively。分别的功能是查询留言、查询出子留言、迭代查询出子集回复。
我们先看listMessage()方法:
先查询出所有parentid为-1的的message,这些message为父留言。
分别对每个parentid=-1的留言进行处理,先获得该message的id和name。
然后查询以该留言的id作为parentid的留言,查询的子级留言为childmessage。
然后调用combineChildren(childMessages, parentNickname1)方法。
把存储该留言的所有子留言、多层留言的tempReplys存入ReplyMessages中,作为父级留言下面的所有留言。
private List<Message> tempReplys = new ArrayList<>();
@Override
public List<Message> listMessage() {
//查询出父节点
List<Message> messages = messageDao.findByParentIdNull(Long.parseLong("-1"));
for(Message message : messages){
Long id = message.getId();
String parentNickname1 = message.getNickname();
List<Message> childMessages = messageDao.findByParentIdNotNull(id);
//查询出子留言
combineChildren(childMessages, parentNickname1);
message.setReplyMessages(tempReplys);
tempReplys = new ArrayList<>();
}
return messages;
}
我们再看combineChildren方法:
传入了childmessage和parentnickname,对childmessage>0的进行处理。
先获取第一层子留言的id和name,然后存入tempReplys中。
然后调用recursively(childId, parentNickname)方法。
private void combineChildren(List<Message> childMessages, String parentNickname1) {
//判断是否有一级子回复
if(childMessages.size() > 0){
//循环找出子留言的id
for(Message childMessage : childMessages){
String parentNickname = childMessage.getNickname();
childMessage.setParentNickname(parentNickname1);
tempReplys.add(childMessage);
Long childId = childMessage.getId();
//查询二级以及所有子集回复
recursively(childId, parentNickname);
}
}
}
再看trecursively()方法:
传入childid和childname,然后查询所有parentid为childid的留言。
查询该留言的id和name,存入tempReplys中。
然后再调用trecursively()方法,迭代查询以该留言作为父类留言的留言。
private void recursively(Long childId, String parentNickname1) {
//根据子一级留言的id找到子二级留言
List<Message> replayMessages = messageDao.findByReplayId(childId);
if(replayMessages.size() > 0){
for(Message replayMessage : replayMessages){
String parentNickname = replayMessage.getNickname();
replayMessage.setParentNickname(parentNickname1);
Long replayId = replayMessage.getId();
tempReplys.add(replayMessage);
//循环迭代找出子集回复
recursively(replayId,parentNickname);
}
}
}
然后返回list类型的messages,通过model.addAttribute()发送给前端。
然后是留言的展示,首先是一级留言:(这里的messages中的每一个message都是一级留言)
然后是重要的多级留言展示: