Vue+elementUI实现评论功能
参考链接 https://blog.csdn.net/zLanaDelRey/article/details/100997792
前端代码主要参考以上链接,效果类似,后端代码自己封装,构建评论数据
前端代码
可作为组件直接使用,需要传入knowlgIdParam文章id使用
<template> <div> <div v-clickoutside="hideReplyBtn" @click="inputFocus" class="my-reply"> <div class="reply-info"> <div tabindex="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-button class="reply-btn" size="medium" @click="sendComment" type="primary">发表评论el-button> div> div> <div v-for="(item,i) in comments" :key="i" class="author-title reply-father"> <div class="author-info"> <span class="author-commentUser">{{item.commentUser}}span> <span class="author-time">{{item.updatedDate}}span> div> <div class="icon-btn"> <span class="reply-span" @click="showReplyInput(i,item.commentUser,item.commentId)"> 回复 span> div> <div class="talk-box"> <p> <span class="reply">{{item.commentContent}}span> p> div> <div class="reply-box"> <div v-for="(reply,j) in item.reply" :key="j" class="author-title"> <div class="author-info"> <span class="author-commentUser">{{reply.commentUser}}span> <span class="author-time">{{reply.updatedDate}}span> div> <div class="icon-btn"> <span class="reply-span" @click="showReplyInput(i,reply.commentUser,reply.commentId)"> 回复span> div> <div class="talk-box"> <p> <span class="reply"><span class="reply-span-down">回复span> @{{reply.toCommentUser}}:span> <span class="reply">{{reply.commentContent}}span> p> div> <div class="reply-box"> div> div> div> <div v-show="_inputShow(i)" class="my-reply my-comment-reply"> <div class="reply-info"> <div tabindex="0" contenteditable="true" spellcheck="false" placeholder="输入评论..." @input="onDivInput($event)" class="reply-input reply-comment-input">div> div> <div class=" reply-btn-box"> <el-button class="reply-btn" size="medium" @click="sendCommentReply(i,j)" type="primary">发表评论 el-button> div> div> div> div> template> <script> import {axios} from 'utils/'; import {cbs, gbs} from 'config/'; 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: 'ArticleComment', data() { return { btnShow: false, index: '0', replyComment: '', myName: 'Lana Del Rey', toCommentUser: '', parentCommentId: -1, comments: [ { commentUser: '', commentId: '', commentContent: '', updatedDate: '', inputShow: false, reply: [ { commentUser: '', commentId: '', toCommentUser: '', parentCommentId: '', commentContent: '', updatedDate: '', inputShow: false } ] } ] } }, props: { // 接收父组件传值的变量 knowlgIdParam: { type: String, default: () => { return null } } }, directives: {clickoutside}, created() { // this.$route.query.knowlgId this.getCurrentUser() this.getCommentData() }, methods: { getCurrentUser(){ axios({ type: 'get', path: gbs.adminContext + '/knowlgComment/getCurrentUser', fn: res => { this.myName = res.data console.log(res.data,'res.data') }, errFn: res => { if (res.data != undefined) { that.$message.error('请求出错:' + res) } } }); }, getCommentData() { axios({ type: 'get', path: gbs.adminContext + '/knowlgComment/listMain', data: {knowlgId: this.knowlgIdParam}, fn: res => { this.comments = res.data console.log(res.data,'res.data') console.log(res.data.reply,'res.data.reply') }, errFn: res => { if (res.data != undefined) { that.$message.error('请求出错:' + res) } } }); }, saveComment(comment){ axios({ type: 'post', path: gbs.adminContext + '/knowlgComment/opeMain', data: comment, headers: {'Content-Type': 'application/json;charset=UTF-8'}, fn: res => { }, errFn: res => { if (res.data != undefined) { this.$message.error('请求出错:' + res) } } }); }, 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, commentUser, commentId) { this.comments[this.index].inputShow = false this.index = i this.comments[i].inputShow = true this.toCommentUser = commentUser this.parentCommentId = commentId }, _inputShow(i) { return this.comments[i].inputShow }, sendComment() { if (!this.replyComment) { this.$message({ showClose: true, type: 'warning', message: '评论不能为空' }) } else { let a = {} let input = document.getElementById('replyInput') a.commentUser = this.myName a.commentContent = this.replyComment a.knowlgId = this.knowlgIdParam this.comments.push(a) this.replyComment = '' input.innerHTML = '' this.saveComment(a) } }, sendCommentReply(i, j) { if (!this.replyComment) { this.$message({ showClose: true, type: 'warning', message: '评论不能为空' }) } else { let a = {} a.commentUser = this.myName a.toCommentUser = this.toCommentUser a.commentContent = this.replyComment a.parentCommentId = this.parentCommentId a.knowlgId = this.knowlgIdParam if(!this.comments[i].reply){ this.comments[i].reply = [] } this.comments[i].reply.push(a) this.replyComment = '' document.getElementsByClassName("reply-comment-input")[i].innerHTML = "" this.saveComment(a) } }, onDivInput: function (e) { this.replyComment = e.target.innerHTML; }, }, } script> <style lang="stylus" scoped> .my-reply padding 10px background-color #fafbfc .header-img display inline-block vertical-align top .reply-info display inline-block margin-left 5px width 90% @media screen and (max-width: 1200px) { width 80% } .reply-input min-height 20px line-height 22px padding 10px 10px color #ccc background-color #fff border-radius 5px &:empty:before content attr(placeholder) &:focus:before content none &:focus padding 8px 8px border 2px solid #409EFF box-shadow none outline none .reply-btn-box height 25px margin 10px 0 .reply-btn position relative float right margin-right 15px .my-comment-reply margin-left 50px .reply-input width flex .reply-span font-size 14px color #909399 .reply-span-down font-size 12px color #909399 .author-title:not(:last-child) border-bottom: 1px solid rgba(178, 186, 194, .3) .author-title padding 10px .header-img display inline-block vertical-align top .author-info display inline-block margin-left 5px width 60% height 40px line-height 20px > span display block cursor pointer overflow hidden white-space nowrap text-overflow ellipsis .author-commentUser font-size 14px font-weight bold .author-time font-size 12px color #909399 .icon-btn width 30% padding 0 !important float right @media screen and (max-width: 1200px) { width 20% padding 7px } > span cursor pointer .iconfont margin 0 5px .talk-box margin 0 50px > p margin 0 .reply font-size 14px .reply-box margin 10px 0 0 50px style>
后端主要代码
实体类
构建评论数据方法
/** * @description 构建两层评论数据 * @param [item] * @return java.util.List* */ @Override public ListbuildCommentTree(KnowlgCommentDomain item){ List reList = new ArrayList<>(); // 获取树查询需要的commentIds List listCI = selectCommentIds(item); // 根据knId获取列表,用于获取 评论被回复人 List listAll = itemDao.selectListByKnId(item); // 循环开始构建评论树,只需要两层 for(KnowlgCommentDomain k:listCI){ List reply = new ArrayList<>(); // 根据commentId进行递归查询 即单独获取一条需要组装的评论数据 List list = selectListByTree(k); // 将list转换为一条评论数据 null是顶层评论Id,其余为reply KnowlgCommentVO v = new KnowlgCommentVO(); for(KnowlgCommentDomain c:list){ if("null".equals(c.getParentCommentId())){ BeanUtils.copyProperties(c,v); v.setInputShow(false); v.setToCommentUser(getToCommentUser(c,listAll)); break; } } for(KnowlgCommentDomain c:list){ if(!("null".equals(c.getParentCommentId()))){ KnowlgCommentVO kc = new KnowlgCommentVO(); BeanUtils.copyProperties(c,kc); kc.setInputShow(false); kc.setToCommentUser(getToCommentUser(c,listAll)); reply.add(kc); } } v.setReply(reply); reList.add(v); } return reList; }/**
* @description 根据Id获取评论被回复人 * @param [comment, list] * @return java.lang.String **/ private String getToCommentUser(KnowlgCommentDomain comment,Listlist){ forif(comment.getParentCommentId().equals(k.getCommentId())){ return k.getCommentUser(); } } return null;
主要用到的sql
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.sinolife.knowledge.manage.dao.KnowlgCommentDao"> <resultMap type="com.sinolife.api.domain.manage.KnowlgCommentDomain" id="KnowlgCommentResult"> <result property="commentId" column="COMMENT_ID" jdbcType="VARCHAR" /> <result property="knowlgId" column="KNOWLG_ID" jdbcType="VARCHAR" /> <result property="commentUser" column="COMMENT_USER" jdbcType="VARCHAR" /> <result property="commentContent" column="COMMENT_CONTENT" jdbcType="VARCHAR" /> <result property="parentCommentId" column="PARENT_COMMENT_ID" jdbcType="VARCHAR" /> <result property="isValid" column="IS_VALID" jdbcType="VARCHAR" /> resultMap> <select id="selectListByTree" parameterType="com.sinolife.api.domain.manage.KnowlgCommentDomain"
resultMap="KnowlgCommentResult"> select * from ( select <include refid="selectKnowlgCommentVo"/> from KNOWLG_COMMENT START WITH COMMENT_ID = #{commentId} CONNECT BY PARENT_COMMENT_ID = PRIOR COMMENT_ID) <where> 1=1 and IS_VALID = 'Y' where> <if test="sortField != null and sortField != '' and direction != null and direction !=''"> order by ${sortField} ${direction} if> select> <select id="selectCommentIds" parameterType="com.sinolife.api.domain.manage.KnowlgCommentDomain"
resultMap="KnowlgCommentResult"> select COMMENT_ID from KNOWLG_COMMENT <where> 1=1 and KNOWLG_ID = #{knowlgId} and PARENT_COMMENT_ID = 'null' and IS_VALID = 'Y' where> <if test="sortField != null and sortField != '' and direction != null and direction !=''"> order by ${sortField} ${direction} if> select> mapper>