机缘
积累经验,0搭建前后端,程序小白搭建开发
成就
- 项目中,发表项目后,进行一级评论的总数统计,也就是话题总数
- 实现一级和二级的评论以及回复
- 二维数组,数据拼装,单表查询
后端:
DTO对象:
接收前端传来的评论数据,以及后端数据拼装,进行入库的对象
package com.admin.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommentDTO {
/**
* 用户id
*/
private Integer userId;
/**
* 项目id
*/
private Integer projectId;
/**
* 用户头像
*/
private String userAvatar;
/**
* 用户昵称
*/
private String userNickName;
/**
* 评论内容
*/
private String content;
/**
* 评论时间
*/
private LocalDateTime contentTime;
/**
* 回复评论Id
*/
private Integer replyId;
/**
* 回复的昵称
*/
private String replyNickName;
}
VO对象:
返回去给前端的数据、或者是拼装返回的数据:
package com.admin.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommentVO {
/**
* 评论的Id
*/
private Integer id;
/**
* 用户id
*/
private Integer userId;
/**
* 项目id
*/
private Integer projectId;
/**
* 用户头像
*/
private String userAvatar;
/**
* 用户昵称
*/
private String userNickName;
/**
* 评论内容
*/
private String content;
/**
* 是否显示回复二级评论 0不显示 1显示
*/
private Integer isReplying;
/**
* 评论时间
*/
@JsonFormat(pattern = "yyyy.MM.dd")
private LocalDateTime contentTime;
/**
* 用于存储对应的二级评论
*/
private List<CommentSecondVO> commentSecondList;
}
package com.admin.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
/**
* 存储二级评论的内容。对象
*/
public class CommentSecondVO {
/**
* 评论的Id
*/
private Integer id;
/**
* 用户id
*/
private Integer userId;
/**
* 项目id
*/
private Integer projectId;
/**
* 用户头像
*/
private String userAvatar;
/**
* 用户昵称
*/
private String userNickName;
/**
* 评论内容
*/
private String content;
/**
* 是否显示回复二级评论 0不显示 1显示
*/
private Integer isReplying;
/**
* 评论时间
*/
@JsonFormat(pattern = "yyyy.MM.dd")
private LocalDateTime contentTime;
/**
* 回复评论Id
*/
private Integer replyId;
/**
* 回复的昵称
*/
private String replyNickName;
}
Controller控制层:
package com.admin.controller.user;
import com.admin.dto.CommentDTO;
import com.admin.result.Result;
import com.admin.service.CommentService;
import com.admin.vo.CommentVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@Api(tags = "评论相关模块")
@Slf4j
@RequestMapping("/user/comment")
public class CommentController {
@Autowired
private CommentService commentService;
@ApiOperation("新增评论")
@PostMapping("/add")
public Result<Boolean> add(@RequestBody CommentDTO commentDTO){
log.info("接受添加评论的信息:{}",commentDTO);
Boolean b = commentService.add(commentDTO);
return Result.success(b);
}
@ApiOperation("获取项目的所有评论")
@GetMapping("/getProjectComments/{projectId}")
public Result<List<CommentVO>> getProjectComments(@PathVariable Integer projectId){
log.info("项目的Id:{}",projectId);
List<CommentVO> commentVOList = commentService.getProjectComments(projectId);
return Result.success(commentVOList);
}
@ApiOperation("获取项目话题一级评论的总数")
@GetMapping("/getCommentTalkTotal/{projectId}")
public Result<Integer> getCommentTalkTotal(@PathVariable Integer projectId){
log.info("获取项目一级评论的总数的项目的Id:{}",projectId);
Integer result = commentService.getCommentTalkTotal(projectId);
return Result.success(result);
}
}
服务层和实现层:
服务层:
package com.admin.service;
import com.admin.dto.CommentDTO;
import com.admin.vo.CommentVO;
import java.util.List;
public interface CommentService {
/**
* 新增评论
* @param commentDTO
* @return
*/
Boolean add(CommentDTO commentDTO);
/**
* 获取项目的评论
* @param projectId
* @return
*/
List<CommentVO> getProjectComments(Integer projectId);
/**
* 获取项目话题一级评论的总数
* @param projectId
* @return
*/
Integer getCommentTalkTotal(Integer projectId);
}
实现层:
package com.admin.service.Impl;
import com.admin.dto.CommentDTO;
import com.admin.entity.Comment;
import com.admin.exception.BaseException;
import com.admin.mapper.CommentMapper;
import com.admin.service.CommentService;
import com.admin.vo.CommentSecondVO;
import com.admin.vo.CommentVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Service
public class CommentServiceImpl implements CommentService {
@Autowired
private CommentMapper commentMapper;
/**
* 新增评论
* @param commentDTO
* @return
*/
@Override
public Boolean add(CommentDTO commentDTO) {
try {
commentDTO.setContentTime(LocalDateTime.now());
Comment comment = new Comment();
BeanUtils.copyProperties(commentDTO,comment);
Boolean b = commentMapper.add(comment);
return b;
}catch (Exception e){
log.error(String.valueOf(e));
throw new BaseException("评论失败,请联系管理员");
}
}
/**
* 获取项目的评论
* @param projectId
* @return
*/
@Override
public List<CommentVO> getProjectComments(Integer projectId) {
//1.查询项目的所有评论
List<Comment> commentList = commentMapper.getProjectComments(projectId);
//2.拆分哪些是一级评论集合,哪些是二级评论
//一级评论集合
List<Comment> commentFirstList = commentList.stream()
.filter(comment -> comment.getReplyId() == null)
.collect(Collectors.toList());
//二级评论集合
List<Comment> commentSecondList = commentList.stream()
.filter(comment -> comment.getReplyId() != null)
.collect(Collectors.toList());
//3.对象数据拷贝VO
List<CommentVO> CommentVOList = commentFirstList.stream().map(o -> {
CommentVO commentVO = new CommentVO();
BeanUtils.copyProperties(o, commentVO);
//用来是否显示回复二级评论属性
commentVO.setIsReplying(0);
return commentVO;
}).collect(Collectors.toList());
List<CommentSecondVO> CommentSecondVOList = commentSecondList.stream().map(o -> {
CommentSecondVO commentSecondVO = new CommentSecondVO();
BeanUtils.copyProperties(o, commentSecondVO);
commentSecondVO.setIsReplying(0);
return commentSecondVO;
}).collect(Collectors.toList());
//4.二级评论建立 哈希值, k:是回复的评论id replyId v:数组
if(CommentSecondVOList!=null && CommentSecondVOList.size()>0){
Map<Integer, List<CommentSecondVO>> commentSecondVOMap = CommentSecondVOList.stream()
.collect(Collectors.groupingBy(CommentSecondVO::getReplyId));
//5.去寻找一级评论的id,将二级评论放到CommentVO里面的二级评论集合里面
for(CommentVO commentVO :CommentVOList){
List<CommentSecondVO> commentSecondVOS = commentSecondVOMap.get(commentVO.getId());
if(commentSecondVOS!=null && commentSecondVOS.size()>0){
commentVO.setCommentSecondList(commentSecondVOS);
}
}
}
//6.返回前端
return CommentVOList;
}
/**
* 获取项目话题一级评论的总数
* @param projectId
* @return
*/
@Override
public Integer getCommentTalkTotal(Integer projectId) {
Integer result = commentMapper.getCommentTalkTotal(projectId);
return result;
}
}
注意这个接口:
Mapper层:
package com.admin.mapper;
import com.admin.entity.Comment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface CommentMapper {
/**
* 新增评论
* @param comment
* @return
*/
Boolean add(Comment comment);
/**
* 查询项目的所有评论
* @param projectId
* @return
*/
@Select("select * from comment where projectId = #{projectId} order by contentTime desc")
List<Comment> getProjectComments(Integer projectId);
/**
* 获取项目话题一级评论的总数
* @param projectId
* @return
*/
@Select("SELECT COUNT(*) from comment where projectId = #{projectId} and replyId is NULL")
Integer getCommentTalkTotal(Integer projectId);
}
前端:
评论的组件:
<template>
<div class="commentList">
<div class="commentInput">
<textarea v-model="commentText" placeholder="输入您的评论..." maxlength="450"/>
<div class="commentInput_btn">
<div class="commentInput_btn_emoji">
<el-dropdown trigger="click">
<span class="el-dropdown-link" style="font-size: 30px">
{{emojiJson[0]}}
</span>
<el-dropdown-menu slot="dropdown">
<div class="emoji_dialog">
<i style="cursor: pointer" v-for="(item,index) in emojiJson" :key="index" @click="addEmoji(item,'first')">
{{item}}
</i>
</div>
</el-dropdown-menu>
</el-dropdown>
</div>
<!-- 您可以添加更多的表情 -->
<el-button type="danger" size="medium" @click="submitComment('first')">评论</el-button>
</div>
</div>
<div class="comments" v-loading="loading">
<div v-for="(comment, index) in commentList" :key="index">
<!-- 一级评论内容-->
<div class="comment_list">
<div class="comment_user_cover">
<el-avatar :src="comment.userAvatar" width="50" height="50"/>
</div>
<div class="comment_userInfo_content">
<div>
{{comment.userNickName}} {{comment.contentTime}}
</div>
<div style="font-size: 20px;color: black">
{{ comment.content }}
</div>
</div>
<div class="comment_reply_btn">
<div style="cursor: pointer" @click="replyComment(comment,index)">
<i class="el-icon-chat-dot-square"></i>
<span v-if="comment.isReplying == 0">回复</span>
<span v-else-if="comment.isReplying == 1">收起</span>
</div>
</div>
</div>
<!-- 一级级评论盒子-->
<div v-if="comment.isReplying == 1" class="reply_box">
<div style="width: 90%">
<textarea v-model="commentSecondText" :placeholder="placeholderReply"/>
<div class="commentInput_btn">
<div class="commentInput_btn_emoji">
<el-dropdown trigger="click">
<span class="el-dropdown-link" style="font-size: 30px">
{{emojiJson[0]}}
</span>
<el-dropdown-menu slot="dropdown">
<div class="emoji_dialog">
<i style="cursor: pointer" v-for="(item,index) in emojiJson" :key="index" @click="addEmoji(item,'second')">
{{item}}
</i>
</div>
</el-dropdown-menu>
</el-dropdown>
</div>
<!-- 您可以添加更多的表情 -->
<el-button type="danger" size="medium" @click="submitComment('second',comment)">评论</el-button>
</div>
</div>
</div>
<!-- 二级评论内容 -->
<div class="comment_second_list">
<div v-for="(commentSecond,index) in comment.commentSecondList" :key="index">
<div class="comment_second_content">
<div class="comment_user_cover">
<el-avatar :src="commentSecond.userAvatar" width="50" height="50"/>
</div>
<div class="comment_second_userInfo_content">
<div>
{{commentSecond.userNickName}} 回复 {{commentSecond.replyNickName}} {{commentSecond.contentTime}}
</div>
<div style="font-size: 20px;color: black">
{{ commentSecond.content }}
</div>
</div>
<div class="comment_reply_btn">
<div style="cursor: pointer" @click="replyComment(commentSecond,index)">
<i class="el-icon-chat-dot-square"></i>
<span v-if="commentSecond.isReplying == 0">回复</span>
<span v-else-if="commentSecond.isReplying == 1">收起</span>
</div>
</div>
</div>
<!-- 二级评论盒子-->
<div v-if="commentSecond.isReplying === 1" class="reply_box">
<div style="width: 80%">
<textarea v-model="commentSecondText" :placeholder="placeholderReply"/>
<div class="commentInput_btn">
<div class="commentInput_btn_emoji">
<el-dropdown trigger="click">
<span class="el-dropdown-link" style="font-size: 30px">
{{emojiJson[0]}}
</span>
<el-dropdown-menu slot="dropdown">
<div class="emoji_dialog">
<i style="cursor: pointer" v-for="(item,index) in emojiJson" :key="index" @click="addEmoji(item,'second')">
{{item}}
</i>
</div>
</el-dropdown-menu>
</el-dropdown>
</div>
<!-- 您可以添加更多的表情 -->
<el-button type="danger" size="medium" @click="submitComment('second',commentSecond)">评论</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {emojiData} from "@/api/data/emoji";
import {getToken} from "@/utils/http/auth";
import {message} from "@/utils/Message";
import {addComment, getProjectComments} from "@/api/user/comment";
export default {
name: "CommentList",
props:{
projectId:{
type:Number,
default:null,
}
},
created() {
this.getComment(this.projectId)
//从vuex 获取数据
const userInfo = this.$store.getters.userInfo_data;
this.firstComment.userId =userInfo.id
this.secondComment.userId = userInfo.id
if(userInfo.head !=null && userInfo.head !=""){
this.firstComment.userAvatar =userInfo.head
this.secondComment.userAvatar = userInfo.head
}
this.firstComment.userNickName =userInfo.nickName
this.secondComment.userNickName = userInfo.nickName
},
data() {
return {
//二级评论提示
placeholderReply:"",
//表情数据
emojiJson:emojiData.data.split(","),
commentText: "",
commentSecondText:"",
// 一级评论的对象
firstComment:{
userId:null,
projectId:null,
userAvatar:"https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg",
userNickName:"",
content:"",
},
// 二级评论的对象
secondComment:{
userId:null,
projectId:null,
userAvatar:"https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg",
userNickName:"",
content:"",
//回复评论的id
replyId:null,
//回复评论的昵称
replyNickName:"",
},
//所有评论集合
commentList: [],
//笼罩层
loading:true,
//存储回复评论的Id, 点击的时候的变化
temporaryId:null,
};
},
methods: {
/**
* 回复评论
* @param comment
* @param index
*/
replyComment(comment,index){
//每次只能回复一个评论 点击回复又马上收起
if(this.temporaryId === comment.id){
console.log("1")
if(comment.isReplying === 0){
comment.isReplying = 1;
}else if(comment.isReplying === 1){
comment.isReplying = 0;
}
}else {
//点击回复完评论,没有回复这个评论,又去点击另外一个评论进行回复
if(comment.isReplying === 0){
//当点击回复一级评论的时候,全部的二级评论回复状态给收起来,所有的一级评论也给我收起来,最后再展开回复当前的评论
if(comment.replyId === undefined){
//当点击回复一级评论的时候,全部的二级评论回复状态给收起来,所有的一级评论也给我收起来,
this.commentList.forEach(comments=>{
comment.isReplying = 0
if(comments.commentSecondList !== null && comments.commentSecondList.length>0){
comments.commentSecondList.forEach(item=>{
item.isReplying=0;
})
}
})
}else {
//当点击回复二级评论的时候
this.commentList.forEach(comment=>{
comment.isReplying = 0
})
}
comment.isReplying = 1;
}
}
this.placeholderReply = "回复:"+comment.userNickName;
this.temporaryId = comment.id
},
/**
* 插入表情
* @param emoji
* @param type
*/
addEmoji(emoji,type) {
switch (type) {
case "first":
this.commentText +=emoji;
break;
case "second":
this.commentSecondText += emoji;
break;
}
},
/**
* 提交评论
*/
submitComment(type,comment) {
const token = getToken();
if(token){
switch (type){
case "first":
if (this.commentText.trim()!== "") {
this.firstComment.projectId = this.projectId
this.firstComment.content=this.commentText
this.addCommentMethod(this.firstComment)
this.commentText =""
}else {
message(true,"不能评论空内容!!!","error",true)
}
console.log(this.firstComment,"nickName")
break;
case "second":
if (this.commentSecondText.trim()!== "") {
this.secondComment.projectId = this.projectId
//这里做一判断,如果是在二级评论下回复的,它还是属于第一级评论下的二级评论
if(comment.replyId !== undefined && comment.replyId !==null){
this.secondComment.replyId = comment.replyId
}else {
this.secondComment.replyId = comment.id
}
this.secondComment.replyNickName = comment.userNickName
this.secondComment.content = this.commentSecondText
console.log(this.secondComment,"this.secondComment")
this.addCommentMethod(this.secondComment)
this.commentSecondText=""
}else {
message(true,"不能评论空内容!!!","error",true)
}
break;
}
}else {
message(true,"温馨提示,没有登录,不能评论哦","error",true)
}
},
/**
* 新增评论接口
* @param data
*/
addCommentMethod(data){
addComment(data).then(res=>{
if(res.data.code == 200){
message(true,"评论成功,文明评论哦","success",true)
this.getComment(data.projectId)
this.$emit("get-talk-comment-total",data.projectId)
}else {
message(true,res.data.msg,"error",true)
}
})
},
/**
* 获取项目的所有评论数据
* @param projectId
*/
getComment(projectId){
getProjectComments(projectId).then(res=>{
if(res.data.code == 200){
this.commentList = res.data.data
this.loading=false
}
})
}
},
}
</script>
<style scoped>
.commentList{
height: 100%;
width: 100%;
box-sizing: border-box;
}
.commentInput{
margin-bottom: 50px;
}
.commentInput_btn{
display: flex;
flex-direction: row;
height: 50px;
padding-top: 10px;
width: 120px;
float: right;
}
.commentInput_btn_emoji{
cursor: pointer;
width: 70px;
height: 100%;
}
.emoji_dialog{
height: 100px;
width: 400px;
font-size: 20px;
overflow-y: auto;
}
textarea {
width: 100%;
height: 100px;
padding: 10px;
box-sizing: border-box;
border: 1px solid #ccc;
font-size: 16px;
resize: none;
}
.comments{
height: calc(100% - 170px);
overflow-y: auto;
}
.comment_list{
margin-top: 20px;
width: 100%;
display: flex;
flex-direction: row;
height: 70px;
}
.comment_userInfo_content,.comment_reply_btn{
height: 100%;
display: flex;
color: gray;
font-size: 16px;
}
.comment_userInfo_content {
width: 83%;
flex-direction: column;
justify-content: space-around;
padding-left: 10px;
}
.comment_reply_btn{
flex-direction: row;
padding-top: 5px;
cursor: pointer;
}
.reply_box{
margin-top: 20px;
width: 100%;
display: flex;
flex-direction: row;
justify-content: center;
}
.comment_second_list{
/*margin-top: 20px;*/
width: 100%;
}
.comment_second_content{
margin: 20px 0px;
width: 100%;
padding-left: 10%;
display: flex;
flex-direction: row;
}
.comment_second_userInfo_content{
width: 81%;
flex-direction: column;
justify-content: space-around;
padding-left: 10px;
}
</style>
使用:
<!-- 话题盒子-->
<div class="item_content" v-if="showComment">
<comment-list :project-id="projectAllData.projectBaseInfo.id" @get-talk-comment-total="getTalkCountTotal"/>
</div>
憧憬
目前只有进行简单的评论,还没有进行过滤敏感词,以及删除评论功能,后续会进行更新。
总结
1、在分析自己要做成具体的样子,需要什么数据结构,完成这些数据的步骤
2、前端:自己模拟数据,画好
3、再写接口的同时,要分析步骤,罗列出来,再进行代码的编写
4、以前写的代码,偶尔进行回顾,不然会发现不是自己写的了,多练
开发过程中偶尔会有很多考虑不到的地方,希望小伙伴或者前辈能够在评论区给予建议!!!