2025-02-07 16:18:46 +08:00

637 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="answer-discuss">
<div class="header flexacenter">
回答&讨论
<span class="num">{{ commentTotalCount || "" }}</span>
</div>
<div class="input-box">
<div class="top flexflex">
<img class="avatar" v-if="user.avatar" :src="user.avatar" />
<textarea class="input-textarea flex1" maxlength="500" v-model="inputTextarea" @input="autoResize" @paste="handleInputPaste" placeholder="说说你的看法…"></textarea>
</div>
<div class="picture-box" v-if="picture.url">
<div class="picture">
<img class="close" @click="closeFileUpload()" src="/img/close-icon.png" />
<img class="img" @click="handleAnswerText" :src="picture.base64 || picture.url" />
</div>
</div>
<div class="bottom flexacenter">
<div class="operate flexacenter">
<div class="item" :class="{ 'pitch': emojiState }">
<img class="icon" src="/img/smiling-face.png" @click="openEmoji()" alt="" />
<div class="emoji-box">
<div class="emoji-icon" v-for="item in emojiData" :key="item" @click="selectEmoji(item)">{{ item }}</div>
</div>
</div>
<div class="item flexacenter">
<input class="file" type="file" @change="handleFileUpload($event)" accept=".png, .jpg, .jpeg" />
<img class="icon" src="/img/picture-icon.png" alt="" />
<span class="file-hint">最多可上传1张图片支持在输入框中直接粘贴图片。</span>
</div>
</div>
<div class="btn" @click="submitAnswerComments()">发送</div>
</div>
</div>
<div class="comments-box">
<div class="comments-item" v-for="(item, index) in commentList" :key="index">
<div class="comments-header flexacenter">
<div class="comments-header-left flexacenter">
<img class="comments-avatar" @click="openUserInfo(index)" :src="item['avatar']" />
<div class="comments-username" @click="openUserInfo(index)">{{ item["nickname"] || "匿名用户" }}</div>
<div class="comments-time">{{ handleDate(item["timestamp"]) }}</div>
<div class="comments-identity" v-if="item['isauthor'] == 1">提问者</div>
<div class="avatar-box flexflex" v-if="item['avatarState']">
<a class="avatar-item flexcenter" target="_blank" @click.prevent="sendMessage(item['uin'])">
<img class="avatar-icon" src="@/img/send-messages-icon.png" />
发送信息
</a>
<a class="avatar-item flexcenter" target="_blank" @click.prevent="TAHomePage(item['uin'])">
<img class="avatar-icon" src="@/img/homepage-icon.png" />
TA的主页
</a>
<div class="avatar-mask" @click="closeUserInfo(index)"></div>
</div>
</div>
<div class="comments-header-right flexacenter">
<div class="menu-box flexacenter" @click="openMenuState(index)">
<img class="menu-icon" src="/img/menu-icon-gray.svg" />
<div class="report-box flexcenter">举报</div>
</div>
<img class="comment-icon" @click="openAnswerCommentsChild(index)" src="/img/comment-icon-gray.svg" />
<div class="flexacenter like-box" @click="operateAnswerCommentsLike(item.token, index)">
<img class="like-icon" v-if="item['islike'] == 0" src="/img/like-icon-gray.png" />
<img class="like-icon" v-else src="/img/like-icon-colours.png" />
<div class="like-quantity">{{ item["likenum"] || "" }}</div>
</div>
</div>
</div>
<div class="comments-content">
<div class="comments-text" v-html="item['content']"></div>
<img class="comments-img" @click="handleAnswerText" :src="item.image?.base64 || item.image?.url" v-if="item.image?.url" />
<div class="input-box" v-if="item['childState']">
<img class="cross" @click="closeAnswerCommentsChild(index)" src="/img/cross-icon.png" />
<div class="top flexflex">
<textarea class="input-textarea flex1" maxlength="500" placeholder="说说你的看法…" v-model="item['commentInput']" @input="autoResize" @paste="handleInputPaste($event, index)"></textarea>
</div>
<div class="picture-box" v-if="item.picture?.url">
<div class="picture">
<img class="close" @click="closeFileUpload(index)" src="/img/close-icon.png" />
<img class="img" @click="handleAnswerText" :src="item.picture.base64 || item.picture.url" />
</div>
</div>
<div class="bottom flexacenter">
<div class="operate flexacenter">
<div class="item" :class="{ 'pitch': item.emojiState }">
<img class="icon" src="/img/smiling-face.png" @click="openEmoji(index)" alt="" />
<div class="emoji-box">
<div class="emoji-icon" v-for="item in emojiData" :key="item" @click="selectEmoji(item, index)">{{ item }}</div>
</div>
</div>
<div class="item flexacenter">
<input class="file" type="file" @change="handleFileUpload($event, index)" accept=".png, .jpg, .jpeg" />
<img class="icon" src="/img/picture-icon.png" alt="" />
<span class="file-hint">最多可上传1张图片支持在输入框中直接粘贴图片</span>
</div>
</div>
<div class="btn" @click="submitAnswerComments(index)">发送</div>
</div>
</div>
</div>
<div class="child-comments" v-if="item['child'].length != 0">
<div class="comments-item" v-for="(ite, i) in item['child']" :key="ite.id">
<div class="comments-header flexacenter">
<div class="comments-header-left flexacenter">
<img class="comments-avatar" @click="openUserInfo(index, i)" :src="ite['avatar']" />
<div class="comments-username" @click="openUserInfo(index, i)">{{ ite["nickname"] || "匿名用户" }}</div>
<div class="comments-time">{{ handleDate(ite["timestamp"]) }}</div>
<!-- <div class="comments-identity" v-if="ite['questioner'] == 1">提问者</div> -->
<!-- <div class="comments-identity" v-else-if="ite['isauthor'] == 1">回答者</div> -->
<div class="comments-identity" v-if="ite['isauthor'] == 1">提问者</div>
<div class="avatar-box flexflex" v-if="ite['avatarState']">
<a class="avatar-item flexcenter" target="_blank">
<img class="avatar-icon" src="@/img/send-messages-icon.png" @click.prevent="sendMessage(ite['uin'])" />
发送信息
</a>
<a class="avatar-item flexcenter" target="_blank" @click.prevent="TAHomePage(ite['uin'])">
<img class="avatar-icon" src="@/img/homepage-icon.png" />
TA的主页
</a>
<div class="avatar-mask" @click="closeUserInfo(index, i)"></div>
</div>
</div>
<div class="comments-header-right flexacenter">
<div class="menu-box flexacenter">
<img class="menu-icon" src="/img/menu-icon-gray.svg" />
<div class="report-box flexcenter">举报</div>
</div>
<img class="comment-icon" @click="openAnswerCommentsChild(index, i)" src="/img/comment-icon-gray.svg" />
<div class="flexacenter like-box" @click="operateAnswerCommentsLike(ite.token, index, i)">
<img class="like-icon" v-if="ite['islike'] == 0" src="/img/like-icon-gray.png" />
<img class="like-icon" v-else src="/img/like-icon-colours.png" />
<div class="like-quantity">{{ ite["likenum"] || "" }}</div>
</div>
</div>
</div>
<div class="comments-content">
<div class="comments-text">
<!-- <div class="comments-reply" v-if="JSON.stringify(ite['reply']) != '[]'">@{{ ite["reply"]["nickname"] }}</div> -->
<div class="comments-reply" v-if="ite?.reply?.nickname">@{{ ite["reply"]["nickname"] || "匿名用户" }}</div>
<span v-html="ite['content']"></span>
</div>
<img class="comments-img" @click="handleAnswerText" :src="ite.image?.base64 || ite.image?.url" v-if="ite.image?.url" />
<div class="input-box" v-if="ite['childState']">
<img class="cross" @click="closeAnswerCommentsChild(index)" src="/img/cross-icon.png" />
<div class="top flexflex">
<textarea class="input-textarea flex1" maxlength="500" v-model="ite['commentInput']" :placeholder="'回复“' + (ite['nickname'] || '匿名用户') + '”:'" @input="autoResize" @paste="handleInputPaste($event, index, i)"></textarea>
</div>
<div class="picture-box" v-if="ite.picture?.url">
<div class="picture">
<img class="close" @click="closeFileUpload(index, i)" src="/img/close-icon.png" />
<img class="img" @click="handleAnswerText" :src="ite.picture.base64 || ite.picture.url" />
</div>
</div>
<div class="bottom flexacenter">
<div class="operate flexacenter">
<div class="item" :class="{ 'pitch': ite.emojiState }">
<img class="icon" src="/img/smiling-face.png" @click="openEmoji(index, i)" alt="" />
<div class="emoji-box">
<div class="emoji-icon" v-for="item in emojiData" :key="item" @click="selectEmoji(item, index, i)">{{ item }}</div>
</div>
</div>
<div class="item flexacenter">
<input class="file" type="file" @change="handleFileUpload($event, index, i)" accept=".png, .jpg, .jpeg" />
<img class="icon" src="/img/picture-icon.png" alt="" />
<span class="file-hint">最多可上传1张图片支持在输入框中直接粘贴图片</span>
</div>
</div>
<div class="btn" @click="submitAnswerComments(index, i)">发送</div>
</div>
</div>
</div>
</div>
<div class="comments-also flexacenter" v-if="item['childnum'] > item['child'].length" @click="alsoCommentsData(index, i)">
<div class>还有{{ item["childnum"] - item.child.length }}条回复</div>
<img class="also-icon" src="/img/arrow-circular-gray.png" />
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { tr } from "element-plus/es/locale"
const props = defineProps({
token: String,
})
onMounted(() => {
// getComment()
})
const $ajax = inject("$ajax")
const $ajaxGET = inject("$ajaxGET")
const detailsToken = inject("detailsToken")
const handleDate = inject("handleDate")
const isNeedLogin = inject("isNeedLogin")
const handleMsg = inject("handleMsg")
const uploadImg = inject("uploadImg")
const handleAnswerText = inject("handleAnswerText")
const emojiMaskState = inject("emojiMaskState")
const user = inject("user")
const detailLoading = inject("detailLoading")
// 点击ta的主页
const TAHomePage = uin => {
redirectToExternalWebsite(`https://bbs.gter.net/home.php?mod=space&uid=${uin}`)
}
// 点击发送信息
const sendMessage = uin => {
if (uin && typeof messagePrivateItem == "function") {
messagePrivateItem({ uin: uin })
return
} else redirectToExternalWebsite(`https://bbs.gter.net/home.php?mod=space&showmsg=1&uid=${uin}`)
}
// 跳转 url
const redirectToExternalWebsite = url => {
const link = document.createElement("a")
link.href = url
link.target = "_blank"
link.click()
}
const commentList = ref([])
let commentCount = ref(0)
let commentTotalCount = ref(0)
let commentPage = ref(1)
let isgetCommentSate = false // 请求评论状态
let alreadyCommentIdList = []
const getComment = () => {
if (commentPage.value == 0 || isgetCommentSate) return
isgetCommentSate = true
$ajax("/api/comment/lists", {
token: detailsToken.value,
page: commentPage.value,
limit: 1500,
})
.then(res => {
if (res.code != 200) return
let data = res.data
data.data.forEach((element, index) => {
element["isReplyBoxShow"] = 0
// element.timestamp = util.timeformat(element.timestamp, 2)
if (element.child.length > 0) {
element.child.forEach(el => {
// el.timestamp = util.timeformat(el.timestamp, 2)
el["isReplyBoxShow"] = 0
})
}
})
if (commentPage.value > 1) {
let alreadyCommentIdList = alreadyCommentIdList
for (let index = 0; index < data.data.length; index++) {
if (alreadyCommentIdList.includes(data.data[index].id)) {
data.data.splice(index, 1)
index--
}
}
}
commentList.value = commentList.value.concat(data.data)
// console.log("commentList", commentList.value)
commentCount.value = data.count
commentTotalCount.value = data.comments
commentPage.value = data.count > commentList.length ? commentPage.value + 1 : 0
// console.log("commentList", commentList)
// this.setData({
// commentList,
// commentCount: data.count,
// commentTotalCount: data.comments,
// commentPage: data.count > commentList.length ? this.data.commentPage + 1 : 0,
// })
})
.finally(() => {
isgetCommentSate = false
})
}
let picture = ref({})
// 监听 detailsToken 的变化
watch(
detailsToken,
(newValue, oldValue) => {
if (newValue !== oldValue) {
commentPage.value = 1
commentList.value = []
picture.value = {}
commentTotalCount.value = 0
commentCount.value = 0
alreadyCommentIdList = []
getComment()
}
},
{ immediate: true }
)
const maxSize = 20 * 1024 * 1024 // 20MB
const handleFileUpload = (event, index, i) => {
closeEmoji()
const file = event.target.files[0] // 获取选择的文件
if (!file) return
if (file.size > maxSize) {
handleMsg("error", "文件大小不能超过 20MB")
return
}
const reader = new FileReader()
reader.onload = e => {
const base64 = e.target.result
uploadImg(base64).then(res => {
const obj = {
base64,
...res,
}
if (i != undefined) commentList.value[index].child[i]["picture"] = obj
else if (index != undefined) commentList.value[index]["picture"] = obj
else picture.value = obj
handleMsg("success", "上传成功")
})
}
reader.readAsDataURL(file)
}
// 删除上传的图片
const closeFileUpload = (index, i) => {
if (i != undefined) commentList.value[index].child[i]["picture"] = {}
else if (index != undefined) commentList.value[index]["picture"] = {}
else picture.value = {}
}
//
let emojiState = ref(false)
const emojiData = ["😀", "😁", "😆", "😅", "😂", "😉", "😍", "🥰", "😋", "😜", "🤪", "😎", "🤩", "🥳", "😔", "🙁", "😭", "😡", "😳", "🤗", "🤔", "🤭", "🤫", "😯", "😵", "🙄", "🥴", "🤢", "🤑", "🤠", "👌", "✌️", "🤟", "🤘", "🤙", "👍", "👎", "✊", "👏", "🤝", "🙏", "💪", "❤️", "💔", "🌹", "🥀", "🎉", "🎁", "🧧", "🌙", "⭐", "🌍", "💌", "📬", "🚗", "🚕", "🚲", "🛵", "🚀", "🚁", "⛵", "🚢", "🍎", "🍐", "🍊", "🍉", "🍓", "🍑", "🍔", "🍟", "🍕", "🥪", "🍜", "🍡", "🍨", "🍦", "🎂", "🍰", "🍭", "🍿", "🍩", "🧃", "🍹"]
// 打开 Emoji
const openEmoji = (index, i) => {
if (i != undefined) commentList.value[index].child[i]["emojiState"] = true
else if (index != undefined) commentList.value[index]["emojiState"] = true
else {
closeEmoji()
closeAnswerCommentsChild()
emojiState.value = true
}
emojiMaskState.value = true
}
// 关闭 Emoji
const closeEmoji = (index, i) => {
commentList.value.forEach(ele => {
ele["emojiState"] = false
if (ele["child"] && ele["child"].length != 0) {
ele["child"].forEach(el => {
el["emojiState"] = false
})
}
})
emojiState.value = false
emojiMaskState.value = false
}
defineExpose({
closeEmoji,
})
// 选择 Emoji
const selectEmoji = (key, index, i) => {
closeEmoji()
if (i != undefined) {
if (!commentList.value[index]["child"][i]["commentInput"]) commentList.value[index]["child"][i]["commentInput"] = ""
commentList.value[index]["child"][i]["commentInput"] += key
} else if (index != undefined) {
if (!commentList.value[index]["commentInput"]) commentList.value[index]["commentInput"] = ""
commentList.value[index]["commentInput"] += key
} else {
inputTextarea.value += key
}
}
// 自动输入框增高
const autoResize = e => {
e.target.style.height = "auto" // 重置高度
e.target.style.height = `${e.target.scrollHeight}px` // 设置为内容高度
}
const handleInputPaste = (event, index, ii) => {
const items = event.clipboardData.items // 获取粘贴的内容
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.type.startsWith("image/")) {
event.preventDefault()
const file = item.getAsFile() // 获取文件
if (file.size > maxSize) {
handleMsg("error", "文件大小不能超过 20MB")
return
}
const reader = new FileReader()
reader.onload = e => {
const base64 = e.target.result
uploadImg(base64).then(res => {
const obj = {
base64,
...res,
}
if (ii != undefined) commentList.value[index].child[ii]["picture"] = obj
else if (index != undefined) commentList.value[index]["picture"] = obj
else picture.value = obj
handleMsg("success", "上传成功")
})
}
reader.readAsDataURL(file)
}
}
}
let inputTextarea = ref("")
// 提交回答-评论
const submitAnswerComments = (index, i) => {
if (isNeedLogin.value) {
goLogin()
return
}
let content = ""
let parentid = null
let token = detailsToken.value
let image = {}
if (i != null) {
content = commentList.value[index]["child"][i]["commentInput"]
parentid = commentList.value[index]["child"][i]["id"]
image = commentList.value[index]["child"][i]["picture"]
} else if (index != null) {
content = commentList.value[index]["commentInput"]
parentid = commentList.value[index]["id"]
image = commentList.value[index]["picture"]
} else {
content = inputTextarea.value
image = picture.value
}
detailLoading.value = true
$ajax("/api/comment/submit", {
content,
token,
parentid,
image: image ? { aid: image.aid, url: image.url } : null,
})
.then(res => {
if (res.code != 200) return
let data = res.data
if (i != null) {
let targetData = {
id: data["commentid"],
content,
isauthor: 1,
islike: 0,
likenum: 0,
reply: {
nickname: commentList.value[index]["child"][i]["nickname"],
},
...data,
image,
}
commentList.value[index]["child"].push(targetData)
commentList.value[index]["childnum"]++
} else if (index != null) {
let targetData = {
id: data["commentid"],
content,
isauthor: 1,
islike: 0,
likenum: 0,
reply: [],
...data,
image,
}
commentList.value[index]["child"].unshift(targetData)
commentList.value[index]["childnum"]++
} else {
let targetData = {
id: data["commentid"],
content,
isauthor: 1,
islike: 0,
likenum: 0,
...data,
child: [],
image,
}
commentList.value.unshift(targetData)
// commentCount.value++
inputTextarea.value = ""
picture.value = {}
}
commentTotalCount.value = data.count || 0
// targetAnswerList[index]["commentnum"] = data["count"]
closeAnswerCommentsChild()
handleMsg("success", res["message"] || "操作成功")
})
.finally(() => {
detailLoading.value = false
})
}
// 回答-评论 点赞
const operateAnswerCommentsLike = (token, index, i) => {
if (isNeedLogin.value) {
goLogin()
return
}
$ajax("/api/comment/like", {
token,
}).then(res => {
if (res.code != 200) return
let data = res.data
if (i != undefined) {
commentList.value[index].child[i]["islike"] = data["status"]
commentList.value[index].child[i]["likenum"] = data["likenum"]
} else {
commentList.value[index]["islike"] = data["status"]
commentList.value[index]["likenum"] = data["likenum"]
}
handleMsg("success", res["message"] || "操作成功")
})
}
// 打开 回答-评论 的子评论
const openAnswerCommentsChild = (index, i) => {
// console.log("isNeedLogin", isNeedLogin)
if (isNeedLogin.value) {
goLogin()
return
}
closeAnswerCommentsChild()
if (i == null) commentList.value[index]["childState"] = true
else commentList.value[index]["child"][i]["childState"] = true
}
// 关闭 回答-评论 的子评论
const closeAnswerCommentsChild = () => {
commentList.value.forEach(ele => {
ele["childState"] = false
ele["commentInput"] = "" // 删除原本输入值
if (ele["child"] && ele["child"].length != 0) {
ele["child"].forEach(el => {
el["childState"] = false
el["commentInput"] = ""
})
}
})
}
// 获取剩下的子评论
const alsoCommentsData = (index, i) => {
// const targetAnswerList = [...answerList.value]
const parentid = commentList.value[index]["id"]
const token = detailsToken.value
$ajax("/api/comment/childrenList", {
token,
parentid,
limit: 2000,
page: 1,
childlimit: 3,
}).then(res => {
if (res.code != 200) return
let data = res.data
let merged = [...commentList.value[index]["child"], ...data.data.filter(item2 => !commentList.value[index]["child"].find(item1 => item1.id == item2.id))]
commentList.value[index]["child"] = merged
// answerList.value = targetAnswerList
})
}
const handleMenuState = inject("handleMenuState")
const openMenuState = (index, i) => {
if (isNeedLogin.value) {
goLogin()
return
}
let reportToken = ""
if (i === undefined) reportToken = commentList.value[index]["token"]
else reportToken = commentList.value[index]["child"][i]["token"]
handleMenuState(reportToken)
}
const openUserInfo = (index, i) => {
if (i != undefined && commentList.value[index].child[i]["uin"] > 0) commentList.value[index].child[i]["avatarState"] = true
else if (index != undefined && commentList.value[index]["uin"] > 0) commentList.value[index]["avatarState"] = true
}
const closeUserInfo = (index, i) => {
if (i != undefined) commentList.value[index].child[i]["avatarState"] = false
else if (index != undefined) commentList.value[index]["avatarState"] = false
}
</script>
<style scoped></style>