【Vue】高仿CSDN的评论区功能,全手敲!
2-2、点击评论图标的时候,触发一个方法,打开弹窗,并根据文章id请求后台评论,后端用Java对评论和回复进行封装返回2-3、发表评论,将评论内容和用户id以及文章id发给后端保存,发表的时候,前端伪更新评论区,将新发表的评论push到comments里面,就不需要再次请求后端获取所有评论,除非用户主动刷新页面。2-4、打开回复框,注意我们动态绑定了每条评论的openReply属性,每个回复框都是
·
1、效果
体验地址(服务器已过期):桂林高校社区
2、实现流程
2-1、当点击评论图标时,在右边弹出评论区,给该评论区设置一个div浮动标签,用v-if判断显示与否,本来想用el-drawer组件实现右边弹窗抽屉效果,但是那个遮罩层让我很不满,干脆自己实现一个抽屉效果。
<!--评论弹窗-->
<div v-if="drawer" class="commentDrawer">
<span>评论区</span>
<span @click="drawer = false" style="cursor: pointer;float: left;margin-left: 10px">关闭</span>
<!--输入框-->
<div class="inputComment">
<textarea v-model="textareaContent" placeholder="说点什么..." maxlength="200" @input="calcInput">
</textarea>
<span style="font-size: 14px;float: left">还可以输入{{ canInputText }}个字符</span>
<span class="sent" @click="sentComment">发送</span>
</div>
<!--评论列表-->
<div class="comment-list">
<ul class="comment-ul">
<li class="comment-li" v-for="(com,index) in comments">
<div class="comment-li-top">
<img :src="com.avatarUrl"/>
<span class="comment-nickName">{{ com.nickName }}</span>
<span style="font-size: 15px;margin-left: auto">{{ com.commentTimeRes }}</span>
<span v-if="!com.openReply"
style="cursor: pointer;margin-left:auto;font-size: 16px" @click="openReply(com,index)">
回复</span>
<span v-if="com.openReply"
style="cursor: pointer;margin-left: auto;font-size: 16px" @click="closeReply(index)">
收起</span>
</div>
<div class="comment-li-content" @click="openReply(com,index)">
<span style="text-align: left;float: left;cursor: pointer">{{ com.content }}</span>
</div>
<div class="inputReply" v-if="com.openReply">
<textarea v-model="com.replyContent" :placeholder="com.placeholder" maxlength="200"
@input="calcInputReply(index)">
</textarea>
<span style="font-size: 14px;float: left">还可以输入{{ com.canInputReply }}个字符</span>
<span class="sent" @click="sentReply(com,index)">回复</span>
</div>
<!--回复列表-->
<ul class="reply-ul">
<li class="reply-li" v-for="(reply,index1) in com.replyies">
<div class="reply-li-top">
<img :src="reply.fromUserAvatarUrl"/>
<span class="reply-nickName">{{ reply.fromUserNickName }}
<span style="font-size: 15px;margin: 3px;color: var(--text-color)">回复</span>
{{reply.toUserNickName}}</span>
<span style="margin-left: auto;font-size: 13px">{{ reply.replyTimeRes }}</span>
<span v-if="!reply.openReply"
style="cursor: pointer;margin-left:auto;font-size: 14px" @click="openReplySon(reply)">
回复</span>
<span v-if="reply.openReply"
style="cursor: pointer;margin-left: auto;font-size: 14px" @click="closeReplySon(reply)">
收起</span>
</div>
<div class="reply-li-content" @click="openReplySon(reply)">
<span style="float: left">{{ reply.content }}</span>
</div>
<!--回复下的回复框-->
<div class="inputReplySon" v-show="reply.openReply">
<textarea v-model="reply.replyContent" :placeholder="reply.placeholder" maxlength="200"
@input="calcReplySon(index,index1)">
</textarea>
<span style="font-size: 12px;float: left">还可以输入{{ reply.canInputReply }}个字符</span>
<span class="sent" @click="sentReplySon(com,reply)">回复</span>
</div>
</li>
</ul>
</li>
</ul>
</div>
</div>
2-2、点击评论图标的时候,触发一个方法,打开弹窗,并根据文章id请求后台评论,后端用Java对评论和回复进行封装返回
//跳转评论详情
toComment(id) {
this.drawer = false
this.clickArticleId = id
setTimeout(() => {
this.drawer = true
}, 100)
//获取该动态所有评论
this.$http.post('/circle/comment/getComment', {
articleId: id,
type: 'article'
}).then(res => {
console.log('评论', res)
this.comments = res.data.comments
})
},
2-3、发表评论,将评论内容和用户id以及文章id发给后端保存,发表的时候,前端伪更新评论区,将新发表的评论push到comments里面,就不需要再次请求后端获取所有评论,除非用户主动刷新页面。
//发表评论
sentComment() {
if (this.userInfo === null) {
//没有登录
this.$message({
message: '请先登陆哦~',
type: 'warning'
})
this.$loginToast.open()
return
}
let articleId = this.clickArticleId
let userId = this.userInfo.id
let type = 'article'
let content = this.textareaContent
let comment = {
type: type,
composeId: articleId,
content: content,
fromUserid: userId
}
//请求
this.$http.post('/circle/comment/addComment', comment).then(res => {
if (res.data.code === 200) {
this.$message({
message: '发送成功',
type: 'success'
})
this.textareaContent = ''
} else {
this.$message({
message: '发送失败',
type: 'error'
})
}
})
//用现有数据更新当前评论区
let addComment = {
avatarUrl: this.userInfo.avatarUrl,
nickName: this.userInfo.nickName,
content: this.textareaContent,
replyies: [],
openReply: false,
commentTimeRes: '1秒前',
placeholder: '',
replyContent: '',
canInputReply: 200,
}
this.comments.push(addComment)
},
2-4、打开回复框,注意我们动态绑定了每条评论的openReply属性,每个回复框都是独立的,并不会相互影响,更新openReply属性不能简单地用this.comment[index].openReply = true ,这样打开并不能让页面重新渲染,要用this.$set(对象,属性,值)来动态渲染。
//打开回复框
openReply(com) {
//打开该评论的回复框
this.$set(com, 'openReply', true)
this.$set(com, 'placeholder', '回复@' + com.nickName)
},
closeReply(index) {
this.$set(this.comments[index], 'openReply', false)
},
//打开回复下面的回复框
openReplySon(reply){
this.$set(reply,'openReply',true)
this.$set(reply,'placeholder','回复'+ reply.fromUserNickName)
},
closeReplySon(reply){
this.$set(reply,'openReply',false)
},
2-5、发送回复,动态更新评论区的回复区
sentReply(com, index) {
//先判断有没有登录
if (this.userInfo === null) {
//没有登录
this.$message({
message: '请先登陆哦~',
type: 'warning'
})
this.$loginToast.open()
return
}
let reply = {
commentId: com.id,
content: com.replyContent,
replyTimeRes: '一秒前',
fromUserid: this.userInfo.id,
toUserid: com.fromUserid,
fromUserAvatarUrl: this.userInfo.avatarUrl,
fromUserNickName: this.userInfo.nickName,
toUserNickName: com.nickName,
canInputReply: 200,
placeholder: '',
replyContent: ''
}
//更新当前视图
this.comments[index].replyies.push(reply)
this.$http.post('/circle/reply/sentReply', reply).then(res => {
console.log(res)
if (res.data.code === 200){
//关闭回复框
this.$set(com,'replyContent','')
this.$set(com,'openReply',false)
}
})
},
2-6、评论区的样式表,回复区的样式比较多
.commentDrawer {
background-color: var(--li-bg-color);
color: var(--text-color);
position: fixed;
z-index: 2005;
width: 30%;
height: 100%;
right: 0;
top: 0;
border-top-left-radius: 14px;
border-bottom-left-radius: 14px;
}
.inputComment {
margin-top: 5%;
width: 88%;
margin-left: 3%;
position: relative;
height: 185px;
border-radius: 13px;
padding: 3%;
background-color: var(--main-bg-color);
color: var(--text-color);
}
.inputComment textarea {
width: 98%;
position: relative;
height: 150px;
border: 0 solid;
outline: none;
resize: none;
background-color: var(--main-bg-color);
color: var(--text-color);
}
.inputComment .sent {
border-radius: 30px;
background-color: #ee464b;
color: white;
padding: 2px 15px;
font-size: 16px;
float: right;
cursor: pointer;
height: 25px;
}
.comment-list {
width: 100%;
border-radius: 14px;
margin-top: 4%;
height: 72%;
position: relative;
}
.comment-ul {
margin: 0;
padding: 0;
width: 100%;
height: 99%;
position: relative;
list-style-type: none;
overflow: auto;
}
.comment-li {
margin: 2%;
float: left;
width: 96%;
position: relative;
overflow: auto;
}
.comment-li-top {
display: flex;
float: left;
align-items: center;
width: 90%;
}
.comment-li-top span {
margin-left: 8px;
}
.comment-li img {
width: 35px;
height: 35px;
border-radius: 50%;
}
.comment-li-content {
margin-left: 7%;
width: 86%;
font-size: 18px;
clear: left;
float: left;
padding: 5px;
cursor: pointer;
overflow: auto;
}
.comment-nickName {
font-size: 18px;
color: #2073e3;
}
.inputReply {
float: left;
margin-top: 1%;
width: 76%;
margin-left: 8%;
position: relative;
height: 150px;
border-radius: 13px;
padding: 2%;
background-color: var(--main-bg-color);
color: var(--text-color);
}
.inputReply textarea {
width: 98%;
position: relative;
height: 120px;
border: 0 solid;
outline: none;
resize: none;
background-color: var(--main-bg-color);
color: var(--text-color);
}
.inputReply .sent {
border-radius: 30px;
background-color: #ee464b;
color: white;
padding: 2px 15px;
font-size: 16px;
float: right;
cursor: pointer;
height: 25px;
}
.reply-ul {
margin: 0;
padding: 0;
width: 100%;
position: relative;
list-style-type: none;
overflow: auto;
}
.reply-li {
margin-left: 7%;
margin-top: 1%;
float: left;
width: 92%;
position: relative;
overflow: auto;
}
.reply-li-top {
display: flex;
float: left;
align-items: center;
width: 90%;
}
.reply-li img {
width: 35px;
height: 35px;
border-radius: 50%;
}
.reply-li-content {
margin-left: 8%;
width: 82%;
font-size: 16px;
clear: left;
float: left;
padding: 5px;
cursor: pointer;
}
.reply-nickName {
font-size: 16px;
color: #2073e3;
}
.inputReplySon {
float: left;
margin-top: 1%;
width: 69%;
margin-left: 9%;
position: relative;
height: 150px;
border-radius: 13px;
padding: 2%;
background-color: var(--main-bg-color);
color: var(--text-color);
}
.inputReplySon textarea {
width: 93%;
position: relative;
height: 120px;
border: 0 solid;
outline: none;
resize: none;
background-color: var(--main-bg-color);
color: var(--text-color);
}
.inputReplySon .sent {
border-radius: 30px;
background-color: #ee464b;
color: white;
padding: 2px 13px;
font-size: 13px;
float: right;
cursor: pointer;
height: 22px;
}
2-7、整个页面代码,包括动态列表和评论区
<template>
<div>
<div class="main">
<div>
<el-button type="primary" @click="publish" style="position: fixed;right: 20%">发布动态</el-button>
<ul class="list" v-infinite-scroll="load" style="overflow:auto" infinite-scroll-immediate="false"
v-loading="loading">
<li v-for="(item,index) in articles" class="list-item">
<div class="list-top">
<img :src="item.publisherAvatarUrl"/>
<div style="margin-left: 6px;color: #2073e3;float: left">
<span style="float: left">{{ item.publisherNickName }}</span>
<div style="width: 200px"></div>
<span style="float: left;font-size: 15px;align-items: center;display: flex;color: #a426ce">
{{ item.publisherSchool }}
<!--性别图标-->
<img v-if="item.publisherGender===1" style="width: 26px;height: 26px;margin-left: 5px"
src="https://guilinus.oss-cn-guangzhou.aliyuncs.com/%E6%80%A7%E5%88%AB_%E7%94%B7.png"/>
<img v-if="item.publisherGender===2" style="width: 26px;height: 26px;margin-left: 5px"
src="https://guilinus.oss-cn-guangzhou.aliyuncs.com/%E6%80%A7%E5%88%AB-%E5%A5%B3.png"/>
</span>
</div>
</div>
<div class="list-main">
<div class="list-main-text">
<p>{{ item.content }}</p>
</div>
<div class="list-main-img">
<div v-for="img in item.imgUrls">
<el-image :src="img" fit="cover"
:preview-src-list="item.imgUrls" @click="addClick(item.id)"></el-image>
</div>
</div>
<div style="width: 100%"></div>
</div>
<div class="list-bottom">
<span style="font-size: 17px;text-align: left;">{{ item.publishTimeRes }}</span>
<span>
浏览{{ item.click }}次
</span>
<!--评论-->
<span @click="toComment(item.id)">
<img
src="https://guilinus.oss-cn-guangzhou.aliyuncs.com/icon/%E8%AF%84%E8%AE%BA%E5%8C%BA%20%281%29.png"/>
<span style="font-size: 26px">{{ item.comment }}</span>
</span>
<!--点赞-->
<span>
<img v-if="!item.isLike" @click="doLike(item.id,index)"
src="https://guilinus.oss-cn-guangzhou.aliyuncs.com/icon/%E7%82%B9%E8%B5%9E%20%281%29.png"/>
<img v-if="item.isLike" @click="cancelLike(item.id,index)"
src="https://guilinus.oss-cn-guangzhou.aliyuncs.com/icon/%E7%82%B9%E8%B5%9E_%E5%9D%97%20%281%29.png"/>
<span style="font-size: 26px">{{ item.zan }}</span>
</span>
</div>
</li>
</ul>
</div>
</div>
<publish-dia ref="dia"></publish-dia>
<!-- <article-detail ref="detail"></article-detail>-->
<!--评论弹窗-->
<div v-if="drawer" class="commentDrawer">
<span>评论区</span>
<span @click="drawer = false" style="cursor: pointer;float: left;margin-left: 10px">关闭</span>
<!--输入框-->
<div class="inputComment">
<textarea v-model="textareaContent" placeholder="说点什么..." maxlength="200" @input="calcInput">
</textarea>
<span style="font-size: 14px;float: left">还可以输入{{ canInputText }}个字符</span>
<span class="sent" @click="sentComment">发送</span>
</div>
<!--评论列表-->
<div class="comment-list">
<ul class="comment-ul">
<li class="comment-li" v-for="(com,index) in comments">
<div class="comment-li-top">
<img :src="com.avatarUrl"/>
<span class="comment-nickName">{{ com.nickName }}</span>
<span style="font-size: 15px;margin-left: auto">{{ com.commentTimeRes }}</span>
<span v-if="!com.openReply"
style="cursor: pointer;margin-left:auto;font-size: 16px" @click="openReply(com,index)">
回复</span>
<span v-if="com.openReply"
style="cursor: pointer;margin-left: auto;font-size: 16px" @click="closeReply(index)">
收起</span>
</div>
<div class="comment-li-content" @click="openReply(com,index)">
<span style="text-align: left;float: left;cursor: pointer">{{ com.content }}</span>
</div>
<div class="inputReply" v-if="com.openReply">
<textarea v-model="com.replyContent" :placeholder="com.placeholder" maxlength="200"
@input="calcInputReply(index)">
</textarea>
<span style="font-size: 14px;float: left">还可以输入{{ com.canInputReply }}个字符</span>
<span class="sent" @click="sentReply(com,index)">回复</span>
</div>
<!--回复列表-->
<ul class="reply-ul">
<li class="reply-li" v-for="(reply,index1) in com.replyies">
<div class="reply-li-top">
<img :src="reply.fromUserAvatarUrl"/>
<span class="reply-nickName">{{ reply.fromUserNickName }}
<span style="font-size: 15px;margin: 3px;color: var(--text-color)">回复</span>
{{reply.toUserNickName}}</span>
<span style="margin-left: auto;font-size: 13px">{{ reply.replyTimeRes }}</span>
<span v-if="!reply.openReply"
style="cursor: pointer;margin-left:auto;font-size: 14px" @click="openReplySon(reply)">
回复</span>
<span v-if="reply.openReply"
style="cursor: pointer;margin-left: auto;font-size: 14px" @click="closeReplySon(reply)">
收起</span>
</div>
<div class="reply-li-content" @click="openReplySon(reply)">
<span style="float: left">{{ reply.content }}</span>
</div>
<!--回复下的回复框-->
<div class="inputReplySon" v-show="reply.openReply">
<textarea v-model="reply.replyContent" :placeholder="reply.placeholder" maxlength="200"
@input="calcReplySon(index,index1)">
</textarea>
<span style="font-size: 12px;float: left">还可以输入{{ reply.canInputReply }}个字符</span>
<span class="sent" @click="sentReplySon(com,reply)">回复</span>
</div>
</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import publishDia from "@/views/gaoxiaoquan/publishDia";
export default {
components: {
publishDia,
},
name: "UniversityCircle",
data() {
return {
articles: [],
page: 0,
limit: 20,
loading: false,
userInfo: {},
clickArticleId: 0,
drawer: false,
textareaContent: '',
canInputText: 200,
comments: [
{
id: '0',
replyies: [{
fromUserNickName: '',
toUserNickName: '',
fromUserAvatarUrl: '',
content: '',
replyTimeRes: '',
commentId: '',
fromUserid: '',
toUserid: '',
replyContent: '',
placeholder: '',
openReply: false,
canInputReply: 200
}],
commentTimeRes: '',
openReply: false,
placeholder: '',
replyContent: '',
canInputReply: 200,
}
]
}
},
created() {
let userStatus = JSON.parse(localStorage.getItem('userInfo'))
if (userStatus != null) {
this.userInfo = userStatus.user
} else {
this.userInfo = null
}
this.load()
},
methods: {
publish() {
if(this.userInfo === null){
this.$message({
message: '请先登录',
type: 'warning'
})
this.$loginToast.open()
return
}
this.$refs.dia.open()
},
load() {
//分页获取数据
if (this.articles.length % 20 === 0) {
this.loading = true
this.$http.post('/circle/article/getArticles',
{
page: this.page,
limit: this.limit,
userId: this.userInfo === null ? '0' : this.userInfo.id
}).then(res => {
if (res.data.code === 200) {
for (let i = 0; i < res.data.articles.length; i++) {
this.articles.push(res.data.articles[i])
}
if (res.data.articles.length >= 20) {
this.page += 20;
}
}
console.log('返回', res)
this.loading = false
})
} else {
this.$message({
message: '我是有底线哒~',
type: 'warning',
offset: 900,
center: true
})
}
},
//点赞
doLike(articleId, index) {
//先判断有没有登录
if (this.userInfo === null) {
//没有登录
this.$message({
message: '登陆后才能点赞哦~',
type: 'warning'
})
this.$loginToast.open()
} else {
console.log('articleId', articleId)
let userId = this.userInfo.id
this.$http.post('circle/zan/doLike', {
articleId: articleId,
userId: userId
}).then(res => {
console.log(res)
this.articles[index].zan++
this.articles[index].isLike = true
})
}
},
//取消点赞
cancelLike(articleId, index) {
//先判断有没有登录
if (this.userInfo === null) {
//没有登录
this.$message({
message: '请先登陆哦~',
type: 'warning'
})
this.$loginToast.open()
} else {
let userId = this.userInfo.id
this.$http.post('circle/zan/cancelLike', {
articleId: articleId,
userId: userId
}).then(res => {
console.log(res)
this.articles[index].zan--
this.articles[index].isLike = false
})
}
},
//增加浏览量
addClick(id){
console.log('id',id)
this.$http.post('/circle/article/addClick',{
articleId: id
})
},
//跳转评论详情
toComment(id) {
this.drawer = false
this.clickArticleId = id
setTimeout(() => {
this.drawer = true
}, 100)
//获取该动态所有评论
this.$http.post('/circle/comment/getComment', {
articleId: id,
type: 'article'
}).then(res => {
console.log('评论', res)
this.comments = res.data.comments
})
},
//计算输入字数
calcInput() {
let len = this.textareaContent.length
this.canInputText = 200 - len;
},
calcInputReply(index) {
let len = this.comments[index].replyContent.length
this.$set(this.comments[index], 'canInputReply', 200 - len)
},
calcReplySon(index,index1){
let len = this.comments[index].replyies[index1].replyContent.length
this.$set(this.comments[index].replyies[index1],'canInputReply',200 -len)
},
//发表评论
sentComment() {
if (this.userInfo === null) {
//没有登录
this.$message({
message: '请先登陆哦~',
type: 'warning'
})
this.$loginToast.open()
return
}
let articleId = this.clickArticleId
let userId = this.userInfo.id
let type = 'article'
let content = this.textareaContent
let comment = {
type: type,
composeId: articleId,
content: content,
fromUserid: userId
}
//请求
this.$http.post('/circle/comment/addComment', comment).then(res => {
if (res.data.code === 200) {
this.$message({
message: '发送成功',
type: 'success'
})
this.textareaContent = ''
} else {
this.$message({
message: '发送失败',
type: 'error'
})
}
})
//用现有数据更新当前评论区
let addComment = {
avatarUrl: this.userInfo.avatarUrl,
nickName: this.userInfo.nickName,
content: this.textareaContent,
replyies: [],
openReply: false,
commentTimeRes: '1秒前',
placeholder: '',
replyContent: '',
canInputReply: 200,
}
this.comments.push(addComment)
},
//打开回复框
openReply(com) {
//打开该评论的回复框
this.$set(com, 'openReply', true)
this.$set(com, 'placeholder', '回复@' + com.nickName)
},
closeReply(index) {
this.$set(this.comments[index], 'openReply', false)
},
//打开回复下面的回复框
openReplySon(reply){
this.$set(reply,'openReply',true)
this.$set(reply,'placeholder','回复'+ reply.fromUserNickName)
},
closeReplySon(reply){
this.$set(reply,'openReply',false)
},
sentReply(com, index) {
//先判断有没有登录
if (this.userInfo === null) {
//没有登录
this.$message({
message: '请先登陆哦~',
type: 'warning'
})
this.$loginToast.open()
return
}
let reply = {
commentId: com.id,
content: com.replyContent,
replyTimeRes: '一秒前',
fromUserid: this.userInfo.id,
toUserid: com.fromUserid,
fromUserAvatarUrl: this.userInfo.avatarUrl,
fromUserNickName: this.userInfo.nickName,
toUserNickName: com.nickName,
canInputReply: 200,
placeholder: '',
replyContent: ''
}
//更新当前视图
this.comments[index].replyies.push(reply)
this.$http.post('/circle/reply/sentReply', reply).then(res => {
console.log(res)
if (res.data.code === 200){
//关闭回复框
this.$set(com,'replyContent','')
this.$set(com,'openReply',false)
}
})
},
//发送回复下面的回复
sentReplySon(com,parentReply){
//先判断有没有登录
if (this.userInfo === null) {
//没有登录
this.$message({
message: '请先登陆哦~',
type: 'warning'
})
this.$loginToast.open()
return
}
let reply = {
commentId: com.id,
content: parentReply.replyContent,
replyTimeRes: '一秒前',
fromUserid: this.userInfo.id,
toUserid: parentReply.fromUserid,
fromUserAvatarUrl: this.userInfo.avatarUrl,
fromUserNickName: this.userInfo.nickName,
toUserNickName: parentReply.fromUserNickName,
canInputReply: 200,
placeholder: '',
replyContent: ''
}
com.replyies.push(reply)
this.$http.post('/circle/reply/sentReply',reply).then(res => {
console.log(res)
if (res.data.code === 200){
//关闭回复框
this.$set(parentReply,'replyContent','')
this.$set(parentReply,'openReply',false)
}
})
}
}
}
</script>
<style scoped>
.main {
width: 70%;
position: absolute;
left: 15%;
top: 4%;
height: 950px;
background-color: var(--main-bg-color);
border-radius: 13px;
}
.list {
margin-top: 0;
margin-left: 15%;
list-style-type: none;
width: 83%;
height: 940px;
}
.list-item {
margin: 3px;
color: var(--text-color);
border-radius: 10px;
padding: 5px;
float: left;
width: 90%;
}
.list-top img {
width: 45px;
height: 48px;
border-radius: 50%;
object-fit: fill;
}
.list-top {
display: flex;
align-items: center;
width: 300px;
flex-wrap: wrap;
}
.list-main {
float: left;
display: flex;
flex-wrap: wrap;
}
.list-main-text {
text-align: left;
float: left;
overflow: hidden;
text-overflow: ellipsis;
width: 700px;
display: -webkit-box;
-webkit-line-clamp: 6;
-webkit-box-orient: vertical;
}
.list-main-img {
margin-top: 6px;
float: left;
display: flex;
flex-wrap: wrap;
width: 600px;
}
.list-main-img .el-image {
width: 195px;
height: 195px;
border-radius: 8px;
margin: 2px;
}
.list-bottom {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
width: 600px;
height: 40px;
}
.list-bottom img {
width: 30px;
height: 30px;
margin-top: 5px;
}
.commentDrawer {
background-color: var(--li-bg-color);
color: var(--text-color);
position: fixed;
z-index: 2005;
width: 30%;
height: 100%;
right: 0;
top: 0;
border-top-left-radius: 14px;
border-bottom-left-radius: 14px;
}
.inputComment {
margin-top: 5%;
width: 88%;
margin-left: 3%;
position: relative;
height: 185px;
border-radius: 13px;
padding: 3%;
background-color: var(--main-bg-color);
color: var(--text-color);
}
.inputComment textarea {
width: 98%;
position: relative;
height: 150px;
border: 0 solid;
outline: none;
resize: none;
background-color: var(--main-bg-color);
color: var(--text-color);
}
.inputComment .sent {
border-radius: 30px;
background-color: #ee464b;
color: white;
padding: 2px 15px;
font-size: 16px;
float: right;
cursor: pointer;
height: 25px;
}
.comment-list {
width: 100%;
border-radius: 14px;
margin-top: 4%;
height: 72%;
position: relative;
}
.comment-ul {
margin: 0;
padding: 0;
width: 100%;
height: 99%;
position: relative;
list-style-type: none;
overflow: auto;
}
.comment-li {
margin: 2%;
float: left;
width: 96%;
position: relative;
overflow: auto;
}
.comment-li-top {
display: flex;
float: left;
align-items: center;
width: 90%;
}
.comment-li-top span {
margin-left: 8px;
}
.comment-li img {
width: 35px;
height: 35px;
border-radius: 50%;
}
.comment-li-content {
margin-left: 7%;
width: 86%;
font-size: 18px;
clear: left;
float: left;
padding: 5px;
cursor: pointer;
overflow: auto;
}
.comment-nickName {
font-size: 18px;
color: #2073e3;
}
.inputReply {
float: left;
margin-top: 1%;
width: 76%;
margin-left: 8%;
position: relative;
height: 150px;
border-radius: 13px;
padding: 2%;
background-color: var(--main-bg-color);
color: var(--text-color);
}
.inputReply textarea {
width: 98%;
position: relative;
height: 120px;
border: 0 solid;
outline: none;
resize: none;
background-color: var(--main-bg-color);
color: var(--text-color);
}
.inputReply .sent {
border-radius: 30px;
background-color: #ee464b;
color: white;
padding: 2px 15px;
font-size: 16px;
float: right;
cursor: pointer;
height: 25px;
}
.reply-ul {
margin: 0;
padding: 0;
width: 100%;
position: relative;
list-style-type: none;
overflow: auto;
}
.reply-li {
margin-left: 7%;
margin-top: 1%;
float: left;
width: 92%;
position: relative;
overflow: auto;
}
.reply-li-top {
display: flex;
float: left;
align-items: center;
width: 90%;
}
.reply-li img {
width: 35px;
height: 35px;
border-radius: 50%;
}
.reply-li-content {
margin-left: 8%;
width: 82%;
font-size: 16px;
clear: left;
float: left;
padding: 5px;
cursor: pointer;
}
.reply-nickName {
font-size: 16px;
color: #2073e3;
}
.inputReplySon {
float: left;
margin-top: 1%;
width: 69%;
margin-left: 9%;
position: relative;
height: 150px;
border-radius: 13px;
padding: 2%;
background-color: var(--main-bg-color);
color: var(--text-color);
}
.inputReplySon textarea {
width: 93%;
position: relative;
height: 120px;
border: 0 solid;
outline: none;
resize: none;
background-color: var(--main-bg-color);
color: var(--text-color);
}
.inputReplySon .sent {
border-radius: 30px;
background-color: #ee464b;
color: white;
padding: 2px 13px;
font-size: 13px;
float: right;
cursor: pointer;
height: 22px;
}
</style>
3、后端代码,包括获取所有动态和动态评论区代码
3-1、与article 有关的代码
3-1-1、Controller类
package cn.us.guiyoucircle.controller;
import cn.hutool.core.lang.hash.Hash;
import cn.us.guiyoucircle.entity.Article;
import cn.us.guiyoucircle.mapper.ArticleMapper;
import cn.us.guiyoucircle.service.ArticleService;
import cn.us.guiyoucircle.util.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.stereotype.Controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <p>
* 前端控制器
* </p>
*
* @author 李石林
* @since 2022-08-20
*/
@RestController
@RequestMapping("//circle/article")
public class ArticleController {
@Autowired(required = false)
private ArticleMapper articleMapper;
@Autowired
private ArticleService articleService;
@PostMapping("/publish")
public R publish(@RequestBody Article article){
boolean flag = articleService.publishArticle(article);
if (!flag){
return R.error();
}
return R.ok();
}
@PostMapping("/getArticles")
public R getArticles(@RequestBody HashMap<String,Object> map){
int page = (int) map.get("page");
int limit = (int) map.get("limit");
long userId =Long.parseLong((String) map.get("userId"));
List<Article> articles = articleService.getArticlePage(page,limit,userId);
return R.ok().put("articles",articles);
}
@PostMapping("addClick")
public R addClick(@RequestBody HashMap<String,String> map){
long articleId = Long.parseLong(map.get("articleId"));
this.articleMapper.addClick(articleId);
return R.ok();
}
@PostMapping("/getUserArticle")
public R getUserArticle(@RequestBody Map<String,String> map){
long userId = Long.parseLong(map.get("userId"));
List<Article> articles = this.articleService.getUserArticles(userId);
return R.ok().put("articles",articles);
}
@PostMapping("/updateLock")
public R updateLock(@RequestBody Map<String,String> map){
boolean lock = Boolean.parseBoolean(map.get("lock"));
int flag = lock? 1:0;
long articleId = Long.parseLong(map.get("articleId"));
int f = this.articleMapper.updateLock(flag,articleId);
if(f==0){
return R.error();
}
return R.ok();
}
@PostMapping("/deleteArticle")
public R deleteArticle(@RequestBody Map<String,String> map){
long articleId = Long.parseLong(map.get("articleId"));
this.articleService.removeById(articleId);
return R.ok();
}
}
3-1-2、Server实现类
package cn.us.guiyoucircle.service.impl;
import cn.us.guiyoucircle.entity.Article;
import cn.us.guiyoucircle.entity.ArticleImg;
import cn.us.guiyoucircle.entity.User;
import cn.us.guiyoucircle.mapper.ArticleImgMapper;
import cn.us.guiyoucircle.mapper.ArticleMapper;
import cn.us.guiyoucircle.mapper.CommentMapper;
import cn.us.guiyoucircle.mapper.ZanMapper;
import cn.us.guiyoucircle.service.ArticleImgService;
import cn.us.guiyoucircle.service.ArticleService;
import cn.us.guiyoucircle.service.UserService;
import cn.us.guiyoucircle.util.DateTimeResult;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 服务实现类
* </p>
*
* @author 李石林
* @since 2022-08-20
*/
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
@Autowired(required = false)
ArticleMapper articleMapper;
@Autowired(required = false)
ArticleImgMapper articleImgMapper;
@Autowired
UserService userService;
@Autowired(required = false)
CommentMapper commentMapper;
@Autowired
DateTimeResult dateTimeResult;
@Autowired(required = false)
ZanMapper zanMapper;
@Autowired
private ArticleImgService articleImgService;
@Override
public boolean publishArticle(Article article) {
System.out.println(article);
//取出文章的所以图片地址
List<String> imgUrls = article.getImgUrls();
System.out.println("图片"+imgUrls);
//获取当前时间
LocalDateTime publishTime = LocalDateTime.now();
article.setPublishTime(publishTime);
int f = this.baseMapper.insert(article);
if (f != 0){
//插入成功,存图片
Long articleId = article.getId();
List<ArticleImg> list = new ArrayList<>();
if(!imgUrls.isEmpty()){
for(String s:imgUrls){
ArticleImg img = new ArticleImg();
img.setArticleId(articleId);
img.setImgurl(s);
list.add(img);
}
boolean flag = this.articleImgService.saveBatch(list);
return flag;
}
return true;
}
return false;
}
@Override
public List<Article> getArticlePage(int page, int limit, long userId) {
//分页获取
List<Article> list = this.articleMapper.getArticlesPage(page,limit);
return articleListRes(list,userId);
}
@Override
public List<Article> getUserArticles(long userId) {
//获取该用户的动态
List<Article> articles = this.articleMapper.getUserArticles(userId);
return articleListRes(articles,userId);
}
//封装一个返回动态的方法
public List<Article> articleListRes(List<Article> list,long userId){
List<Article> newList = new ArrayList<>();
for(Article article : list){
//查这篇动态的图片
Long id = article.getId();
Long publisherId = article.getPublisherId();
List<String> imgUrls = this.articleImgMapper.getImgs(id);
article.setImgUrls(imgUrls);
String publishTimeRes = dateTimeResult.getTime(article.getPublishTime());
article.setPublishTimeRes(publishTimeRes);
User user = userService.getById(publisherId);
article.setPublisherNickName(user.getNickName());
article.setPublisherAvatarUrl(user.getAvatarUrl());
article.setPublisherSchool(user.getSchool());
article.setPublisherGender(user.getGender());
//查询这篇动态的点赞数
long zan = (long) this.zanMapper.getArticleZan(id);
//查询评论数
long comment = (long) this.commentMapper.getCommentCount(id,"article");
//查询该用户是否点赞了该文章
Integer likeStatus = (Integer) this.zanMapper.isLike(id,userId);
boolean isLike = (likeStatus != null && likeStatus == 1);
article.setZan(zan);
article.setComment(comment);
article.setIsLike(isLike);
newList.add(article);
}
return newList;
}
}
3-2、与评论有关的代码
3-2-1、Controller类
package cn.us.guiyoucircle.controller;
import cn.us.guiyoucircle.entity.Comment;
import cn.us.guiyoucircle.service.CommentService;
import cn.us.guiyoucircle.util.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* <p>
* 前端控制器
* </p>
*
* @author 李石林
* @since 2022-08-20
*/
@RestController
@RequestMapping("//circle/comment")
public class CommentController {
@Autowired
CommentService commentService;
@PostMapping("/addComment")
public R addComment(@RequestBody Comment comment){
comment.setCommentTime(LocalDateTime.now());
boolean f = this.commentService.save(comment);
if(!f){
return R.error();
}
return R.ok();
}
@PostMapping("/getComment")
public R getComment(@RequestBody Map<String,String> map){
long articleId = Long.parseLong(map.get("articleId"));
System.out.println(articleId);
String type = map.get("type");
List<Comment> comments = this.commentService.getComment(articleId,type);
return R.ok().put("comments",comments);
}
}
3-2-2、server实现类
package cn.us.guiyoucircle.service.impl;
import cn.hutool.core.date.DateTime;
import cn.us.guiyoucircle.entity.Comment;
import cn.us.guiyoucircle.entity.Reply;
import cn.us.guiyoucircle.entity.User;
import cn.us.guiyoucircle.mapper.CommentMapper;
import cn.us.guiyoucircle.mapper.ReplyMapper;
import cn.us.guiyoucircle.service.CommentService;
import cn.us.guiyoucircle.service.UserService;
import cn.us.guiyoucircle.util.DateTimeResult;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 服务实现类
* </p>
*
* @author 李石林
* @since 2022-08-20
*/
@Service
public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> implements CommentService {
@Autowired
UserService userService;
@Autowired(required = false)
CommentMapper commentMapper;
@Autowired(required = false)
ReplyMapper replyMapper;
@Autowired
DateTimeResult dateTimeResult;
@Override
public List<Comment> getComment(long articleId, String type) {
List<Comment> comments = commentMapper.getComment(type,articleId);
List<Comment> commentsList = new ArrayList<>();
//对每一条评论,查找该评论的所有回复
for(Comment c: comments){
long userId = c.getFromUserid();
User user = userService.getById(userId);
c.setAvatarUrl(user.getAvatarUrl());
c.setNickName(user.getNickName());
//处理评论时间
String commentTimeRes = this.dateTimeResult.getTime(c.getCommentTime());
c.setCommentTimeRes(commentTimeRes);
c.setCanInputReply(200);
long commentId = c.getId();
//通过评论id区回复表找回复
List<Reply> replies = this.replyMapper.getReply(commentId);
List<Reply> replyList = new ArrayList<>();
//对每一条回复,设置头像和昵称
for(Reply r: replies){
long fromUserid = r.getFromUserid();
long toUserid = r.getToUserid();
String replyTimeRes = this.dateTimeResult.getTime(r.getReplyTime());
r.setReplyTimeRes(replyTimeRes);
User fromUser = this.userService.getById(fromUserid);
User toUser = this.userService.getById(toUserid);
r.setFromUserNickName(fromUser.getNickName());
r.setToUserNickName(toUser.getNickName());
r.setFromUserAvatarUrl(fromUser.getAvatarUrl());
r.setCanInputReply(200);
replyList.add(r);
}
c.setReplyies(replyList);
//将封装好的回复添加到list集合
commentsList.add(c);
}
//返回封装好的评论
return commentsList;
}
}
3-3、与回复有关的代码
3-3-1、Controller类
package cn.us.guiyoucircle.controller;
import cn.us.guiyoucircle.entity.Reply;
import cn.us.guiyoucircle.service.ReplyService;
import cn.us.guiyoucircle.util.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
/**
* <p>
* 前端控制器
* </p>
*
* @author 李石林
* @since 2022-08-20
*/
@RestController
@RequestMapping("//circle/reply")
public class ReplyController {
@Autowired
private ReplyService replyService;
@PostMapping("/sentReply")
public R sentReply(@RequestBody Reply reply){
reply.setReplyTime(LocalDateTime.now());
boolean f = this.replyService.save(reply);
if (f){
return R.ok();
}
return R.error();
}
}
就这样。
更多推荐
已为社区贡献1条内容
所有评论(0)