效果图:
评论组件:
<template>
<div class="comment-view">
<div v-show="isShowInput" id="commentForm" class="comment-form white-box">
<div class="form-box">
<el-input
ref="commentInput"
v-model="content"
type="textarea"
:rows="3"
:autofocus="true"
resize="none"
placeholder="请输入内容"
></el-input>
<el-button :loading="isLoading" :disabled="isLoading" class="primary" size="small" @click="commentSubmit">发布</el-button>
</div>
</div>
<div ref="commentCt" class="comment-ct white-box">
<div class="tit">
评论<span v-if="count">({{ count }})</span>
</div>
<div class="comment-list">
<div v-for="(item, index) in commentList" :key="index" class="item">
<a class="left-avatar" :href="`/user/${item.memberId}`">
<el-image :src="item.avatar" fit="cover" class="avatar"></el-image>
</a>
<div class="right-info">
<div class="person">
<div class="name-time">
<a class="name" :href="`/user/${item.memberId}`">{{ item.nickname }}</a>
<SvgIcon v-if="item.isExpert === 1" icon="auth"></SvgIcon>
<el-tag v-if="item.isAuthor === 1" color="#F3F3F3" type="info" size="mini">作者</el-tag>
<div class="time">{{ item.releaseTime | dateFormat }}</div>
</div>
</div>
<div class="info">{{ item.content }}</div>
<ItemOperation
:operStaus="[
{ icon: 'o-nice', key: 'giveCount', isOperate: true },
{ icon: 'o-comment', text: '回复', isOperate: true },
{ icon: 'o-report', text: '举报', isOperate: true },
]"
:data="item"
:type="5"
@handleComment="handleComment"
>
</ItemOperation>
<div v-if="formId === item.id" class="item-form-box">
<el-input v-model="formValue" :autofocus="true" :placeholder="formPlaceholder"></el-input>
<el-button
class="primary"
size="small"
:loading="isLoading"
:disabled="isLoading"
@click="replySubmit(item.id, item.memberId, 1)"
>发布</el-button>
</div>
<div class="children-list">
<div
v-for="(it, i) in item.children"
v-show="i < showCount || openIds.includes(item.id)"
:key="i"
class="item"
>
<a class="left-avatar" :href="`/user/${it.memberId}`">
<el-image :src="it.avatar" fit="cover" class="avatar"></el-image>
</a>
<div class="right-info">
<div class="person">
<div class="name-time">
<a class="name" :href="`/user/${it.memberId}`">{{ it.nickname }}</a>
<SvgIcon v-if="it.isExpert === 1" icon="auth"></SvgIcon>
<el-tag v-if="it.isAuthor === 1" color="#F3F3F3" type="info" size="mini">作者</el-tag>
<div v-if="it.beNickname" class="reply"><span>回复</span> {{ it.beNickname }}</div>
<div class="time">{{ it.releaseTime | dateFormat }}</div>
</div>
</div>
<div class="info">{{ it.content }}</div>
<ItemOperation
:operStaus="[
{ icon: 'o-nice', key: 'giveCount', isOperate: true },
{ icon: 'o-comment', text: '回复', isOperate: true },
{ icon: 'o-report', text: '举报', isOperate: true },
]"
:data="it"
:type="5"
@handleComment="handleComment"
/>
<div v-if="formId === it.id" class="item-form-box">
<el-input v-model="formValue" :autofocus="true" :placeholder="formPlaceholder"></el-input>
<el-button
class="primary"
size="small"
:loading="isLoading"
:disabled="isLoading"
@click="replySubmit(item.id, it.memberId, 2)"
>发布</el-button>
</div>
</div>
</div>
<el-link
v-if="item.children.length > showCount && !openIds.includes(item.id)"
class="link"
type="primary"
:underline="false"
@click="openCommit(item)"
>展开{{ item.children.length - showCount }}条评论</el-link>
</div>
</div>
</div>
</div>
<Empty :type="loadingType" @loading="loading" />
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { controlComment, getCommentList } from "@api/public/index";
import { commentItem } from "@api/public/index.d";
interface DomEle extends Element {
offsetTop: number;
}
@Component({
name: "CommentView",
components: {},
})
export default class CommentView extends Vue {
@Prop({
type: Number,
default: 0,
required: true,
})
readonly sourceId!: number; // 来源文章id
openIds: number[] = [];
isLoading = false;
showCount = 1; // 控制第一次加载几条回复
content = ""; // 发布评论输入框内容
formValue = ""; // 回复评论输入框内容,控制评论中的评论框内容
formId: number | string = ""; // 控制评论中的评论框的展示
formPlaceholder = ""; // 回复输入框的提示语
commentList: commentItem[] = [];
listQuery = {
page: 1,
limit: 5,
};
count = 0;
loadingType = 0;
isShowInput = false;
// 滚动到评论输入框并聚焦
pageScrollFn() {
this.isShowInput = !this.isShowInput;
if (this.isShowInput) {
this.content = "";
this.formId = "";
this.$nextTick(() => {
const formDom: DomEle | null = document.querySelector("#commentForm");
formDom &&
window.scrollTo({
top: formDom.offsetTop - 160,
behavior: "smooth",
});
(this.$refs.commentInput as any).focus();
});
}
}
// 滚动到评论
scrollComment() {
this.$nextTick(() => {
const formDom: any = this.$refs.commentCt;
formDom &&
window.scrollTo({
top: formDom.offsetTop - 80,
behavior: "smooth",
});
});
}
// 展示回复输入框并清空值
handleComment(info: any) {
if (this.formId === info.id) {
this.formId = "";
} else {
this.formId = info.id;
}
this.formValue = "";
this.formPlaceholder = `回复:${info.nickname}`;
}
// 控制显示回复数量
openCommit(item: { id: number }) {
const i = this.openIds.findIndex(x => x === item.id);
if (i !== -1) {
this.openIds.splice(i, 1);
} else {
this.openIds.push(item.id);
}
}
/**
* @description: 发布评论/回答
* @param {*}
* @return {*}
*/
async commentSubmit() {
const nowPath = this.$route.fullPath;
await this.$loginDialog({ nowPath });
if (!this.content) {
this.$message("请填写评论内容");
return;
}
this.isLoading = true;
const json = {
content: this.content,
pid: 0,
sourceId: this.sourceId,
};
try {
await controlComment(json);
this.isLoading = false;
this.content = "";
this.listQuery.page = 1;
this.getCommentList();
this.$emit("refresh");
} catch (error) {
this.isLoading = false;
}
}
/**
* @description: 回复
* @param {*} pid 一级评论id
* @param {*} beMemberId 被评论用户id
* @param {*} type 1 回复评论 回复回复
* @return {*}
*/
async replySubmit(pid: number, beMemberId: number, type: number) {
const nowPath = this.$route.fullPath;
await this.$loginDialog({ nowPath });
if (!this.formValue) {
this.$message("请填写评论内容");
return;
}
this.isLoading = true;
const json: any = {
content: this.formValue,
pid,
sourceId: this.sourceId,
};
if (type === 2) {
json.beMemberId = beMemberId;
}
try {
await controlComment(json);
this.isLoading = false;
this.formId = "";
this.formValue = "";
const {
data: { list, count },
} = await getCommentList({ sourceId: this.sourceId, page: 1, limit: this.commentList.length });
this.commentList = list;
this.count = count;
const i = this.openIds.findIndex(x => x === pid);
if (i === -1) {
this.openIds.push(pid);
}
this.$emit("refresh");
} catch (error) {
this.isLoading = false;
}
}
/**
* @description: 获取案例/知识评论列表
* @return {*}
*/
async getCommentList() {
this.loadingType = 1;
const {
data: { list, count },
} = await getCommentList({ sourceId: this.sourceId, ...this.listQuery });
if (count === 0) {
this.loadingType = 3;
} else if (this.listQuery.page * this.listQuery.limit >= count) {
this.loadingType = 2;
} else {
this.loadingType = 0;
}
this.count = count;
if (this.listQuery.page === 1) {
this.commentList = list;
} else {
this.commentList = [...this.commentList, ...list];
}
}
/**
* @description: 加载更多
* @return {*}
*/
loading() {
this.listQuery.page++;
this.getCommentList();
}
}
</script>
<style scoped lang="scss">
.comment-view {
.form-box {
background: #ecf0ff;
text-align: right;
border-radius: 4px;
padding: 15px;
/deep/ .el-textarea {
.el-textarea__inner {
background: transparent;
border: 0;
font-size: 14px;
color: #000;
height: 80px;
padding: 0;
}
}
.el-button {
margin-top: 15px;
}
}
.item-form-box {
margin-top: 15px;
display: flex;
align-items: center;
background: #ecf0ff;
border-radius: 4px;
padding: 12px;
/deep/ .el-input {
&__inner {
background: #fff;
border: 0;
font-size: 14px;
color: #000;
padding: 0 12px;
}
}
.el-button {
margin-left: 12px;
}
}
.comment-form {
margin-top: 20px;
}
.comment-ct {
margin-top: 20px;
.tit {
position: relative;
padding-left: 20px;
font-size: 24px;
font-weight: 600;
@media (max-width: 767px) {
font-size: 20px;
}
@media (min-width: 768px) and (max-width: 991px) {
font-size: 22px;
}
&::before {
content: "";
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 6px;
height: 100%;
background: #1347f2;
}
}
.comment-list {
.item {
display: flex;
margin-top: 18px;
.left-avatar {
flex: 0 0 40px;
height: 40px;
margin-right: 8px;
cursor: pointer;
}
.right-info {
flex: 1;
&::after {
content: "";
display: block;
height: 1px;
background: #e4eaff;
margin-top: 18px;
}
.person {
// line-height: 30px;
.name-time {
display: flex;
align-items: center;
.name {
text-decoration: none;
color: #333;
font-size: 16px;
font-weight: bold;
margin-right: 4px;
cursor: pointer;
}
/deep/ .svg-icon {
width: 16px;
height: 16px;
}
/deep/.el-tag {
// margin: 0 8px;
margin-left: 8px;
}
.reply {
font-size: 14px;
color: #333333;
span {
color: #cccccc;
margin: 0 6px;
}
}
.time {
font-size: 14px;
color: #ccc;
padding-left: 10px;
}
}
}
.info {
margin: 5px 0 12px;
font-size: 16px;
color: #707070;
}
/deep/ .item-operation {
.tag {
padding: 1px 8px;
border-radius: 4px;
border: 1px solid #1347f2;
font-size: 14px;
color: #1347f2;
@media (max-width: 1199px) {
font-size: 12px;
}
&.active {
border: 1px solid #52c41a;
color: #52c41a;
}
}
}
.form-box {
margin-top: 10px;
}
}
}
}
.children-list {
.item {
.right-info {
&::after {
display: none;
}
}
.person {
line-height: 22px;
}
.info {
margin: 10px 0 10px;
}
.left-avatar {
flex: 0 0 26px;
height: 26px;
margin-right: 5px;
cursor: pointer;
}
}
.link {
margin-top: 10px;
}
}
.btn-box {
margin-top: 20px;
.el-button {
width: 100%;
}
}
}
}
</style>
接口数据处理:可直接赋值给commentList看静态效果
[
{
id: 37,
pid: 0,
memberId: 100020,
content: "写的太烂了。。。",
beMemberId: 100014,
beNickname: "cz6",
releaseTime: "2022-06-14 11:25",
nickname: "xxx名字",
avatar:
"https://xchlwkj.oss-cn-shenzhen.aliyuncs.com/2022/05/13/member-default-avatar.png.png",
isExpert: 1,
isAuthor: 2,
isGive: 2,
giveCount: 0,
level: 1,
children: [
{
id: 66,
pid: 37,
memberId: 100014,
content: "瞎说什么大实话",
beMemberId: 100020,
beNickname: "xxx名字",
releaseTime: "1小时前",
nickname: "cz6",
avatar:
"https://thirdwx.qlogo.cn/mmopen/vi_32/mtPRUG9a4Ond75av9Rb0kibxLwNebGxeveCFM3M4v4XE59NIHYAPGkquld3mQ18QWic08MOrzQhBPZibMlLrTBGsQ/132",
isExpert: 1,
isAuthor: 1,
isGive: 2,
giveCount: 0,
level: 2,
children: [],
},
{
id: 55,
pid: 37,
memberId: 100014,
content: "xxx0614编辑",
beMemberId: 100014,
beNickname: "cz6",
releaseTime: "2022-06-14 16:06",
nickname: "cz6",
avatar:
"https://thirdwx.qlogo.cn/mmopen/vi_32/mtPRUG9a4Ond75av9Rb0kibxLwNebGxeveCFM3M4v4XE59NIHYAPGkquld3mQ18QWic08MOrzQhBPZibMlLrTBGsQ/132",
isExpert: 1,
isAuthor: 1,
isGive: 2,
giveCount: 0,
level: 2,
children: [],
},
],
},
{
id: 36,
pid: 0,
memberId: 100014,
content: "写的不错",
beMemberId: 100014,
beNickname: "cz6",
releaseTime: "2022-06-14 11:22",
nickname: "cz6",
avatar:
"https://thirdwx.qlogo.cn/mmopen/vi_32/mtPRUG9a4Ond75av9Rb0kibxLwNebGxeveCFM3M4v4XE59NIHYAPGkquld3mQ18QWic08MOrzQhBPZibMlLrTBGsQ/132",
isExpert: 1,
isAuthor: 1,
isGive: 1,
giveCount: 1,
level: 1,
children: [
{
id: 41,
pid: 36,
memberId: 100012,
content: "cz6YYDS",
beMemberId: 100014,
beNickname: "cz6",
releaseTime: "2022-06-14 11:35",
nickname: "不可思议",
avatar:
"https://xchlwkj.oss-cn-shenzhen.aliyuncs.com/2022/05/13/member-default-avatar.png.png",
isExpert: 2,
isAuthor: 2,
isGive: 2,
giveCount: 0,
level: 2,
children: [],
},
],
},
];
本文介绍如何在Vue项目中使用ElementUI封装一个评论组件。通过接口获取数据后进行处理,将评论列表(commentList)赋值展示,实现静态效果。

3809

被折叠的 条评论
为什么被折叠?



