VUE Java评论回复功能
harveyST 人气:0背景
最近需要做一个多级评论的功能,技术路线:VUE(Element)+Java(SpringBoot)
效果
后台
SQL
Java
Controller
/** * 根据关联id获取评论信息 * @param relationId 关联id * @param type 类型 * @return: com.harvey.result.ResultSupport<java.lang.Object> * @date: 2020/12/10 14:37 */ @GetMapping("findList") public ResultSupport<Object> findList(@RequestParam("relationId") String relationId, @RequestParam("type") String type){ log.debug("接口[findList]的参数:relationId={}, type={}", relationId, type); ResultSupport<Object> result = ResultSupport.createMisResp(); try { List<Comment> commentList = commentService.findList(relationId, type); ResultSupportUtils.fillResultSupport(result, commentList); } catch (Exception e) { log.error("[findList]接口执行异常", e); ResultSupportUtils.fillError(result,"系统出现异常!"); } log.debug("接口[findList]的结果:result={}", result); return result; } /** * 保存评论 * @param comment 参数 * @return: com.tortoise.common.result.ResultSupport<java.lang.Object> * @date: 2020/12/10 14:37 */ @PostMapping("save") public ResultSupport<Object> save(@RequestBody Comment comment, HttpServletRequest request){ log.debug("接口[save]的参数:comment={}", comment); ResultSupport<Object> result = ResultSupport.createMisResp(); String token = request.getHeader("authorization"); if (StrUtil.isEmpty(token)) { result.setSuccess(false); result.setMessage("token无效!"); return result; } if (BeanUtil.isEmpty(comment)){ result.setSuccess(false); result.setMessage("参数无效!"); return result; } try { commentService.save(comment, token); } catch (Exception e) { log.error("[save]接口执行异常", e); ResultSupportUtils.fillError(result,"系统出现异常!"); } log.debug("接口[save]的结果:result={}", result); return result; }
Service
/** * 根据关联id获取绩效信息 */ public List<Comment> findList(String relationId, String type) { return commentMapper.findList(relationId, type); } /** * 保存评论 * @param comment 参数 * @param token 用户token * @return: * @date: 2020/12/10 14:37 */ @Transactional(rollbackFor = Exception.class) public void save(Comment comment, String token) { SysUser user = UserUtils.getUser(token); comment.preInsert(user.getId()); comment.setDelFlag("0"); commentMapper.save(comment); }
Mapper
/** * 根据关联id获取绩效信息 */ List<Comment> findList(@Param("relationId") String relationId, @Param("type") String type); /** * 根据id获取子评论内容 */ List<Comment> selectByParentId(@Param("parentId") String parentId); /** * 保存评论 */ void save(Comment comment);
XML
<sql id="commentColumns"> a.id AS "id", a.user_id AS "userId", u.name AS "userName", a.relation_id AS "relationId", a.type AS "type", a.reply_user_id AS "replyUserId", r.name AS "replyUserName", a.parent_id AS "parentId", a.content AS "content", u.photo AS "photo", a.del_flag AS "delFlag", a.create_by AS "createBy", a.create_date AS "createDate" </sql> <sql id="commentJoins"> LEFT JOIN sys_user u ON a.user_id = u.id AND u.del_flag = '0' LEFT JOIN sys_user r ON a.reply_user_id = r.id AND r.del_flag = '0' </sql> <!-- 保存评论 --> <insert id="save"> INSERT INTO comment( id, user_id, relation_id, type, reply_user_id, parent_id, content, del_flag, create_by, create_date ) VALUES ( #{id}, #{userId}, #{relationId}, #{type}, #{replyUserId}, #{parentId}, #{content}, #{delFlag}, #{createBy}, #{createDate} ) </insert> <resultMap id="commentResultMap" type="com.harvey.entity.Comment"> <id column="id" property="id" /> <result column="userId" property="userId" /> <result column="userName" property="userName" /> <result column="relationId" property="relationId" /> <result column="type" property="type" /> <result column="replyUserId" property="replyUserId" /> <result column="replyUserName" property="replyUserName" /> <result column="parentId" property="parentId" /> <result column="content" property="content" /> <collection property="children" column="{parentId=id}" select="selectByParentId" ofType="com.harvey.Comment"/> </resultMap> <!-- 根据关联id获取评论信息 --> <select id="findList" resultMap="commentResultMap"> SELECT <include refid="commentColumns"/> FROM comment a <include refid="commentJoins"/> WHERE a.relation_id = #{relationId} AND a.type = #{type} AND a.parent_id = '0' ORDER BY a.create_date DESC </select> <!-- 根据id获取子评论内容 --> <select id="selectByParentId" resultType="com.harvey.entity.Comment"> SELECT <include refid="commentColumns"/> FROM comment a <include refid="commentJoins"/> WHERE a.parent_id = #{parentId} ORDER BY a.create_date DESC </select>
前端
把评论抽成组件,方便其他模块引用
<!--评论模块--> <template> <el-drawer title="评论" :visible.sync="drawer" direction="rtl" :before-close="handleClose" :modal="false" :withHeader="true" @open="getCommentList" @close="close" size="320px" > <div class="container"> <div class="write-reply" @click="showCommentInputMajor()"> <div style="margin-top: 10px;"> <el-input class="gray-bg-input" v-model="majorComment" type="textarea" :rows="3" autofocus placeholder="写下你的评论" > </el-input> <div style="text-align: right;margin-top: 10px;"> <el-button @click="reset" size="small" round>重置</el-button> <el-button type="primary" round @click="commitMajorComment" size="small" >确定</el-button > </div> </div> </div> <div class="comment" v-for="item in commentList" :key="item.id"> <div class="info"> <img class="avatar" :src="fileUrl + item.photo" width="36" height="36" /> <div class="right"> <div class="name">{{ item.userName }}</div> <div class="date">{{ formatDate(item.createDate) }}</div> </div> </div> <div class="content">{{ item.content }}</div> <div class="control"> <!-- <span class="like" :class="{ active: item.isLike }" @click="likeClick(item)" > <i class="iconfont icon-like"></i> <span class="like-num">{{ item.likeNum > 0 ? item.likeNum + "人赞" : "赞" }}</span> </span> --> <span class="comment-reply" @click="showCommentInput(item, item, 'major')" > <i class="iconfont icon-iconcomment"></i> <span>回复</span> </span> </div> <div class="reply"> <div class="item" v-for="reply in item.children" :key="reply.id"> <div class="reply-content"> <span class="from-name">{{ reply.userName }}</span ><span>: </span> <span class="to-name">@{{ reply.replyUserName }}</span> <span>{{ reply.content }}</span> </div> <div class="reply-bottom"> <span>{{ formatDate(reply.createDate) }}</span> <span class="reply-text" @click="showCommentInput(item, reply, 'child')" > <i class="iconfont icon-iconcomment"></i> <span>回复</span> </span> </div> </div> <transition name="fade"> <div class="input-wrapper" v-if="showItemId === item.id"> <el-tag type="info" effect="dark" v-if="inputLabel != undefined && inputLabel" >{{ inputLabel }}</el-tag > <el-input class="gray-bg-input" v-model="inputComment" type="textarea" :rows="3" autofocus placeholder="写下你的评论" > </el-input> <div class="btn-control"> <el-button @click="cancel" size="small" round>取消</el-button> <el-button type="primary" round @click="commitComment" size="small" >确定</el-button > </div> </div> </transition> </div> </div> </div> </el-drawer> </template> <script> import * as commentApi from "@/api/comment-api"; import { DateUtil } from "@/utils/DateUtils"; import { UserUtil } from "@/utils/UserUtils"; import "@/assets/css/iconfont/iconfont.css"; export default { props: { drawer: { type: Boolean, required: true, default: false }, relationId: { type: String, required: true, default: "" }, commentType: { type: String, required: true, default: "" } }, data() { return { fileUrl: process.env.VUE_APP_FDFST_FILE_URL, commentList: [], inputComment: "", showItemId: "", replyUserId: "", parentId: "", userInfo: UserUtil.getUserByStorage(), inputLabel: "", majorComment: "" }; }, methods: { formatDate(date) { return DateUtil.formatDate(date, "yyyy-MM-dd hh:mm"); }, handleClose(done) { done(); }, /** * 点赞 */ /* likeClick(item) { if (item.isLike === null) { item.likeNum++; } else { if (item.isLike) { item.likeNum--; } else { item.likeNum++; } item.isLike = !item.isLike; } }, */ // 获取评论内容 getCommentList() { commentApi.findList(this.relationId, this.commentType).then(res => { this.commentList = res.data; }); }, /** * 点击取消按钮 */ cancel() { this.showItemId = ""; }, /** * 提交评论 */ commitComment() { // 封装参数 let param = { userId: this.userInfo.userId, relationId: this.relationId, type: this.commentType, replyUserId: this.replyUserId, parentId: this.parentId, content: this.inputComment }; commentApi.saveComment(param).then(res => { if (res.success) { this.$message({ message: "评论成功", type: "success" }); this.getCommentList(); this.inputComment = ""; } else { this.$message.error("评论失败"); } }); }, /** * 提交评论 */ commitMajorComment() { // 封装参数 let param = { userId: this.userInfo.userId, relationId: this.relationId, type: this.commentType, replyUserId: this.replyUserId, parentId: this.parentId, content: this.majorComment }; commentApi.saveComment(param).then(res => { if (res.success) { this.$message({ message: "评论成功", type: "success" }); this.getCommentList(); this.majorComment = ""; } else { this.$message.error("评论失败"); } }); }, /** * 点击评论按钮显示输入框 * item: 当前大评论 * reply: 当前回复的评论 */ showCommentInput(item, reply, type) { if (reply) { this.inputLabel = "@" + reply.userName + " "; if (type === "major") { this.parentId = reply.id; } if (type === "child") { this.parentId = reply.parentId; } this.replyUserId = reply.userId; debugger; } else { this.inputLabel = ""; this.parentId = "0"; this.replyUserId = ""; } this.inputComment = ""; this.showItemId = item.id; }, showCommentInputMajor() { this.inputLabel = ""; this.parentId = "0"; this.replyUserId = ""; }, reset() { this.inputComment = ""; this.majorComment = ""; }, close() { this.$emit("commentClose", "0"); } } }; </script> <style scoped lang="less"> /deep/.el-drawer__body { overflow: auto; } /deep/.el-drawer__header span:focus { outline: 0 !important; } .container { padding: 0 10px; box-sizing: border-box; .comment { display: flex; flex-direction: column; padding: 10px; border-bottom: 1px solid #f2f6fc; .info { display: flex; align-items: center; .avatar { border-radius: 50%; } .right { display: flex; flex-direction: column; margin-left: 10px; .name { font-size: 16px; color: #303133; margin-bottom: 5px; font-weight: 500; } .date { font-size: 12px; color: #909399; } } } .content { font-size: 16px; color: #303133; line-height: 20px; padding: 10px 0; } .control { display: flex; align-items: center; font-size: 14px; color: #909399; .like { display: flex; align-items: center; margin-right: 20px; cursor: pointer; &.active, &:hover { color: #409eff; } .iconfont { font-size: 14px; margin-right: 5px; } } .comment-reply { display: flex; align-items: center; cursor: pointer; &:hover { color: #333; } .iconfont { font-size: 16px; margin-right: 5px; margin-top: 4px; } } } .reply { margin: 10px 0; border-left: 2px solid #dcdfe6; .item { margin: 0 10px; padding: 10px 0; border-bottom: 1px dashed #ebeef5; .reply-content { display: flex; align-items: center; font-size: 14px; color: #303133; .from-name { color: #409eff; } .to-name { color: #409eff; margin-left: 5px; margin-right: 5px; } } .reply-bottom { display: flex; align-items: center; margin-top: 6px; font-size: 12px; color: #909399; .reply-text { display: flex; align-items: center; margin-left: 10px; cursor: pointer; &:hover { color: #333; } .icon-iconcomment { margin-right: 5px; margin-top: 4px; font-size: 13px; } } } } .write-reply { display: flex; align-items: center; font-size: 14px; color: #909399; padding: 10px; cursor: pointer; &:hover { color: #303133; } .el-icon-edit { margin-right: 5px; } } .fade-enter-active, fade-leave-active { transition: opacity 0.5s; } .fade-enter, .fade-leave-to { opacity: 0; } .input-wrapper { padding: 10px; .gray-bg-input, .el-input__inner { /*background-color: #67C23A;*/ } .btn-control { display: flex; justify-content: flex-end; align-items: center; padding-top: 10px; .cancel { font-size: 16px; color: #606266; margin-right: 20px; cursor: pointer; &:hover { color: #333; } } .confirm { font-size: 16px; } } } } } } </style>
其他模块引用该评论组件
<template> <Comment :relationId="kpiPerformance.id" :commentType="'1'" :drawer="isComment" @commentClose="commentClick('0')" ></Comment> </template> <script> import Comment from "@/components/Comment"; export default { components: { Comment } } </script>
加载全部内容