<template> <div class="comment-title flexacenter"> 讨论 <span class="comment-amount">{{ commentComments || "" }}</span> </div> <div class="post-comment flexacenter" ref="postInputRef" :class="{ 'post-comment-focus': postCommentFocusState }" @click="loginJudgment()"> <el-input class="post-input flex1" type="textarea" :autosize="postCommentFocusState" placeholder="说说你的想法或疑问…" v-model="commentInputTop" @blur="postCommentFocusBlur" @focus="postCommentFocusState = true"></el-input> <div class="post-ok flexcenter" @click="submitAnswerComments(commentInputTop)">发送</div> </div> <div class="empty-box" v-if="isEmptyState"> <Empty hint="说说你的观点吧"></Empty> </div> <template v-else> <div class="comment-list"> <div class="comment-item flexflex" v-for="(item, index) in commentList" :key="item.id"> <el-popover placement="bottom-start" :width="140" trigger="click" popper-class="avatar-box-popper" :show-arrow="false" v-model:visible="item['popoverState']"> <template #reference> <img class="comment-avatar" :src="item['avatar']" /> </template> <div class="avatar-box flexflex" v-if="item['uin']"> <a class="avatar-item flexcenter" target="_blank" @click.prevent="sendMessage(item['uin'])"> <img class="avatar-icon" src="@/assets/img/send-messages-icon.png" /> 发送信息 </a> <a class="avatar-item flexcenter" target="_blank" @click.prevent="TAHomePage(item['uin'])"> <img class="avatar-icon" src="@/assets/img/homepage-icon.png" /> TA的主页 </a> </div> </el-popover> <div class="comment-content flex1"> <div class="comment-header flexacenter"> <div class="comment-header-left flexacenter"> <div class="comments-username" @click="openAvatarPopover(index)">{{ item["nickname"] }}</div> <div class="comments-time">{{ handleDate(item["timestamp"]) }}</div> <div class="comments-identity" v-if="item['isauthor']">作者</div> <img class="comments-title" v-if="item['groupid'] === 14" src="@/assets/img/title.png" /> </div> <div class="comment-header-right flexacenter"> <div class="menu-box flexacenter"> <img class="menu-icon" src="@/assets/img/menu-icon-gray.svg" /> <div class="report-box flexcenter" @click="report(item['token'])">举报</div> </div> <img class="comment-icon" title="回复" @click="openAnswerCommentsChild(index)" src="@/assets/img/comment-icon-gray.svg" /> <div class="flexacenter like-box" @click="commentLike(index)"> <img class="like-icon" v-if="item['islike'] == 1" src="@/assets/img/like-icon-colours.png" /> <img class="like-icon" v-else src="@/assets/img/like-icon-gray.png" /> <div class="like-quantity">{{ item["likenum"] || 0 }}</div> </div> </div> </div> <div class="comment-text" @click="openAnswerCommentsChild(index)">{{ item["content"] }}</div> <div class="alreadyVoted" v-if="item.voteoption">已投:{{ item.voteoption }}</div> <div class="comments-input-box flexacenter" v-if="item['childState']"> <div class="comments-input flexflex"> <textarea class="flex1" placeholder="回复" v-model="commentInput"></textarea> <div class="comments-btn flexcenter" @click="submitAnswerComments(commentInput, index)">发送</div> </div> <img class="forkfork" @click="closeAnswerCommentsChild(index)" src="@/assets/img/cross-icon.png" /> </div> <!-- 子评论 --> <div class="child-comments" v-if="item['child'].length > 0"> <div class="comment-item flexflex" v-for="(ite, i) in item['child']" :key="ite.id"> <!-- <img class="comment-avatar" :src="ite['avatar']" /> --> <el-popover placement="bottom-start" :width="140" trigger="click" popper-class="avatar-box-popper" :show-arrow="false" v-model:visible="ite['popoverState']"> <template #reference> <img class="comment-avatar" :src="ite['avatar']" /> </template> <div class="avatar-box flexflex" v-if="ite['uin']"> <a class="avatar-item flexcenter" target="_blank" @click.prevent="sendMessage(ite['uin'])"> <img class="avatar-icon" src="@/assets/img/send-messages-icon.png" /> 发送信息 </a> <a class="avatar-item flexcenter" target="_blank" @click.prevent="TAHomePage(ite['uin'])"> <img class="avatar-icon" src="@/assets/img/homepage-icon.png" /> TA的主页 </a> </div> </el-popover> <div class="comment-content flex1"> <div class="comment-header flexacenter"> <div class="comment-header-left flexacenter"> <div class="comments-username" @click="openAvatarPopover(index, i)">{{ ite["nickname"] }}</div> <div class="comments-time">{{ handleDate(ite["timestamp"]) }}</div> <div class="comments-identity" v-if="ite['isauthor']">作者</div> <img class="comments-title" v-if="ite['groupid'] == 14" src="@/assets/img/title.png" /> </div> <div class="comment-header-right flexacenter"> <div class="menu-box flexacenter"> <img class="menu-icon" src="@/assets/img/menu-icon-gray.svg" /> <div class="report-box flexcenter" @click="report(ite['token'])">举报</div> </div> <img class="comment-icon" title="回复" @click="openAnswerCommentsChild(index, i)" src="@/assets/img/comment-icon-gray.svg" /> <div class="flexacenter like-box" @click="commentLike(index, i)"> <img class="like-icon" v-if="ite['islike'] == 1" src="@/assets/img/like-icon-colours.png" /> <img class="like-icon" v-else src="@/assets/img/like-icon-gray.png" /> <div class="like-quantity">{{ ite["likenum"] || 0 }}</div> </div> </div> </div> <div class="comment-text" @click="openAnswerCommentsChild(index, i)"> <div class="comments-reply" v-if="ite?.reply?.nickname">@{{ ite?.reply?.nickname }}</div> {{ ite["content"] }} </div> <div class="alreadyVoted" v-if="ite.voteoption">已投:{{ ite.voteoption }}</div> <div class="comments-input-box flexacenter" v-if="ite['childState']"> <div class="comments-input flexflex"> <textarea class="flex1" placeholder="回复" v-model="commentInput"></textarea> <div class="comments-btn flexcenter" @click="submitAnswerComments(commentInput, index, i)">发送</div> </div> <img class="forkfork" @click="closeAnswerCommentsChild(index, i)" src="@/assets/img/cross-icon.png" /> </div> </div> </div> </div> <!-- 还有几个 --> <div class="comments-also flexacenter" v-if="item['childnum'] > item['child'].length" @click="alsoCommentsData(index)"> <div class="">还有{{ item["childnum"] - item["child"].length }}条回复</div> <img class="also-icon" src="@/assets/img/arrow-circular-gray.png" /> </div> </div> </div> </div> <div class="comment-end" v-if="commentPage == 0 && commentList.length != 0">· End ·</div> </template> <Report v-if="reportAlertShow" :reportToken="reportToken"></Report> <!-- 投票后自动评论 --> <el-dialog class="default-popup automatic-reviews-popup" v-model="reviewsPopoverState" width="720px" align-center autosize> <div class="automatic-header"> <div class="automatic-title">说说您的投票理由</div> <div class="automatic-have">已投:{{ haveVotedValue }}</div> </div> <el-input class="automatic-input" placeholder="请输入…" v-model="reviewsPopoverInput" type="textarea"></el-input> <div class="automatic-bottom flexflex"> <div class="automatic-send flexcenter" @click="submitAnswerComments(reviewsPopoverInput)">发送</div> </div> </el-dialog> </template> <script setup> import { ElMessage } from "element-plus" import { isEmpty } from "element-plus/es/utils" let haveVotedValue = inject("haveVotedValue") let isNeedLogin = inject("isNeedLogin") const goLogin = inject("goLogin") const props = defineProps({ token: String }) watch( () => props.token, () => getCommentList(), { immediate: false } ) onMounted(() => window.addEventListener("scroll", handleScroll)) const sendMessage = inject("sendMessage") const TAHomePage = inject("TAHomePage") let postCommentFocusState = ref(false) let commentCount = ref(0) let commentComments = ref(0) // 所有的评论数 let commentPage = ref(1) let commentList = ref([]) let commentLoading = false let isEmptyState = ref(false) // 评论是否为空 // 获取详情评论数据 const getCommentList = () => { if (commentPage.value == 0 || commentLoading || !props.token) return commentLoading = true commentListHttp({ page: commentPage.value, childlimit: 1, limit: 10, token: props.token, }) .then(res => { if (res.code != 200) return let data = res.data commentCount.value = data["count"] if (data["count"] == 0) isEmptyState.value = true else isEmptyState.value = false commentList.value = commentList.value.concat(data["data"]) commentComments.value = data["comments"] if (commentList.value.length == data["count"]) commentPage.value = 0 else commentPage.value++ }) .finally(() => (commentLoading = false)) } // 评论点赞 const commentLike = (index, i) => { if (isNeedLogin.value) { goLogin() return } const targetCommentList = [...commentList.value] let token = "" if (i != null) token = targetCommentList[index]["child"][i].token else token = targetCommentList[index].token detailsLikeCommentHttp({ token }).then(res => { if (res.code != 200) return let data = res.data if (i != null) { targetCommentList[index]["child"][i].islike = data["status"] targetCommentList[index]["child"][i].likenum = data["likenum"] } else { targetCommentList[index].islike = data["status"] targetCommentList[index].likenum = data["likenum"] } ElMessage.success(res.message) }) } // 打开 回答-评论 的子评论 const openAnswerCommentsChild = (index, i) => { if (isNeedLogin.value) { goLogin() return } closeAnswerCommentsChild(false) if (i == null) commentList.value[index]["childState"] = true else commentList.value[index]["child"][i]["childState"] = true // commentInput.value = "" } // 关闭 回答-评论 的子评论 isempty 是否需要清空输入框 默认需要清空 const closeAnswerCommentsChild = () => { commentList.value.forEach(ele => { ele["childState"] = false if (ele["child"] && ele["child"].length != 0) ele["child"].forEach(el => (el["childState"] = false)) }) } // 讨论的输入框 let commentInputTop = ref("") let commentInput = ref("") // 提交回答-评论 const submitAnswerComments = (content, index, i) => { if (isNeedLogin.value) { goLogin() return } const targetCommentList = [...commentList.value] // let content = "" let parentid = null // if (index == null) content = commentInputTop.value // else content = commentInput.value if (i != null) parentid = targetCommentList[index]["child"][i]["id"] else if (index != null) parentid = targetCommentList[index]["id"] if (!content) { ElMessage.error("请填写评论内容") return } detailsSubmitommentListHttp({ content, token: props.token, parentid, }).then(res => { if (res.code != 200) { ElMessage.error(res.message) return } let data = res.data if (i != null) { let targetData = { id: data["commentid"], content, isauthor: 1, islike: 0, likenum: 0, reply: { nickname: targetCommentList[index]["child"][i]["nickname"], }, voteoption: haveVotedValue.value || null, ...data, } targetCommentList[index]["child"].unshift(targetData) targetCommentList[index]["childnum"]++ } else { let targetData = { id: data["commentid"], content, isauthor: 1, islike: 0, likenum: 0, ...data, child: [], voteoption: haveVotedValue.value || null, } if (index != null) { targetCommentList[index]["child"].unshift(targetData) targetCommentList[index]["childnum"]++ } else { targetCommentList.unshift(targetData) commentCount.value++ } } commentComments.value++ commentList.value = targetCommentList // 请求 输入框的数据 commentInputTop.value = "" commentInput.value = "" reviewsPopoverInput.value = "" reviewsPopoverState.value = false isEmptyState.value = false // 取消有可能的 没有评论 closeAnswerCommentsChild() if (bottomNavigationState) { bottomNavigationState = false floorCommentBtn("back") } ElMessage.success(res.message) }) } // 获取剩下的子评论 const alsoCommentsData = (index, ind) => { if (isNeedLogin.value) { goLogin() return } let targetCommentItem = { ...commentList.value[index] } const token = targetCommentItem["token"] const parentid = targetCommentItem["id"] let page = targetCommentItem["childPage"] ?? 1 detailsChildCommentListHttp({ childlimit: 1, limit: 10, page, parentid, token: props.token, }).then(res => { if (res.code != 200) return let data = res.data let childData = targetCommentItem.child.concat(data.data) const filteredData = childData.filter((obj, index, self) => { // 检查当前对象在数组中的第一个索引是否与当前索引相等 return self.findIndex(item => item.id == obj.id) == index }) targetCommentItem.child = filteredData targetCommentItem["childnum"] = data.count if (targetCommentItem.child.length == data["count"]) page = 0 else page++ targetCommentItem["childPage"] = page commentList.value[index] = targetCommentItem }) } let reportAlertShow = ref(false) let reportToken = ref("") // 点击打开举报 const report = token => { if (isNeedLogin.value) { goLogin() return } reportToken.value = token reportAlertShow.value = true } // 打开评论的 信息框 const openAvatarPopover = (index, i) => { if (isNeedLogin.value) { goLogin() return } if (i != null) commentList.value[index]["child"][i]["popoverState"] = true else commentList.value[index]["popoverState"] = true } // 监听滚动到底部 const handleScroll = () => { // return const scrollTop = document.documentElement.scrollTop || document.body.scrollTop const scrollHeight = document.documentElement.scrollHeight const clientHeight = document.documentElement.clientHeight // 列表下 滑动到底部 获取新数据 if (scrollTop + clientHeight >= scrollHeight - 40) getCommentList() } provide("reportAlertShow", reportAlertShow) // 登录判断 const loginJudgment = () => { if (isNeedLogin.value) goLogin() } // 修改投票的值 const changeCommentVoteoption = voteoption => { const uin = window["userInfoWin"]["uin"] commentList.value.forEach(element => { if (uin == element["uin"]) element["voteoption"] = voteoption element.child.forEach(el => { if (uin == element["uin"]) el["voteoption"] = voteoption }) }) } // 修改投票的值 const wipeCommentVoteoption = () => { const uin = window["userInfoWin"]["uin"] commentList.value.forEach(element => { if (uin == element["uin"]) element["voteoption"] = null element.child.forEach(el => { if (uin == element["uin"]) el["voteoption"] = null }) }) } let reviewsPopoverState = ref(false) // 自动投票弹窗状态 let reviewsPopoverInput = ref("") // 自动投票理由 // 调用自动评论 const reviewsComment = value => { reviewsPopoverState.value = true } let bottomNavigationState = false // 底部导航栏的 评论 const bottomNavigationBar = value => { bottomNavigationState = true submitAnswerComments(value) } const floorCommentBtn = inject("floorCommentBtn") const postInputRef = ref(null) const postCommentFocusBlur = () => { postCommentFocusState.value = false const refref = postInputRef.value nextTick(() => { let targetDom = refref.querySelector(".el-textarea__inner") targetDom.style.height = "" }) } defineExpose({ changeCommentVoteoption, wipeCommentVoteoption, reviewsComment, bottomNavigationBar }) </script> <style scoped lang="less"> .comment-title { font-weight: 650; color: #000000; font-size: 16px; margin-bottom: 16px; .comment-amount { color: #555; font-weight: 400; margin-left: 8px; } } .post-comment { background-color: rgba(255, 255, 255, 1); border: 1px solid rgba(215, 215, 215, 1); // border-right-width: 0; border-radius: 6px; margin-bottom: 30px; margin-right: 30px; transition: all 5s; justify-content: space-between; overflow: hidden; &.post-comment-focus { // border-right-width: 1px; flex-direction: column; .post-input { /deep/ .el-textarea__inner { width: 468px; min-height: 148px !important; max-height: 80vh; // height: 100% !important; } } .post-ok { align-self: flex-end; height: 32px; margin-bottom: 10px; margin-right: 10px; } } .post-input { background-color: transparent; font-size: 14px; resize: none; transition: all 0.5s; &::placeholder { color: #aaaaaa; } &::-webkit-scrollbar { width: 0 !important; } scrollbar-width: none; -ms-overflow-style: none; border: none; /deep/ .el-textarea__inner { border: none; box-shadow: none; resize: none; min-height: 60px !important; // height: 60px !important; padding: 10px; transition: all 0.5s; } } .post-ok { width: 60px; height: 62px; background-color: var(--main-color); color: #fff; font-size: 14px; cursor: pointer; border-radius: 6px; transition: all 0.5s; } } .comment-list { margin-bottom: 78px; .comment-item { &:not(:first-of-type) { .comment-avatar { margin-top: 10px; } .comment-header { padding-top: 10px; border-top: 1px dotted #d7d7d7; } } padding-right: 30px; .comment-avatar { width: 20px; height: 20px; border-radius: 50%; margin-right: 10px; cursor: pointer; } .comment-content { .comment-header { display: flex; justify-content: space-between; margin-bottom: 10px; .comment-header-left { font-size: 13px; .comments-avatar { width: 20px; height: 20px; margin-right: 10px; border-radius: 50%; } .comments-username { color: #555; margin-right: 10px; cursor: pointer; } .comments-time { color: #aaaaaa; margin-right: 10px; } .comments-title { height: 16px; } .comments-identity { font-size: 12px; color: #7f7f7f; padding: 0 3px; height: 20px; background-color: rgba(240, 242, 245, 1); border: 1px solid rgba(215, 215, 215, 1); border-radius: 5px; } } .comment-header-right { .menu-box { position: relative; &:hover .report-box { display: flex; } .menu-icon { width: 14px; height: 14px; cursor: pointer; } .report-box { display: none; position: absolute; top: 24px; right: 0; width: 60px; height: 24px; background-color: rgba(246, 246, 246, 1); border: 1px solid rgba(215, 215, 215, 1); border-radius: 5px; font-size: 12px; color: #7f7f7f; cursor: pointer; &::after { content: ""; width: 58px; height: 36px; position: absolute; top: -14px; right: 0; } } } .comment-icon { width: 14px; height: 13px; margin-left: 30px; cursor: pointer; } .like-box { font-size: 12px; color: #aaa; margin-left: 30px; cursor: pointer; .like-icon { width: 14px; height: 14px; } .like-quantity { margin-left: 6px; } } } } .comment-text { font-size: 14px; line-height: 22px; color: #333; margin-bottom: 10px; word-break: break-all; min-height: 22px; cursor: pointer; .comments-reply { color: #92a1bf; display: inline; } } .alreadyVoted { font-size: 12px; color: #aaaaaa; background-color: rgba(246, 246, 246, 1); line-height: 17px; width: fit-content; margin-bottom: 15px; word-break: break-word; } .comments-input-box { margin-top: 13px; margin-bottom: 10px; .comments-input { // width: 519px; flex: 1; height: 60px; border: 1px solid rgba(215, 215, 215, 1); border-right: none; border-radius: 8px; margin-right: 16px; position: relative; z-index: 1; &::after { content: ""; width: 20px; height: 20px; display: block; background-color: rgba(215, 215, 215, 1); position: absolute; top: -2px; left: 21px; transform: rotate(45deg); z-index: -1; } textarea { border: none; outline: none; resize: none; padding: 11px 16px; border-radius: 7px 0 0 7px; font-size: 14px; } .comments-btn { width: 58px; height: 58px; // background-color: #31d72e; background-color: var(--main-color); border-radius: 0 7px 7px 0; font-size: 14px; color: #ffffff; cursor: pointer; } } .forkfork { width: 12px; height: 12px; cursor: pointer; } } } .child-comments { .comment-avatar { margin-top: 10px; } .comment-header { padding-top: 10px; border-top: 1px dotted #d7d7d7; } .comment-item { padding-right: 0; } } .comments-also { color: #62b1ff; line-height: 22px; font-size: 13px; height: 46px; margin-left: 30px; cursor: pointer; border-top: 1px dotted #d7d7d7; .also-icon { width: 10px; height: 10px; margin-left: 8px; } } } } .comment-end { font-size: 12px; color: #d7d7d7; text-align: center; margin-bottom: 118px; padding-right: 30px; } .empty-box { padding: 80px 0 110px; } </style> <style> .automatic-reviews-popup { border-radius: 10px; .automatic-header { padding: 20px; border-bottom: 1px dotted #ebebeb; .automatic-title { font-weight: 650; font-size: 18px; color: #000000; margin-bottom: 12px; } .automatic-have { background-color: rgba(246, 246, 246, 1); font-size: 12px; color: #aaa; width: fit-content; } } .automatic-input { .el-textarea__inner { min-height: 256px !important; box-shadow: none; padding: 20px; resize: none; } } .automatic-bottom { justify-content: flex-end; padding: 0 10px 10px; .automatic-send { background-color: var(--main-color); color: #fff; font-size: 16px; width: 100px; height: 40px; border-radius: 6px; cursor: pointer; } } } </style>