效果展示
功能设计
我们需要达成的功能点如下:
- 当用户未登陆(游客状态)时,评论区显示未登陆的提示,并且当用户点击发表评论、回复或点赞按钮时,会自动跳到登陆页面。
- 当用户已登陆时,发表评论后时时更新评论区域,局部刷新加入新增评论。
- 当用户点击回复时,在对应用户评论下显示回复框,当点击另外一个回复按钮时,当前回复框消失,而另一用户评论的回复框显示。
关于样式
前端样式部分建立在 amazeUI
基础上,如使用请先引入相关CSS与JS
CSS与JS的引入我使用的是 BootCDN
作为CDN加速:
<link href="https://cdn.bootcss.com/amazeui/2.7.2/css/amazeui.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/amazeui/2.7.2/js/amazeui.min.js"></script>
判断游客状态
同样使用 Thymeleaf
作为模板,首先在html标签中引入Thymeleaf
xmlns:th="http://www.thymeleaf.org"
之后利用Thymeleaf
中的判断语句来判断游客状态:
th:if="${#httpServletRequest.remoteUser}
在后台,通过安全认证后,利用HttpServletRequest
接口类中的getRemoteUser()
方法可以获取到用户名
而在前端同样可以利用 remoteUser
来判断用户是否登录,具体实现代码:
<div th:unless="${#httpServletRequest.remoteUser}" id="comment-init-text">哼,不<a href="/login">登录</a>就想对我指指点点,白嫖吗!</div>
<textarea th:if="${#httpServletRequest.remoteUser}" maxlength="500" id="comment-text" class="am-modal-prompt-input private-conversation" placeholder="畅所欲言叭,骚年d(`・∀・)b!"></textarea>
th:if
在Thymeleaf
语法中表示判断,如果 remoteUser
存在,则显示该标签,而th:unless
作用正好相反。
这样,如果用户处于游客状态(未登录),就只会显示第一个标签(div),使用户登录,如果用户已经登录,则显示第二个标签。
评论获取
这里我将评论区获取的ajax封装为一个函数,方便调用:
function getComment() {
$.ajax(
{
type:"post",
url:"/getArticleComment",
dataType:"json",
async: false,
data:{
articleId: $("#articleId").val()
},
success:function(data){
var commentStr = "";
if(data['msg']=="empty") {
$(".article-comment-nonpeople").css("display", "block");
$(".article-comment-list-ol").html(commentStr);
selfIdEnd = 0;
} else {
$(".article-comment-nonpeople").css("display", "none");
selfIdEnd = data['data']['comment'][0]['selfId'];
$.each(data['data']['comment'],function(index,obj){
var commentStrUnit = "";
commentStrUnit += '<li id="article-comment-list-li">\n' +
' <div>\n' +
' <div class="article-comment-list-left">\n' +
' <img class="article-comment-headimg" src="'+ obj['commentImg'] +'" />\n' +
' </div>\n' +
' <div class="article-comment-list-right">\n' +
' <span class="comment-right-name" id="comment-right-name-'+ obj['selfId'] +'">'+ obj['respondentName'] +'</span>\n' +
' <span class="comment-right-self">#'+ obj['selfId'] +'楼</span>\n' +
' <span class="comment-right-time">'+ obj['date'] +'</span>\n' +
' <div class="comment-right-text">\n' +
obj['content'] +
' </div>\n' +
' <div>\n' +
' <span><a class="comment-right-btn" id="comment-right-btn-'+ obj['selfId'] +'">回复</a></span>\n' +
' <span class="comment-reply-like-heart" id="comment-reply-like-heart-'+ obj['selfId'] +'"></span>\n' +
' <span class="comment-reply-like-text" id="comment-reply-like-text-'+ obj['selfId'] +'">'+ obj['like'] +'</span>\n' +
' </div>\n' +
'\n' +
' <div class="comment-reply-squ" id="comment-reply-squ-'+ obj['selfId'] +'">\n' +
' <textarea maxlength="500" class="am-modal-prompt-input private-conversation comment-reply comment-reply-text" id="comment-reply-text-'+ obj['selfId'] +'" placeholder="畅所欲言叭,骚年d(`・∀・)b!"></textarea>\n' +
' <div class="comment-reply-btn-div">\n' +
' <a class="comment-reply-btn-left" id="comment-reply-btn-left-'+ obj['selfId'] +'">取消回复</a>\n' +
' <a class="comment-reply-btn-right">发表评论</a>\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' <div class="article-comment-list-reply">\n' +
' <ol class="article-comment-list-ol" id="article-comment-list-ol-'+ obj['selfId'] +'">\n';
$.each(obj['reply'],function(index, obj2){
commentStrUnit +=
' <li>\n' +
' <div>\n' +
' <div class="article-comment-list-left">\n' +
' <img class="article-comment-headimg" src="'+ obj2['commentImg'] +'" />\n' +
' </div>\n' +
' <div class="article-comment-list-right">\n' +
' <span class="comment-right-name" id="comment-right-name-'+ obj['selfId'] + '-' + obj2['selfId'] +'">'+ obj2['respondentName'] +'</span>\n' +
' <span class="comment-right-time">'+ obj2['date'] +'</span>\n' +
' <div class="comment-right-text">\n' +
' <span class="comment-right-text-replyname">@'+ obj2['answerName'] +'</span>\n' +
' <span>'+ obj2['content'] +'</span>\n' +
' </div>\n' +
' <div>\n' +
' <span><a class="comment-right-btn" id="comment-right-btn-'+ obj['selfId'] + '-' + obj2['selfId'] +'">回复</a></span>\n' +
' <span class="comment-reply-like-heart" id="comment-reply-like-heart-'+ obj['selfId'] + '-' + obj2['selfId'] +'" style="margin-right: 410px !important;"></span>\n' +
' <span class="comment-reply-like-text" id="comment-reply-like-text-'+ obj['selfId'] + '-' + obj2['selfId'] +'">'+ obj2['like'] +'</span>\n' +
' </div>\n' +
' <div class="comment-reply-squ" id="comment-reply-squ-'+ obj['selfId'] + '-' + obj2['selfId'] +'">\n' +
' <textarea maxlength="500" class="am-modal-prompt-input private-conversation comment-reply comment-reply-text" id="comment-reply-text-'+ obj['selfId'] + '-' + obj2['selfId'] +'" placeholder="畅所欲言叭,骚年d(`・∀・)b!"></textarea>\n' +
' <div class="comment-reply-btn-div">\n' +
' <a class="comment-reply-btn-left" id="comment-reply-btn-left-'+ obj['selfId'] + '-' + obj2['selfId'] +'">取消回复</a>\n' +
' <a class="comment-reply-btn-right" style="float: right; margin-left: 0 !important;">发表评论</a>\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' <div></div>\n' +
' <div class="clear"></div> ' +
' </li>\n';
});
commentStrUnit += ' </ol>\n' +
' </div>\n' +
' <div class="clear"></div> ' +
' </li>';
commentStr += commentStrUnit;
});
$(".article-comment-list-ol").html(commentStr);
if(data['data']['likeList'] != "noLogin") {
$.each(data['data']['likeList'],function(index, obj) {
if(obj.split(",").length != 1) {
var likeHeart = "#comment-reply-like-heart-" + obj.split(",")[0] + "-" + obj.split(",")[1];
} else {
var likeHeart = "#comment-reply-like-heart-" + obj;
}
$(likeHeart).addClass("comment-reply-like-heart2");
});
}
}
},
error:function(){
console.log("评论显示失败!");
}
});
}
这样,只要在JS里直接调用getComment()
函数,即可显示评论区
评论插入
$("#comment-btn").click(function () {
if(loginCheck==1) {
window.location.href = "/login";
} else {
if($("#comment-text").val()=="") {
alert("嘿,你还麻油输入评论内容捏(=m=)!")
} else {
var selfIdFinal = parseInt(selfIdEnd) + 1;
$.ajax(
{
type:"post",
url:"/insertComment",
dataType:"json",
async: false,
data:{
articleId: $("#articleId").val(),
selfId: selfIdFinal,
answerName: "Seaguller",
content: $("#comment-text").val()
},
success:function(data){
if(data['msg']=="noLogin") {
window.location.href = "/login";
}else if(data['msg']=="success") {
$(".article-comment-nonpeople").css("display", "none");
getComment();
$("#comment-text").val("");
}
},
error:function(){
console.log("评论插入失败!");
}
});
}
}
});
评论插入其实就是和后端交互后重新调用getComment()
函数,这样重新获取评论区的数据实现了实时插入与评论区的动态刷新。
评论回复
评论回复主要在于两点:如何实现回复框在同一时间仅出现一个以及回复后评论区实时显示。
评论区实时显示这个并不难,我们上面评论插入中已经提到,因为把获取评论区内容的ajax请求封装为了一个函数,所以当我们点击回复按钮后,重新调用一下getComment()
函数,即可做到实时显示效果,代码如下:
/* 发表回复 */
$(".article-comment-list-ol").on("click", ".comment-reply-btn-right", function () {
var selfIdSplit = $(this).prev().attr("id").split("-");
if (selfIdSplit.length == 5) {
var selfIdOl = $("#article-comment-list-ol-" + selfIdSplit[selfIdSplit.length-1]);
var selfIdLiCount = selfIdOl.children("li").length + 1;
var selfIdFinal = selfIdSplit[selfIdSplit.length-1] + "," + selfIdLiCount;
var answerNameReplyFloor = selfIdSplit[selfIdSplit.length-1];
} else {
var selfIdOl = $("#article-comment-list-ol-" + selfIdSplit[selfIdSplit.length-2]);
var selfIdLiCount = selfIdOl.children("li").length + 1;
var selfIdFinal = selfIdSplit[selfIdSplit.length-2] + "," + selfIdLiCount;
var answerNameReplyFloor = selfIdSplit[selfIdSplit.length-2] + "-" + selfIdSplit[selfIdSplit.length-1];
}
if($("#comment-reply-text-" + answerNameReplyFloor).val()=="") {
alert("嘿,你还麻油输入回复内容捏(=m=)!");
console.log("reply: " + answerNameReplyFloor);
} else {
var answerNameReply = $("#comment-right-name-" + answerNameReplyFloor).html();
$.ajax(
{
type:"post",
url:"/insertComment",
dataType:"json",
async: false,
data:{
articleId: $("#articleId").val(),
selfId: selfIdFinal,
answerName: answerNameReply,
content: $("#comment-reply-text-" + answerNameReplyFloor).val()
},
success:function(data){
if(data['msg']=="noLogin") {
window.location.href = "/login";
}else if(data['msg']=="success") {
$("#comment-reply-text-" + answerNameReplyFloor).val("");
getComment();
}
},
error:function(){
alert("评论插入失败!");
}
});
}
});
上面以selfId
开头的变量是判断回复层数的,具体的方法主要是后端处理,这个会在后端讲到。
回复框设置
回复框设置的思想其实也是将回复框打开和关闭封装为一个函数:
/* 回复框框 */
function rep(num, flag) {
var strRepId = "#comment-reply-squ-" + num;
if(flag=="open") {
$(".comment-reply-squ").css("display", "none");
$(strRepId).css("display", "block");
} else if(flag=="close") {
$(strRepId).css("display", "none");
}
}
传参中num
表示楼数,而flag
表示显示/隐藏
每一个回复框预先都是设置为隐藏的,当调用这个函数时,传入对应回复的楼数和显示/关闭,即可控制对应的回复框。
(比如第七楼的第二个回复,他的ID就是comment-reply-squ-7-2
,如果要其显示,调用函数时只要传入7-2
和open
即可)
而因为每一个回复框的class
都是comment-reply-squ
所以,只要在open
的选择判断中将class
为comment-reply-squ
的display
属性设置为none
,即可隐藏所有的回复框,再将传入的指定回复框(用户所点击的)显示,即可做到同一时间仅有一个回复框显示。
注意点
- 如果把获取评论区封装为了函数并调用的话,动态添加的元素节点的交互请用
on
来进行,否则获取不到,详见文章 关于jQuery获取不到动态添加的元素节点的问题 - 请注意将其关联元素节点设置为相对定位或在页面实际高度改变后相应的改变元素节点的位置,因为回复框或者新插入的评论会撑大你的页面,如果你评论区下面还有东西的话就可能会造成页面布局混乱的问题。
- 注意设置登录状态检测,包括回复区点击回复时的登录状态检测
- 可以设置分页来避免评论过多的问题
- 注意设置评论/回复框(
textarea
)的字数限制避免评论字数过长导致页面布局混乱。 - 如果按照我的方法,请注意层级的设置,比如第7楼第2个回复,一定要是
7-2
,而不能是72
(我一开始中间就没加-
,后来导致第1楼第1个回复11
和第11楼一样嘞)