个人博客网站——留言及多级回复功能
通常情况下,在博客网站中,无论是文章下的评论还是单独的留言,都会有多级的回复功能。这里我们主要介绍多级回复功能的留言实现。
新增留言
在这里的新增留言,由留言板块、姓名板块和邮箱板块组成,其中隐藏获取了用户的电脑和浏览器内核版本。
<!--新增留言--> <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都是一级留言) 然后是重要的多级留言展示: