vue+elementUI+后端springboot多用户评论、回复、点赞

  • 演示
  • 数据库
  • 后端

评论、回复、点赞、取消点赞。
前端 vue+elementUI、vue脚手架vue-VLI、axios
后端 springboot、mybatis-plus(和其代码生成器)
数据库 MySQL
项目地址:https://gitee.com/panqiyi/comment-demo

演示

评论:

回复:

点赞、取消点赞(图标颜色变化):

示例图片:
周杰伦用户:显示周杰伦点赞过的评论变色

刘德华用户:显示刘德华点赞过的评论变色

前端页面vue
ComTest.vue

<template><div class="comment"><div><div v-clickoutside="hideReplyBtn" @click="inputFocus" class="my-reply"><el-avatar class="header-img" :size="40" :src="avatar"></el-avatar><div class="reply-info"><divtabindex="0"contenteditable="true"id="replyInput"spellcheck="false"placeholder="输入评论..."class="reply-input"@focus="showReplyBtn"@input="onDivInput($event)"></div></div><div class="reply-btn-box" v-show="btnShow"><el-buttonclass="reply-btn"size="medium"@click="sendComment"type="primary">发表评论</el-button></div></div><divv-for="(item, i) in comments":key="i"class="author-title reply-father"><el-avatar class="header-img" :size="40" :src="item.avatar"></el-avatar><div class="author-info"><span class="author-name">{{ item.username }}</span><span class="author-time">{{ item.time }}</span></div><div class="icon-btn"><span @click="showReplyInput(i, item.username, item.id)"><i class="iconfont el-icon-s-comment"></i>{{ item.commentNum }}</span><iv-if="item.likeFlag"class="iconfont el-icon-caret-top likeIcon"@click="like(item.id)"></i><iv-elseclass="iconfont el-icon-caret-top"@click="like(item.id)"></i>{{ item.like }}</div><div class="talk-box"><p><span class="reply"> {{ item.comment }}</span></p></div><div class="reply-box"><div v-for="(reply, j) in item.reply" :key="j" class="author-title"><el-avatarclass="header-img":size="40":src="reply.avatar"></el-avatar><div class="author-info"><span class="author-name">{{ reply.username }}</span><span class="author-time">{{ reply.time }}</span></div><div class="icon-btn"><span @click="showReplyInput(i, reply.username, reply.id)"><i class="iconfont el-icon-s-comment"></i>{{ reply.commentNum }}</span><iv-if="reply.likeFlag"class="iconfont el-icon-caret-top likeIcon"@click="like(reply.id)"></i><iv-elseclass="iconfont el-icon-caret-top"@click="like(reply.id)"></i>{{ reply.like }}</div><div class="talk-box"><p>回复<span> @{{ reply.parentName }}: </span><span class="reply"> {{ reply.comment }}</span></p></div><div class="reply-box"></div></div></div><div v-show="_inputShow(i)" class="my-reply my-comment-reply"><el-avatar class="header-img" :size="40" :src="avatar"></el-avatar><div class="reply-info"><divtabindex="0"contenteditable="true"spellcheck="false":placeholder="placeholder"@input="onDivInput($event)"class="reply-input reply-comment-input"></div></div><div class="reply-btn-box"><el-buttonclass="reply-btn"size="medium"@click="sendCommentReply(i)"type="primary">发表评论</el-button></div></div></div></div></div>
</template><script>
import axios from "axios";
const clickoutside = {// 初始化指令bind(el, binding, vnode) {function documentHandler(e) {// 这里判断点击的元素是否是本身,是本身,则返回if (el.contains(e.target)) {return false;}// 判断指令中是否绑定了函数if (binding.expression) {// 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法binding.value(e);}}// 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听el.vueClickOutside = documentHandler;document.addEventListener("click", documentHandler);},update() {},unbind(el, binding) {// 解除事件监听document.removeEventListener("click", el.vueClickOutside);delete el.vueClickOutside;},
};
export default {name: "ComTest", // 组件名称data() {return {placeholder: "", // 回复者名称btnShow: false,index: "0",replyComment: "", // 评论输入内容username: "jaychou", // 登录的用户名avatar: "https://s1.ax1x.com/2022/06/10/Xc9lUf.png", // 登录用户头像userId: "", // 登录用户idparentName: "", // 回复的对象(父评论)用户名parentId: 0, // 父iditemId: "6666", // 文章等等idcomments: [{// username:'Lana Del Rey',// id:19870621,// avatar:'https://ae01.alicdn.com/kf/Hdd856ae4c81545d2b51fa0c209f7aa28Z.jpg',// parentName:'', // 父评论名// parentId:'', // 父评论id// comment:'我发布一张新专辑Norman Fucking Rockwell,大家快来听啊',// time:'2019年9月16日 18:43',// commentNum:2, // 该评论的回复条数// like:15, // 点赞// likeFlag=true, // 点赞图标状态颜色变化// inputShow:false, // 输入框隐藏// reply:[//     {//         username:'Taylor Swift',//         id:19891221,//         avatar:'https://ae01.alicdn.com/kf/H94c78935ffa64e7e977544d19ecebf06L.jpg',//         parentName:'Lana Del Rey',//         parentId:19870621,//         comment:'我很喜欢你的新专辑!!',//         time:'2019年9月16日 18:43',//         commentNum:1,//         like:15,//         likeFlag=true, // 点赞图标状态颜色变化//         inputShow:false//     }//]},],};},directives: { clickoutside },created() {// 注意this// 获取用户登录信息this.getLoginUser();},methods: {myrefresh() {//console.log(this.userId)// alert(this.userId+"nmnm")//let url = `/api1/ts/tcomment/commentList/${this.itemId}/${this.userId}`//查询评论信息列表展示, 文章id/用户id axios.get("http://localhost:8080/ts/tcomment/commentList/"+this.itemId+"/"+this.userId).then((resp) => {if (resp.data.success) {let list = resp.data.data;console.log(list.comments);this.comments = list.comments;}});},getLoginUser() {// 后台session获取登录信息axios.get("http://localhost:8080/ts/tuser/getLoginUser").then((resp) => {if (resp.data.success) {let user = resp.data.data.user;console.log(user);if (user == null) {this.avatar = "https://s1.ax1x.com/2022/06/10/Xc9lUf.png"; // 没登陆时,默认头像地址} else {this.username = user.username;this.userId = user.id;console.log(this.userId)//alert(this.userId)this.avatar = user.avatar;}}}).finally(()=>{this.myrefresh() // 刷新});},inputFocus() {var replyInput = document.getElementById("replyInput");replyInput.style.padding = "8px 8px";replyInput.style.border = "2px solid #409EFF";replyInput.focus();},showReplyBtn() {this.btnShow = true;},hideReplyBtn() {this.btnShow = false;replyInput.style.padding = "10px";replyInput.style.border = "none";},showReplyInput(i, name, id) {this.comments[this.index].inputShow = false;this.index = i;this.comments[i].inputShow = true;this.parentName = name;this.parentId = id;this.placeholder = "回复 @" + name;//alert(i)},_inputShow(i) {return this.comments[i].inputShow;},sendComment() {// 父评论if (!this.replyComment) {this.$message({showClose: true,type: "warning",message: "评论不能为空",});} else {let a = {};a.userId = this.userId;a.username = this.username;a.content = this.replyComment;a.avatar = this.avatar;a.itemId = 6666;a.parentId = "0";a.parentName = "";axios.post("http://localhost:8080/ts/tcomment/addComment", a).then((response) => {if (response.data.success) {console.log(response.data);this.$message.success("评论成功!");} else {this.$message.error("评论失败,请稍后重试!");}}).finally(() => {this.myrefresh();});//document.getElementById("replyInput").innerHTML = "";this.replyComment = "";}},sendCommentReply(i) {// 子回复提交if (!this.replyComment) {this.$message({showClose: true,type: "warning",message: "评论不能为空",});} else {// 组装请求数据let a = {};a.userId = this.userId;a.username = this.username;a.content = this.replyComment;a.avatar = this.avatar;a.itemId = 6666;a.parentId = this.parentId;a.parentName = this.parentName;//axios.post("http://localhost:8080/ts/tcomment/addComment", a).then((response) => {if (response.data.success) {this.$message.success("回复成功!");} else {this.$message.error("回复失败,请稍后重试!");}}).finally(() => {this.myrefresh();});this.replyComment = "";document.getElementsByClassName("reply-comment-input")[i].innerHTML ="";}},onDivInput: function (e) {this.replyComment = e.target.innerHTML;},like(id) {//点赞  评论id// alert(id)//组装数据let a = {commentId: id,userId: this.userId,};axios.post("http://localhost:8080/ts/tlike/likeControl", a).then((resp) => {//this.$message.success("成功!!")this.myrefresh();});},},
};
</script><style>
.comment {width: 1000px;margin: 0 auto;font-family: PingFang SC, HarmonyOS_Regular, Helvetica Neue, Microsoft YaHei,sans-serif;
}
.my-reply {padding: 10px;background-color: #fafbfc;
}
.my-reply .header-img {display: inline-block;vertical-align: top;
}
.my-reply .reply-info {display: inline-block;margin-left: 5px;width: 90%;
}
@media screen and (max-width: 1200px) {.my-reply .reply-info {width: 80%;}
}
.my-reply .reply-info .reply-input {min-height: 20px;line-height: 22px;padding: 10px 10px;color: #ccc;background-color: #fff;border-radius: 5px;
}
.my-reply .reply-info .reply-input:empty:before {content: attr(placeholder);
}
.my-reply .reply-info .reply-input:focus:before {content: none;
}
.my-reply .reply-info .reply-input:focus {padding: 8px 8px;border: 2px solid #409eff;box-shadow: none;outline: none;
}
/* .reply-info>div .reply-input:focus{border: 2px solid #409EFF;
} */
.my-reply .reply-btn-box {height: 25px;margin: 10px 0;
}
.my-reply .reply-btn-box .reply-btn {position: relative;float: right;margin-right: 15px;
}
.my-comment-reply {margin-left: 50px;
}
.my-comment-reply .reply-input {width: flex;
}
.author-title:not(:last-child) {border-bottom: 1px solid rgba(178, 186, 194, 0.3);
}
.author-title {padding: 10px;
}
.author-title .header-img {display: inline-block;vertical-align: top;
}
.author-title .author-info {display: inline-block;margin-left: 5px;width: 60%;height: 40px;line-height: 20px;
}
.author-title .author-info > span {display: block;cursor: pointer;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;
}
.author-title .author-info .author-name {color: #303133;font-size: 18px;font-weight: 500;
}
.reply-box .talk-box {color: #606266;
}
.reply-box .talk-box span {color: #6298ce;
}
.author-title .author-info .author-time {font-size: 14px;
}
.author-time {color: #606266;
}
.author-title .icon-btn {width: 30%;padding: 0 !important;float: right;
}
@media screen and (max-width: 1200px) {.author-title .icon-btn {width: 20%;padding: 7px;}
}
.author-title .icon-btn > span {cursor: pointer;
}
.author-title .icon-btn .iconfont {margin: 0 5px;
}
.author-title .talk-box {margin: 0 50px;
}
.author-title .talk-box > p {margin: 0;
}
.author-title .talk-box .reply {font-size: 16px;color: #606266;
}.author-title .reply-box {margin: 10px 0 0 50px;background-color: #efefef;
}
/* 点赞图标颜色 */
.likeIcon {color: #40a0ff;
}
</style>

数据库

评论表:

用户表:

点赞表:

后端

核心业务是 取出所有评论 并递归组装为只有二层评论:顶级 >子级(包含子子孙孙级),还有就是点赞业务统计点赞数并判断该登录用户是否对评论点赞,点赞要将图标变色,同一个用户点一次+1,再点-1。

部分代码:TCommentVOServiceImpl

/*** <p>*  评论服务实现类* </p>** @author panqiyi* @since 2022-06-05*/
@Service
public class TCommentVOServiceImpl extends ServiceImpl<TCommentMapper, TComment> implements TCommentService {// 保存评论@Overridepublic Boolean saveComment(TComment TComment) {boolean save = save(TComment);return save;}// 返回 评论列表@Overridepublic List<CommentVo> getCommentList(String itemId) {// 1、获取id=itemId文章下的 所有评论QueryWrapper<TComment> allwrapper = new QueryWrapper<>();allwrapper.eq("item_id",itemId);List<TComment> allList = list(allwrapper); // 所有评论集合// 遍历所有评论集合,复制到 评论视图返回类ArrayList<CommentVo> commentVos = new ArrayList<>(); //评论视图返回类 集合for (TComment tComment : allList) {CommentVo commentVo = new CommentVo();BeanUtils.copyProperties(tComment,commentVo);commentVo.setTime(tComment.getTCreate()); // 时间commentVo.setComment(tComment.getContent()); // 内容commentVos.add(commentVo);}//        for (CommentVo commentVo : commentVos) {//            System.out.println(commentVo);
//        }
//
//        System.out.println("..................");ArrayList<CommentVo> com = new ArrayList<>(); // 最终集合HashMap<String, CommentVo> map = new HashMap<>();// 2、com集合存储顶级评论for (CommentVo tComment : commentVos) { // 遍历每一条评论if ("0".equals(tComment.getParentId())){// 顶级评论com.add(tComment);}map.put(tComment.getId(),tComment); // 所有评论  id:评论对象}//  3、com集合内顶级评论 存储所有包含的子级评论for (CommentVo tComment : commentVos) { // 遍历每一条评论if (!"0".equals(tComment.getParentId())){ // 非顶级评论,既第一子级、第二子级等等评论// 获取所有父级评论 (如 a->b, b->c, c->d, d="0")CommentVo parent = map.get(tComment.getParentId());if (parent == null){continue; // 跳出本次循环}if (parent.getId().equals(tComment.getId())){continue;}parent.getReply().add(tComment); // 所有父级存储子级 (b(reply[b]), c(replay[b(reply[a])]) ....)}}/*System.out.println("=================");for (CommentVo tComment : com) {System.out.println(tComment);}*/// 结果是 顶级包含子级,子级包含孙级....,层次嵌套,// 但是我们想返回的是 父级>子级(包含所有子子孙孙) 两层// 组装好的评论数据List<CommentVo> formatList = getFormat(com);return formatList;}// 存储 新组装好的 子级(子子孙孙) 数据集合ArrayList<CommentVo> newReplys = new ArrayList<>();/*** 重新组装评论集合,使其只有两层 顶级评论>第一子级评论(包含子级和子子孙孙)* @param com* @return*/public List<CommentVo> getFormat(List<CommentVo> com){for (CommentVo tComment : com) { // 遍历顶点集合// 获取顶点 子级集合List<CommentVo> replys = tComment.getReply();for (CommentVo comment : replys) { // 遍历第一级(子级)回复集合  ( for(:) 只有集合元素>0才会进入)newReplys.add(comment); // 存储第一级子评论recursively(comment); // 递归子级评论}tComment.setReply(newReplys); // 重新设置子级(包含子子孙孙)tComment.setCommentNum(newReplys.size()); // 低级评论回复数// 新建(或清除)组装集合,避免下一轮顶点集合组装有数据newReplys = new ArrayList<>();}return com;}/*** 递归子级评论,返回顶级评论下 所有子代 都放到第一子级评论中* @param comment*/private void recursively(CommentVo comment){List<CommentVo> replys = comment.getReply(); // 第二子级集合if (replys.size()>0){ // 存在评论for (CommentVo reply : replys) {newReplys.add(reply); // 存储if (reply.getReply().size()>0){recursively(reply); // 递归}}}}}

建议直接上面我git链接弄下来看完整的。

vue+elementUI+后端springboot多用户评论、回复、点赞相关推荐

  1. springboot+vue+elementUI 基于Springboot的智慧养老平台#毕业设计

    随着社会的发展我国的人口老龄化严重,为了让这些在年前是给社会做出过贡献的老人老有所依,老有所养,度过一个安详的晚年,很多地方都实现了智慧养老,为此我们通过springboot+vue+elementU ...

  2. 基于springboot的评论,点赞模块

    关注作者及转自https://blog.csdn.net/qq_43308337/article/details/104753640 项目地址:点击访问 欢迎各位fork,star 1. 分析阶段 1 ...

  3. 基于SpringBoot +Vue+ ElementUI 开发的多用户博客管理平台,就是这么简单!

    想着刚刚渡过的国庆假期,想到今年的法定假期已经全没了,心里有一股蛋蛋的忧桑,不过马上要周末了,TJ君又觉得精神振奋! 既然周末了,那就该搞点轻松点的东西快乐下,TJ君一直告诫那些晚辈,工作就是快乐,这 ...

  4. vue+element-ui+axios+springboot实现文件上传下载

    前端技术:vue,element-ui,axios 后台技术:springboot 本篇博客只给出关键的代码,提供思路,完全的涉及保密不方便提供 一,上传: 上传element组件代码,支持多文件,拖 ...

  5. java基于ssm+vue+elementUI在线影评电影评论投票系统

    MySQL是一个大型的关系型数据库,MySQL数据库是专门应用在Windows系列操作系统中的数据库平台.具有强大的商业智能和安全可靠的数据存储功能,是构建大型企业级应用程序必不必备的数据库平台之一. ...

  6. 若依管理系统前后端分离版基于ElementUI和SpringBoot怎样实现Excel导入和导出

    场景 使用若依前后端分离版实现Excel的导入和导出. 前端:Vue+ElementUI 后端:SpringBoot+POI+Mysql 注: 博客: https://blog.csdn.net/ba ...

  7. 基于SSM+SpringBoot+Vue前后端分离的高校大学生毕业设计管理系统

    大家好,很高兴和大家分享源码.不管是什么样的需求.都希望各位计算机专业的同学们有一个提高. 大家可以通过常用的搜索引擎,以百度为例,搜索 源码乐园 code51 ,然后再次搜索 自己想要的即可.更多的 ...

  8. php跨域curd,SpringBoot+Vue前后端分离(CURD)Demo

    我发现我好久没有更新了,写一篇证明我还活着. 既然是前后端分离的话,肯定是要有前端和后端的. 这里前端我使用的Vue+ElementUI,后端采用的是SpringBoot+Mybatis+Swagge ...

  9. 基于SpringBoot+SpringCloud+Vue前后端分离项目实战 --开篇

