效果展示
步骤
前端
思路
评论输入的显示与隐藏
点击dom时记录该评论的id,v-show控制展示那条评论的 回复输入框界面 ,第一次点为回复,第二次点则显示取消回复,点击后currReply置空
由于store没有响应式,这里使用计算属性检测变化
一,二级评论实现
一级正常显示,二级复制一级的dom,然后加个padding-left 实现缩进的区别,层次区别
二级评论遍历一级评论的子评论数组并展示
DOM结构
<div>
<!--一级评论-->
<div class="commentBox">
<div class="commentAvatar">
<a :href="comment.url" target="_blank" v-show="comment.url&&comment.email">
<img :src="`http://q1.qlogo.cn/g?b=qq&nk=${comment.email.split(`@`)[0]}&s=100`" loading="lazy">
</a>
<img src="../../assets/logo.png" v-show="!comment.email" loading="lazy">
</div>
<div class="commentBody">
<a class="showNickName" :href="comment.url" target="_blank">{{ comment.nickname }}</a>
<div class="showComment themeText">{{ comment.commentContent }}</div>
<div class="commentFooter">
<div class="commentTime">{{ getTime(comment.createTime) }}</div>
<div class="thumbUp"><i class="fa fa-thumbs-o-up"></i> 666</div>
<i class="fa fa-thumbs-o-down thumbDown"></i>
<!--当用户点击回复时,如果是第一次点,则显示,如果是第二次,则隐藏-->
<button @click="showReplyView(comment.id,comment.nickname)">
{{ this.$store.state.currReply === comment.id ? '取消回复' : '回复' }}
</button>
</div>
</div>
</div>
<!--评论回复输入界面-->
<!--点击回复时记录了当前回复的id,评论比对是否等于自身id,是的话则显示-->
<div class="replyBox" v-show="this.$store.state.currReply===comment.id">
<!--<div class="replyAvatar"><img src="../assets/logo.png" alt=""></div>-->
<div class="replyMain">
<CommentInfoInput/>
</div>
</div>
<!--二级评论-->
<div v-show="comment.children&&comment.children.length>0">
<div class="commentBox" v-for="subComment in comment.children" :key="subComment.id"
style="padding-left: 50px">
<div class="commentAvatar">
<a :href="subComment.url" target="_blank" v-show="subComment.url&&subComment.email">
<!--<img :src="`http://q1.qlogo.cn/g?b=qq&nk=${subComment.email.split(`@`)[0]}&s=100`" loading="lazy">-->
</a>
<img src="../../assets/logo.png" v-show="!subComment.email" loading="lazy">
</div>
<div class="commentBody">
<div class="showComment themeText" style="margin-top: 0">
<a
class="showNickName" :href="subComment.url" target="_blank" style="margin-right: 5px">{{
subComment.nickname
}}
</a>
<a style="color: #00b4d8 ;cursor: pointer" v-if="subComment.replyname!==subComment.nickname">
<!--当回复人为一级评论人的名字时不需要显示,自己脑部b站评论效果吧,或者调一下看下不解释了-->
<span v-show="subComment.replyname!==comment.nickname">回复@{{ subComment.replyname }}</span>
</a>
<!--文章内容区域-->
{{ subComment.commentContent }}
</div>
<div class="commentFooter">
<div class="commentTime">{{ getTime(subComment.createTime) }}</div>
<div class="thumbUp"><i class="fa fa-thumbs-o-up"></i> 666</div>
<i class="fa fa-thumbs-o-down thumbDown"></i>
<!--当用户点击回复时,如果针对当前回复对象显示 取消回复选项-->
<button @click="showReplyView(subComment.id,subComment.nickname)">
{{ currReply === subComment.id ? '取消回复' : '回复' }}
</button>
</div>
</div>
<!--子评论回复输入界面-->
<div class="replyBox" v-show="currReply===subComment.id">
<!--<div class="replyAvatar"><img src="../assets/logo.png" alt=""></div>-->
<div class="replyMain">
<CommentInfoInput/>
</div>
</div>
</div>
</div>
</div>
Method方法 computed数据
name: "Comment",
components: {CommentInfoInput},
props: ['comment'],
// 计算属性设置值时改变,且获取值时获取,避免v-for里读取不到this.$store问题
// 动态变化
computed: {
currReply: {
get() {
return this.$store.state.currReply
},
set(commentId) {
this.$store.commit('changeCurrReply', commentId)
}
}
},
methods: {
showReplyView(commentId, nickName) {
if (commentId === this.$store.state.currReply) {
this.$store.state.currReply = null
return
}
this.currReply = commentId
this.$store.state.currReplyName = nickName
},
}
后端
思路
前面的分页查询不多概述
由于list列表只能通过下标index,0,1,2,3获取对应的评论,我们这里用map给index映射一个id值,就可以根据评论id来获取list对应的评论
遍历所有评论,如果没有父评论说明是一级节点,直接return出去
如果有父评论说明是二级节点,找到其父节点,添加到其children数组
但是如果是这样的话,那我们给二级评论回复时,二级评论的父评论还是二级评论,这样就变成了3级节点
因此我们需要使用递归找到子评论的顶级父节点,添加进去children数组,这样一来,我们就可以保持只有父亲-儿子的关系,而不是爷爷-父亲-孙子-曾孙的关系
由于子评论通常以最早发送的在最上面,也就是升序排列,这里可以使用list身上的api,sort传入Comparator的静态方法comparing根据时间对比如果大的则交换位置,类似于冒泡排序,好像这里还可以传入一个匿名内部类自定义排列规则
最后我们只需要返回过滤出所有的一级评论返回即可,二级的已经被添加到一级评论里去了
@Slf4j
@RestController
@RequestMapping("/comment")
public class CommentController {
@Autowired
private CommentService commentService;
/**
* 查询评论列表
*/
@GetMapping("/selectList/{articleId}/{limit}")
public Result getPageList(
@PathVariable int articleId,
@PathVariable int limit
) {
System.out.println("页数" + limit);
Page<Comment> pageOne = new Page<Comment>(1, 999);
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getArticleId, articleId);
queryWrapper.orderByDesc(Comment::getCreateTime);
commentService.page(pageOne, queryWrapper);
// 获取所有评论,如果有遍历放到一个map,id=>index,
// 后续遍历map里的评论如果有父节点则判断父节点有无子数组 ‘
// 无-》开辟一个数组,存放所有子评论,有=》直接add
List<Comment> records = pageOne.getRecords();
HashMap<Integer, Integer> hm = new HashMap<>();
for (int index = 0; index < records.size(); index++) {
hm.put(records.get(index).getId(), index);
}
// 给所有子评论加工到父评论列表里
for (Comment comment : records) {
// 判断是否有父id评论
// System.out.println(comment);
if (findComment(comment.getPid()) == null) {
continue;
}
// System.out.println(comment.getPid());
// 如果有则获取该评论,加到其孩子队列里
Integer parentId = findParent(comment.getId());
Comment father = records.get(hm.get(parentId));
// 初始化列表
if (father.getChildren() == null) {
father.setChildren(new ArrayList<>());
}
// 在子评论列表头部添加新的子评论,达到二级回复升序排列,一级回复降序
// 给列表加值
father.getChildren().add(comment);
// 升序排序
// Collections.reverse(father.getChildren());
}
// 获取所有一级评论也就是没有pid的评论,因为二级已经被加到一级里了
records = records.stream()
.filter(p -> p.getPid() == null)
.collect(Collectors.toList());
// 2. 对每个顶级评论的子评论进行时间升序排序
for (Comment comment : records) {
List<Comment> children = comment.getChildren();
if (children != null && !children.isEmpty()) {
// 对子评论列表进行时间升序排序
Collections.sort(children, Comparator.comparing(Comment::getCreateTime));
}
}
pageOne.setRecords(records);
return Result.success(pageOne);
}
// 递归到最顶层的Pid对应的节点,也就是一级评论,如果没有父Id则为顶级,有的话继续调用自身函数查找
public Integer findParent(Integer id) {
Comment comment = findComment(id);
if (comment.getPid() == null) {
return comment.getId();
}
return findParent(comment.getPid());
}
// ID找comment
public Comment findComment(Integer id) {
Comment comment = commentService.getById(id);
return comment;
}
// 对子回复进行升序处理
/**
* 获取用户详细信息
*/
@GetMapping(value = "/{id}")
public Result getInfo(@PathVariable("id") Long id) {
return Result.success(commentService.getById(id));
}
别问为什么没有所有详细代码,思路很重要,有了思路其他敲着敲着就出来了,无非是样式不同dom结构不一样而已,那玩意自行diy即可