
实现代码
<template>
<el-card>
<el-row :gutter="10">
<el-col :span="18">
<div class="comment">
<div>
<div class="title">{{ title }}</div>
<!-- 评论框 -->
<div v-clickoutside="hideReplyBtn" @click="inputFocus" class="my-reply mt20">
<el-avatar class="header-img" :size="40" :src="avatar"></el-avatar>
<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="item.id" class="author-title reply-father">
<el-avatar class="header-img" :size="40" :src="item.user.headimage"></el-avatar>
<div class="author-info">
<span class="author-name">{{ item.user.nickname }}</span><span class="author" v-if="item.label_text">{{item.label_text}}</span>
<p>{{ item.text }}</p>
<span class="author-time">{{ item.dateupdated }}</span>
</div>
<div class="icon-btn">
<!-- 编辑 -->
<span v-if="item.user.id == userObj.ownerid" @click="showCommentInput(item, i)">
<i class="iconfont el-icon-edit"></i>
</span>
<!-- 回复 -->
<span @click="showReplyInput(item, i)">
<i class="iconfont el-icon-s-comment"></i><span v-if="item.reply_comment_total">{{ item.reply_comment_total }}</span>
</span>
</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_comment" :key="reply.id" class="author-title">
<el-avatar class="header-img" :size="40" :src="reply.user.headimage"></el-avatar>
<div class="author-info">
<span class="author-name">{{ reply.user.nickname }}</span>
<span class="author-time">{{ reply.dateupdated }}</span>
</div>
<!-- 编辑 -->
<span v-if="reply.user.id == userObj.ownerid" @click="showCommentInput(item,i,reply, j)">
<i class="iconfont el-icon-edit"></i>
</span>
<!-- 回复 -->
<div class="icon-btn">
<span @click="showReplyInput(reply,i,reply)"><i class="iconfont el-icon-s-comment"></i>{{ reply.commentNum }}</span>
</div>
<div class="talk-box">
<p v-if="reply.reply_to_username">
回复<span> @{{ reply.reply_to_username }}: </span>
<span class="reply"> {{ reply.text }}</span>
</p>
<p v-else>
<span class="reply"> {{ reply.text }}</span>
</p>
</div>
<div class="reply-box"></div>
</div>
</div>
<div style="margin-left:50px;padding:0 0 10px; 0" v-if="item.reply_comment_total > item.reply_comment.length">
<el-button class="mt20" @click="moreReply(item,i)">点击查看更多</el-button>
</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">
<div tabindex="0" contenteditable="true" spellcheck="false" :placeholder="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(item,i,reply,j)" type="primary">发表评论</el-button>
<el-button class="reply-btn" size="medium" @click="cancelComment(item,i)">取消</el-button>
</div>
</div>
</div>
<div class="reply-box" v-if="total > comments.length">
<el-button type="primary" class="mt20" @click="moreComment()">点击查看更多</el-button>
</div>
</div>
</div>
</el-col>
</el-row>
</el-card>
</template>
<script>
import {
commentList,
replyList,
commentEdit,
replyEdit
} from "@/api/eduOnline/lily";
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 {
title: "", //评论文章的标题
data: [],
placeholder: "", // 回复者名称
btnShow: false,
index: 0,
replyComment: "", // 评论输入内容
avatar: "https://s1.ax1x.com/2022/06/10/Xc9lUf.png", //默认用户头像
parentName: "", // 回复的对象(父评论)用户名
parentId: 0, // 父id
info: {
allianceid: 0,
infoid: 0,
text: "",
id: "",
page: 1,
size: 5,
},
replyInfo: {
allianceid: 52,
infoid: 1,
reply_id: "",
page: 1,
size: 5,
},
userObj: {
orgid: this.$store.state.user.orgid,
orgname: this.$store.state.user.orgname,
ownerid: this.$store.state.user.userId,
owner: this.$store.state.user.username,
level: this.$store.state.user.level,
},
comments: [],
reply: [],
replyObj: {}, // 记录点击当前回复的信息
isReply: false,
replyPage: {},
total: 0,
};
},
directives: { clickoutside },
created() {
if (this.$route.query) {
this.info.allianceid = this.$route.query.aid;
this.info.infoid = this.$route.query.infoid;
this.replyInfo.allianceid = this.$route.query.aid;
this.replyInfo.infoid = this.$route.query.infoid;
this.title = this.$route.query.title;
}
this.getCommentList();
},
methods: {
// 点击查看更多回复
moreReply(item, i) {
console.log(this.replyInfo.page);
console.log("查看更多回复", i);
this.replyInfo.reply_id = item.id;
console.log("this.replyPage", this.replyPage);
if (
JSON.stringify(this.replyPage) != "{}" &&
this.replyPage[this.replyInfo.reply_id]
) {
this.replyInfo.page = this.replyPage[this.replyInfo.reply_id];
} else {
this.replyInfo.page = 1;
}
console.log("当前回复页码", this.replyInfo);
this.getReplyList(this.replyInfo, i);
},
//点击回复, 展示评论框
showReplyInput(item, i, reply) {
this.replyObj = {};
this.info.id = "";
this.isReply = false;
if (reply) {
this.replyObj = reply;
}
console.log("回复", this.index);
this.comments[this.index].inputShow = false;
this.index = i;
this.comments[i].inputShow = true;
this.parentName = item.user.nickname;
this.parentId = item.id;
this.placeholder = "回复 @" + item.user.nickname;
document.getElementsByClassName("reply-comment-input")[i].innerHTML = "";
this.info.id = "";
//alert(i)
},
// 点击编辑,展示评论框
showCommentInput(item, i, reply, j) {
console.log("编辑");
console.log(this.index);
console.log(i);
this.comments[this.index].inputShow = false;
this.index = i;
this.comments[i].inputShow = true;
this.placeholder = "1";
if (reply) {
this.replyObj = reply;
this.isReply = true;
this.replyComment = reply.text;
} else {
this.isReply = false;
this.info.id = item.id;
this.replyComment = item.text;
}
document.getElementsByClassName("reply-comment-input")[i].innerHTML =
this.replyComment;
},
// 评论获取焦点
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";
},
// 关闭评论框
cancelComment(item, i) {
this.isReply = false;
this.info.id = "";
this.replyObj = {};
this.comments[i].inputShow = false;
this._inputShow(i);
},
_inputShow(i) {
return this.comments[i].inputShow;
},
// 新增评论
sendComment() {
// 父评论
if (!this.replyComment) {
this.$message({
showClose: true,
type: "warning",
message: "评论不能为空",
});
} else {
commentEdit(Object.assign({}, this.userObj, this.info))
.then((res) => {
if (res.code == "0000") {
console.log(res.data);
this.$message.success("评论成功!");
this.getCommentList();
} else {
this.$message.error("评论失败,请稍后重试!");
}
})
.finally(() => {});
//
document.getElementById("replyInput").innerHTML = "";
this.replyComment = "";
}
},
// 点击查看更多评论
moreComment() {
this.info.page++;
this.getCommentList();
},
// 评论列表
getCommentList() {
commentList(this.info).then((res) => {
this.replyPage = {};
if (res.code == "0000") {
this.total = res.data.total;
if (this.info.page == 1) {
this.comments = res.data.data;
} else {
this.comments.push(...res.data.data);
}
this.comments.forEach((item) => {
this.$set(item, "inputShow", false);
});
}
});
},
// 回复列表
getReplyList(obj, i) {
replyList(obj).then((res) => {
if (res.code == "0000") {
if (this.replyInfo.page == 1) {
this.comments[i].reply_comment = res.data.data;
} else {
this.comments[i].reply_comment.push(...res.data.data);
}
this.replyInfo.page++;
this.replyPage[obj.reply_id] = this.replyInfo.page++;
// console.log(this.comments);
}
});
},
// 新增、修改回复,或修改评论
sendCommentReply(item, i, reply, j) {
// 定义回复的字段
let obj = {};
// 定义临时字段,用于存储获取到的回复信息
let replyObj = {};
if (this.replyObj) {
replyObj = JSON.parse(JSON.stringify(this.replyObj));
}
// 修改回复
if (this.isReply) {
obj.id = replyObj.id;
}
// 数据获取后,存储的数据清空,以防数据错乱
this.replyObj = {};
// 给回复进行回复
if (JSON.stringify(replyObj) != "{}") {
obj.reply_to_reply_id = replyObj.reply_id;
obj.reply_to_userid = replyObj.user.id;
}
// 给评论进行回复
obj.reply_id = item.id;
obj.allianceid = 52;
obj.infoid = item.infoid;
obj.text = this.replyComment;
if (!this.replyComment) {
this.$message({
showClose: true,
type: "warning",
message: "评论不能为空",
});
} else {
console.log(obj);
// 评论
if (this.info.id) {
commentEdit(Object.assign({}, this.userObj, this.info))
.then((res) => {
if (res.code == "0000") {
console.log(res.data);
this.$message.success("评论成功!");
this.getCommentList();
this.info.id = "";
} else {
this.$message.error("评论失败,请稍后重试!");
}
})
.finally(() => {});
} else {
// 回复
replyEdit(Object.assign({}, obj, this.userObj))
.then((res) => {
if (res.code == "0000") {
this.$message.success("回复成功!");
this.getCommentList();
} else {
this.$message.error("回复失败,请稍后重试!");
}
})
.finally(() => {});
}
this.replyComment = "";
document.getElementsByClassName("reply-comment-input")[i].innerHTML =
"";
}
},
onDivInput: function (e) {
this.replyComment = e.target.innerText;
this.info.text = e.target.innerText;
},
},
};
</script>
<style>
.title {
padding: 5px 10px;
background-color: #e8f4ff;
border-color: #d1e9ff;
color: #1890ff;
height: 54px;
line-height: 46px;
border-radius: 4px;
display: inline-block;
}
.mt20 {
margin-top: 20px;
}
.f24 {
font-size: 24px;
}
.comment {
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: #666;
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;
}
.author-name {
padding: 0 6px;
display: inline-block !important;
}
.author {
background: #ffba00;
border-radius: 2px;
display: inline-block;
width: 40px;
text-align: center;
color: #fff;
}
</style>