    本文目录 前言 做项目的三大好处 强强联手(天狗组合) 专栏作者简介 专栏的优势 后端规划 1. SpringBoot 和 SpringCloud 的选择 2. Mybatis 和 MybatisPl ...

最新文章

  1. c语言程序设计分段定时器,单片机C语言编程定时器的几种表达方式
  2. 2022博士后,新加坡国立大学 Xinchao Wang 研究组
  3. 人工智能AI-机器视觉CV-数据挖掘DM-机器学习ML-神经网络-[资料集合贴]
  4. DL之Encoder-Decoder:Encoder-Decoder结构的相关论文、设计思路、关键步骤等配图集合之详细攻略
  5. java面试题大合集(开发者必看二)
  6. 简单调试 Bash 脚本
  7. HDU 5730 Shell Necklace(生成函数 多项式求逆)
  8. LeetCode 49. 字母异位词分组(哈希)
  9. 机器学习真的可以起作用吗?(1)
  10. yarn当中各个主要组件的作用及调度器
  11. L3-014 周游世界 (30分)
  12. MMKV_mmkv之基本介绍
  13. 易语言窗口c_句柄取进程名,易语言进程id取窗口句柄
  14. 几种免杀转储lsass进程的技巧
  15. gc cr block lost
  16. PHPStrom 快捷键
  17. 独家解读 | 基于优化的对抗攻击:CW攻击的原理详解与代码解读
  18. 计算机高级筛选在哪找,excel表格数据高级筛选在哪里-EXCEL高级筛选
  19. patronictl
  20. python -m详解

热门文章

  1. iOS keyChain 研究
  2. 查询与退订中国移动短信服务
  3. 电商项目 Java还是Django_Django电商平台Saleor搭建初体验
  4. 小米路由器3有信号无网络连接到服务器,小米路由器3上不了网(不能上网)怎么办?...
  5. 下载网易云音乐的MV
  6. 洛谷 1144 最短路计数 bfs
  7. 带有源代码的2020年20种最佳HTML5游戏模板
  8. 分布式数据库之TiDB
  9. 2019年CVTE实习心得
  10. 字节跳动2019春招后端开发工程师-笔试题解析