PC-mj/pages/details/[id].vue
2025-03-21 16:02:51 +08:00

2207 lines
93 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>
<Head>
<Title>{{ `${seo["title"] || "面经"} - 寄托天下出国留学网` }}</Title>
<Meta name="keyword" :content="seo['keyword']" />
<Meta name="description" :content="seo['description']" />
</Head>
<!-- <div @click="router.push(`/index.html`)">1111</div> -->
<!-- <PageHeade></PageHeade> -->
<div>
<TopHead ref="topHeadRef"></TopHead>
<div class="content flexflex">
<div class="left" :style="{ height: contentRightHeight + 'px' }">
<div class="school-box flexcenter" :style="{ 'pointer-events': info['school']?.['url'] ? 'auto' : 'none' }">
<a class="school-box-icon" :href="info['school']?.['url']" target="_blank"><img class="school-icon" v-if="info['school']?.['image']" :src="info['school']?.['image']" /></a>
<a class="school-name" :href="info['school']?.['url']" target="_blank">{{ info["school"]?.["name"] }}</a>
<a class="school-en-name" :href="info['school']?.['url']" target="_blank">{{ info["school"]?.["enname"] }}</a>
</div>
<div class="mj-total flexacenter">
该校共有
<div class="value">{{ relatedcount }}</div>
个面经
</div>
<div class="mj-list" @scroll="handleListScroll">
<template v-for="(item, index) in relatedlist" :key="index">
<a v-if="item['type']" class="mj-item flexflex recommend" :href="item?.url" target="_blank">
<div class="mj-header flexacenter">
<div class="label flexacenter">
<div class="label-text flexcenter">荐</div>
<div class="label-title">{{ labelObj[item["type"] || "offer"] }}</div>
</div>
<h1>{{ item["title"] }}</h1>
</div>
<div class="info-list flexflex" v-if="item['type'] == 'offer'">
<div class="info-item flexacenter">
<div class="info-name">专业</div>
<div class="info-value flex1 ellipsis">{{ item["professional"] }}</div>
</div>
<div class="info-item flexacenter">
<div class="info-name">学位</div>
<div class="info-value flex1 ellipsis">{{ item["degree"] }}</div>
</div>
<div class="info-item flexacenter">
<div class="info-name">结果</div>
<div class="info-value flex1 ellipsis">{{ item["apply_results"] }}</div>
</div>
</div>
<div class="thread-text ellipsis flexflex" v-if="item['type'] == 'thread' || item['type'] == 'ask'">
<div class="ask-label" v-if="item['type'] == 'ask'">回答:</div>
<div class="flex1 ellipsis">{{ item["message"] }}</div>
</div>
<div class="vote-list" v-if="item['type'] == 'vote'">
<div class="vote-item" v-for="(ite, i) in item['option'].slice(0, 2)" :key="i">{{ numberToEnclosed(i) }} &nbsp; {{ ite }}</div>
<div class="vote-item">{{ numberToEnclosed(3) }} &nbsp; …</div>
</div>
</a>
<a v-else class="mj-item flexflex" :class="{ pitch: pitchIndex == index }" @click.stop.prevent="handleItem(item['uniqid'])" :href="`./details/${item['uniqid']}`">
<div class="mj-header flexacenter">
<img class="mj-avatar" :src="item['avatar']" />
<div class="user-name">{{ item["username"] || "匿名用户" }}</div>
<div class="time">{{ handleDate(item["releasetime"]) }}发布</div>
</div>
<div class="info-list flexflex">
<div class="info-item flexacenter" v-if="item['profession']">
<div class="info-name">专业</div>
<div class="info-value flex1 ellipsis">{{ item["profession"] }}</div>
</div>
<div class="info-item flexacenter" v-if="item['project']">
<div class="info-name">项目</div>
<div class="info-value flex1 ellipsis">{{ item["project"] }}</div>
</div>
<div class="info-item flexacenter" v-if="item['interviewtime']">
<div class="info-name">面试</div>
<div class="info-value flex1 ellipsis">{{ item["interviewtime"] }}</div>
</div>
</div>
</a>
</template>
</div>
</div>
<!-- <div class="right flex1" @scroll="handleCommentsScroll" v-loading="detailsLoading"> -->
<div class="right flex1" ref="contentRightRef" v-loading="detailsLoading">
<!-- <div class="right-loading"></div> -->
<div class="header">
<div class="titletitle">{{ info["subject"] }}</div>
<div class="mj-header flexacenter">
<div class="mj-header-left flexacenter">
<el-popover placement="bottom-start" :width="140" trigger="click" popper-class="avatar-box-popper" :show-arrow="false">
<template #reference>
<img class="mj-avatar" :src="info['avatar']" />
</template>
<div class="avatar-box flexflex" v-if="info['uin']">
<!-- <div class="avatar-box flexflex"> -->
<a class="avatar-item flexcenter" target="_blank" @click.prevent="sendMessage(info['uin'])">
<img class="avatar-icon" src="@/assets/img/send-messages-icon.png" />
发送信息
</a>
<a class="avatar-item flexcenter" target="_blank" @click.prevent="TAHomePage(info['uin'])">
<img class="avatar-icon" src="@/assets/img/homepage-icon.png" />
TA的主页
</a>
</div>
</el-popover>
<div class="user-name">{{ info["nickname"] || "匿名用户" }}</div>
<div class="time">{{ handleDate(info["releasetime"]) }}发布</div>
<div class="hide flexacenter" @click="openHide" v-if="permissions.includes('mj.hide')">
<img class="icon" src="@/assets/img/set-icon.png" />
隐藏
</div>
</div>
<a class="mj-header-right flexacenter" target="_blank" :href="info['threadurl']">
<img class="original-icon" src="@/assets/img/original-icon.png" />
论坛原帖
<!-- <img class="eye-icon" src="@/assets/img/eye-icon.svg" /> -->
<!-- {{ info["views"] }} -->
</a>
</div>
</div>
<div class="details-box">
<div class="details-item">
<div class="details-top">面试过程及内容</div>
<div class="details-list">
<div class="details-list-item flexacenter">
<div class="details-value describe" :class="{ 'unlock-unlock': !isdisplay }" v-if="info['message']">
<div class="text" v-html="info['message']"></div>
<div class="unlock-mask flexflex" style="width: 693px">
<div class="unlock-text-box flexcenter" @click="handleLike">
<div class="unlock-text">作者设置了浏览限制</div>
<div class="unlock-text flexacenter">
<div class="emphasis" @click="loginJudgment()">“评论/点赞”</div>
后即可查看完整内容
</div>
<template v-if="respondListState">
<div class="respond-list-mask" @click.stop="cutRespondState(false)"></div>
<div class="respond-list-box" @click.stop="">
<div class="respond-list-title">选择你的回应:</div>
<div class="respond-list">
<template v-for="item in riposteoptions" :key="item">
<div class="respond-item" v-for="(item, key) in item.data" :key="key" v-html="jointriposte(key)" @click.stop="selectEomjiPop(key, true)"></div>
</template>
</div>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="details-item">
<div class="details-top">申请信息</div>
<div class="details-list">
<div class="details-list-item flexacenter" v-if="info['school']">
<div class="details-name">学校</div>
<a class="details-value flex1" target="_blank" :href="info['school']?.['url']">{{ info["school"]?.name }}</a>
</div>
<div class="details-list-item flexacenter" v-if="info['profession']">
<div class="details-name">专业</div>
<div class="details-value flex1">{{ info["profession"] }}</div>
</div>
<div class="details-list-item flexacenter" v-if="info['project']">
<div class="details-name">项目</div>
<div class="details-value flex1">{{ info["project"] }}</div>
</div>
</div>
</div>
<div class="details-item">
<div class="details-top">面试时间</div>
<div class="details-list">
<div class="details-list-item flexacenter">
<div class="details-name">日期</div>
<div class="details-value date" v-if="info['interviewtime']">{{ timestampToDate(info["interviewtime"]) }}</div>
</div>
</div>
</div>
</div>
<!-- 回应 -->
<div class="respond-area" v-if="false">
<div class="respond-title flexacenter" ref="respondtitle">
回应
<div class="value">{{ ripostecount.total || 0 }}</div>
<div v-if="ripostecount.user > 0" class="respond-list-btn" @click="openPopList">
共 <span class="respond-list-btn-amount">{{ ripostecount.user }}</span
>人回应
<img class="respond-list-btn-icon" src="@/assets/img/arrowsRight.svg" />
</div>
</div>
<div v-if="ripostelist.length == 0" class="respond-no-box flexacenter">
<div class="respond-no flex1">
<div v-for="item in randomEmojis" :key="item" class="code" v-html="jointriposte(item)" @click="selectEomji(item)"></div>
</div>
<RespondAdd></RespondAdd>
</div>
<div v-else class="respond-box">
<div v-for="(item, index) in ripostelist" :key="item" class="respond-item flexacenter" :class="{ pitch: item.selected }" @click="selectListEomji(index)">
<div class="code flexacenter" v-html="jointriposte(item.item)"></div>
{{ item.num }}
</div>
<div v-if="ripostelist.length < 3" class="respond-select flexflex">
<div class="respond-select-box flex1 flexflex">
<template v-for="(item, index) in randomEmojis" :key="item">
<div v-if="index < 5" class="respond-select-item" v-html="jointriposte(item)" @click="selectEomji(item)"></div>
</template>
</div>
<RespondAdd></RespondAdd>
</div>
<RespondAdd v-else></RespondAdd>
</div>
</div>
<div v-if="emojiMaskState" class="emoji-box-mask" @click="closeEmoji()"></div>
<!-- 讨论 -->
<div class="comment-box" ref="commentBoxRef">
<!-- 编辑评论 -->
<div v-if="editCommentState" class="edit-comment flexcenter">
<div class="box">
<div class="text">编辑评论</div>
<div class="input-box">
<div class="top flexflex">
<textarea ref="editInputRef" class="input-textarea flex1" maxlength="500" v-model="editInput" @focus="judgeLogin" @input="autoResize" @paste="handleInputPaste" placeholder="说说你的想法或疑问…"></textarea>
</div>
<div class="picture-box" v-if="editPicture.url">
<div class="picture">
<img class="close" @click="closeEditFileUpload()" src="@/assets/img/close-icon.png" />
<img class="img" @click="handleAnswerText" :src="editPicture.base64 || editPicture.url" />
</div>
</div>
<div class="bottom flexacenter">
<div class="operate flexacenter">
<div class="item" :class="{ pitch: editEmojiState }">
<img class="icon" src="@/assets/img/smiling-face.png" @click="openEditEmoji()" alt="" />
<div v-if="editEmojiState" class="emoji-edit-box-mask" @click="closeEditEmoji()"></div>
<div class="emoji-box">
<div class="emoji-icon" v-for="item in emojiData" :key="item" @click="selectEditEmoji(item)">{{ item }}</div>
</div>
</div>
<div class="item flexacenter" @click="handleEditFile()">
<input class="file" type="file" @change="handleFileUpload($event)" accept=".png, .jpg, .jpeg" />
<img class="icon" style="border-radius: 0" src="@/assets/img/picture-icon.png" alt="" />
<span class="file-hint">最多可上传1张图片支持在输入框中直接粘贴图片。</span>
</div>
</div>
</div>
</div>
<div class="btn-list flexacenter">
<div class="btn" @click="closeEdit()">取消</div>
<div class="btn send" @click="postEditComment()">发送</div>
</div>
</div>
</div>
<div class="comment-title flexacenter">
讨论
<div class="value">{{ commentComments || "" }}</div>
</div>
<!-- <div class="post-comment flexacenter" ref="postInputRef" :class="{ 'post-comment-focus': postCommentFocusState }" @click="loginJudgment()">
<div class="post-comment-input">
<el-input class="post-input flex1" type="textarea" :autosize="postCommentFocusState" :maxlength="500" show-word-limit placeholder="说说你的想法或疑问…" v-model="commentInputTop" @blur="postCommentFocusBlur" @focus="postCommentFocusState = true"></el-input>
</div>
<div class="post-ok flexcenter" @click="submitAnswerComments(commentInputTop)">发送</div>
</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="commentInputTop" @focus="judgeLogin" @input="autoResize" @paste="handleInputPaste" placeholder="说说你的想法或疑问…"></textarea>
</div>
<div class="picture-box" v-if="picture.url">
<div class="picture">
<img class="close" @click="closeFileUpload()" src="@/assets/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="@/assets/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" @click="judgeLogin()">
<input class="file" type="file" @change="handleFileUpload($event)" accept=".png, .jpg, .jpeg" />
<img class="icon" style="border-radius: 0" src="@/assets/img/picture-icon.png" alt="" />
<span class="file-hint">最多可上传1张图片支持在输入框中直接粘贴图片。</span>
</div>
</div>
<div class="btn" @click="submitAnswerComments(commentInputTop)">发送</div>
</div>
</div>
<template v-if="isEmptyState">
<div class="empty-box">
<Empty hint="说说你的观点吧"></Empty>
</div>
</template>
<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['groupimage']" :src="item.groupimage" :alt="item.grouptitle" style="height: 17px" />
</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 class="operate-box">
<div class="item flexcenter" @click="report(item['token'])">举报</div>
<div class="item flexcenter" v-if="permissions.includes('comment.edit')" @click="openEdit(item['token'], index)">编辑</div>
<div class="item flexcenter" v-if="permissions.includes('comment.delete')" @click="commentDelete(item['token'], index)">删除</div>
</div>
</div>
<img class="comment-icon" title="回复" @click="!item['childState'] ? openAnswerCommentsChild(index) : closeAnswerCommentsChild()" 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" v-if="item['content']" @click="!item['childState'] ? openAnswerCommentsChild(index) : closeAnswerCommentsChild()" 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="comments-input-masking" @click="closeAnswerCommentsChild()" v-if="item['childState']"></div> -->
<!-- <div class="comments-input-box" :class="{ 'comments-input-box-show': item['childState'] }"> -->
<div class="input-box" v-if="item['childState']">
<img class="cross" @click="closeAnswerCommentsChild()" src="@/assets/img/cross-icon.png" />
<div class="top flexflex">
<img class="avatar" v-if="user.avatar" :src="user.avatar" />
<textarea class="input-textarea flex1" maxlength="500" placeholder="说说你的想法或疑问…" v-model="item['commentInput']" @focus="judgeLogin" @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="@/assets/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="@/assets/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" @click="judgeLogin()">
<input class="file" type="file" @change="handleFileUpload($event, index)" accept=".png, .jpg, .jpeg" />
<img class="icon" style="border-radius: 0" src="@/assets/img/picture-icon.png" alt="" />
<span class="file-hint">最多可上传1张图片支持在输入框中直接粘贴图片。</span>
</div>
</div>
<div class="btn" @click="submitAnswerComments(item['commentInput'], index)">发送</div>
</div>
</div>
<!-- <div class="comments-input-box" :class="{ 'comments-input-box-show': item['childState'] }">
<div class="comments-input">
<el-input type="textarea" v-model="commentInput" placeholder="回复" :maxlength="500" show-word-limit></el-input>
<div class="operate-bottom flexacenter">
<div class="comments-btn comments-btn-cancel flexcenter" @click="closeAnswerCommentsChild()">取消</div>
<div class="comments-btn flexcenter" @click="submitAnswerComments(commentInput, index)">发送</div>
</div>
</div>
</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['groupimage']" :src="ite.groupimage" :alt="ite.grouptitle" style="height: 17px" />
</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="operate-box">
<div class="item flexcenter" @click="report(ite['token'])">举报</div>
<div class="item flexcenter" v-if="permissions.includes('comment.edit')" @click="openEdit(ite['token'], index, i)">编辑</div>
<div class="item flexcenter" v-if="permissions.includes('comment.delete')" @click="commentDelete(ite['token'], index, i)">删除</div>
</div>
<!-- <div class="report-box flexcenter" @click="report(ite['token'])">举报</div> -->
</div>
<img class="comment-icon" title="回复" @click="!ite['childState'] ? openAnswerCommentsChild(index, i) : closeAnswerCommentsChild()" 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" v-if="ite['content']" @click="!ite['childState'] ? openAnswerCommentsChild(index, i) : closeAnswerCommentsChild()">
<div class="comments-reply" v-if="ite?.reply?.nickname">@{{ ite?.reply?.nickname }}</div>
{{ ite["content"] }}
</div>
<img class="comments-img" @click="handleAnswerText" :src="ite.image?.base64 || ite.image?.url" v-if="ite.image?.url" />
<!-- <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(index, i)">发送</div>
</div>
</div> -->
<!-- <div class="comments-input-masking" @click="closeAnswerCommentsChild()" v-if="ite['childState']"></div> -->
<!-- <div class="comments-input-box" :class="{ 'comments-input-box-show': ite['childState'] }">
<div class="comments-input">
<el-input type="textarea" v-model="commentInput" placeholder="回复" :maxlength="500" show-word-limit></el-input>
<div class="operate-bottom flexacenter">
<div class="comments-btn comments-btn-cancel flexcenter" @click="closeAnswerCommentsChild()">取消</div>
<div class="comments-btn flexcenter" @click="submitAnswerComments(commentInput, index, i)">发送</div>
</div>
</div>
</div> -->
<div class="input-box" v-if="ite['childState']">
<img class="cross" @click="closeAnswerCommentsChild()" src="@/assets/img/cross-icon.png" />
<div class="top flexflex">
<textarea class="input-textarea flex1" maxlength="500" @focus="judgeLogin" :placeholder="'回复“' + (ite['nickname'] || '匿名用户') + '”:'" v-model="ite['commentInput']" @input="autoResize" @paste="handleInputPaste($event, index)"></textarea>
</div>
<div class="picture-box" v-if="ite.picture?.url">
<div class="picture">
<img class="close" @click="closeFileUpload(index, i)" src="@/assets/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="@/assets/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" @click="judgeLogin()">
<input class="file" type="file" @change="handleFileUpload($event, index, i)" accept=".png, .jpg, .jpeg" />
<img class="icon" style="border-radius: 0" src="@/assets/img/picture-icon.png" alt="" />
<span class="file-hint">最多可上传1张图片支持在输入框中直接粘贴图片。</span>
</div>
</div>
<div class="btn" @click="submitAnswerComments(ite['commentInput'], index, i)">发送</div>
</div>
</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>
</div>
</div>
<div class="floor-area flexacenter" @click="closeAnswerCommentsChild()">
<div class="floor-content flexacenter">
<div class="floor-right flexacenter" @mouseenter="handleFloorRight(true)" @mouseleave="handleFloorRight(false)">
手机查看该面经
<img class="arrows-icon" src="@/assets/img/arrows-icon.png" />
<el-popover placement="bottom" width="160px" trigger="hover" v-model:visible="floorRightState" popper-style="padding: 24px;border-radius: 18px;">
<template #reference>
<div class="QR-code-ball flexcenter">
<img class="" src="@/assets/img/QR-code-icon.svg" />
</div>
</template>
<img class="examine-code" :src="qrcode" />
</el-popover>
</div>
<!-- <div class="floor-centre flexflex flexacenter" @click="openHide" v-if="permissions.includes('mj.hide')">
<img class="icon" src="@/assets/img/set-icon.png" />
隐藏
</div> -->
<div class="floor-left flexacenter">
<div class="item flexacenter" v-if="isBrowser" style="cursor: auto">
<img class="icon h8" src="@/assets/img/eye-icon-black.svg" />
{{ info["views"] }}
</div>
<div class="item flexacenter" @click="handleLike" v-if="true">
<img class="icon h16" v-if="islike == 1" src="@/assets/img/like-icon-colours.png" />
<img class="icon h16" v-else src="@/assets/img/like-icon.png" />
{{ info["likenum"] || "" }}
</div>
<div class="item flexacenter" v-else>
<img class="icon h16" src="@/assets/img/riposte-icon.png" />
{{ ripostecount.total || 0 }}
</div>
<div class="item flexacenter" @click="handleScrollComments()"><img class="icon h15" src="@/assets/img/comment-icon.png" />{{ commentComments }}</div>
<ClientOnly>
<div class="item flexacenter" @click="handleCollect()">
<img class="icon h16" v-if="iscollection == 1" src="@/assets/img/collect-icon-colours.svg" />
<img class="icon h16" v-else src="@/assets/img/collect-icon.png" />
{{ info["favnum"] || "收藏" }}
</div>
</ClientOnly>
<ClientOnly>
<el-popover placement="bottom" width="628px" trigger="click" popper-style="padding: 0;border-radius: 10px;" v-model:visible="transmitBoxState">
<template #reference>
<div class="item flexacenter" @click="handleShare"><img class="icon h15" src="@/assets/img/transmit-icon.png" />转发</div>
</template>
<div class="transmit-box flexflex">
<img class="cross-icon" @click="transmitBoxState = false" src="@/assets/img/cross-icon.png" />
<div class="transmit-left transmit-web">
<div class="transmit-title">转发网页版</div>
<div class="transmit-content">
<div class="transmit-headline">{{ info["subject"] }}</div>
<div class="transmit-url">{{ getFullUrl() }}</div>
</div>
<div class="transmit-web-btn flexcenter" @click="copyText(`${info['subject']} + ${getFullUrl()}`)">复制链接</div>
</div>
<div class="transmit-right transmit-mini">
<div class="transmit-title">转发小程序版</div>
<div class="transmit-content flexcenter">
<img class="transmit-mini-img" :src="qrcode" />
<div class="flexcenter">
<img class="give-sweep" src="@/assets/img/give-sweep.png" />
扫码转发该面经
</div>
</div>
</div>
</div>
</el-popover>
</ClientOnly>
</div>
<!-- <div class="floor-middle flexacenter coin-box">
<div class="coin-content flexacenter flex1" @click="openCoinRankList" :style="{ cursor: info.coins != 0 ? 'pointer' : '' }">
<img class="coin-icon" src="@/assets/img/coin-icon.png" />
<div class="coin-text flex1 flexacenter">
已获
<div class="coin-value">{{ info.coins }}</div>
个寄托币
</div>
</div>
<div class="coin-btn flexcenter" @click="openCoinOperation()">给TA投币</div>
</div> -->
</div>
</div>
</div>
<Report v-if="reportAlertShow" :reportToken="reportToken"></Report>
</div>
<!-- 投币操作 弹窗 -->
<div class="pop-masking flexcenter" v-if="isInsertCoinsOperationShow">
<div class="slit-pop-box" v-if="coinMybalance > 0" style="border-radius: 11px">
<div class="slit-left" style="width: 50px">
<img class="slit-left-icon" src="//app.gter.net/image/gter/offer/imgdetails/u620.png" style="margin-top: -8px" />
</div>
<div class="slit-box">
<div class="slit-head" style="flex: 1; flex-direction: column; align-items: flex-start">
<div class="slit-head-title flexflex" style="width: 100%; justify-content: space-between">
<span>投币</span>
<div class="in-all">
你共有 <span>{{ coinMybalance }}</span> 寄托币
</div>
<!-- <a target="_blank" :href="coinConfig.strategy.url" style="font-weight: 100; font-size: 13px; text-decoration: underline;">{{ coinConfig.strategy.button }}</a> -->
</div>
</div>
<div class="coin-quantity flexacenter">
<div class="coin-quantity-item" :class="{ 'coin-pitch': coinAmount == item }" v-for="(item, index) in coinConfig.list" :key="index" @click="coinSelectAmountDispose(item)">
{{ item }} <span>{{ coinConfig.unit }}</span>
</div>
</div>
<el-input class="slit-input" v-model="coinAmount" placeholder="自定义投币金额" show-word-limit="false"> </el-input>
<div class="message-box">
<div class="message-hint">顺便说点什么</div>
<el-input class="slit-input" style="font-size: 15px" v-model="coinMessage" placeholder="请输入" maxlength="500" show-word-limit> </el-input>
</div>
<div class="operation">
<div class="operation-item flexcenter" @click="isInsertCoinsOperationShow = !isInsertCoinsOperationShow">取消</div>
<div class="operation-item flexcenter greenBj" @click="postCoinSbmit()">确定</div>
</div>
</div>
</div>
<div class="no-jituobi-pop-box" v-else>
<img class="no-jituobi-close" @click="isInsertCoinsOperationShow = !isInsertCoinsOperationShow" src="@/assets/img/cross-icon.png" />
<div class="no-jituobi-head flexacenter">
<img class="bi-icon" src="//app.gter.net/image/gter/offer/imgdetails/u620.png" style="margin-right: 12px" />
<span style="margin-top: 10px">
{{ coinConfig?.strategy?.tips }}
</span>
</div>
<a :href="coinConfig?.strategy?.url" target="_blank">
<div class="strategy-btn greenBj flexcenter">{{ coinConfig?.strategy?.button }}<img class="strategy-icon" src="@/assets/img/strategy-icon.svg" /></div>
</a>
</div>
</div>
<!-- 投币 排行榜 -->
<RankingBox v-if="coinrankingState" :coinrankingList="coinrankingList"></RankingBox>
<div class="respond-pop-mask" v-if="respondPopListState">
<div class="respond-pop">
<div class="respond-pop-no" v-if="JSON.stringify(respondDetail) == '{}'">
<img class="respond-title-icon" @click="closePopList()" src="@/assets/img/cross-grey.png" />
<img src="@/assets/img/no-discussion.png" class="respond-pop-no-icon" />
<div class="respond-pop-no-text">- 暂无数据 -</div>
</div>
<template v-else>
<div class="respond-pop-title">
共<span class="respond-pop-amount">{{ ripostecount.user }}</span
>人回应
<img class="respond-title-icon" @click="closePopList()" src="@/assets/img/cross-grey.png" />
</div>
<div class="respond-list">
<div class="respond-item" v-for="(item, index) in respondDetail" :key="index">
<div class="respond-code" :class="{ pitch: item.selected }" v-html="jointriposte(item.item)" @click="selectEomjiListPop(item.item)"></div>
<div class="respond-content flex1">
<div class="respond-total">{{ item.user.length }} 人作此回应</div>
<div class="user-item" v-for="(item, index) in item.user" :key="index" @click="TAHomePage(item['uin'])">
<img class="user-avatar" :src="item.avatar" />
{{ item.nickname || item.username }}
</div>
</div>
</div>
</div>
</template>
</div>
</div>
<!-- 大图 -->
<div class="detail-image-mask flexcenter" v-if="dialogSrc" @click="dialogSrc = ''">
<div class="detail-image flexcenter">
<img class="detail-img" :src="dialogSrc" />
</div>
</div>
<el-dialog v-model="dialogVisible" title="提示" width="500">
<span>确定隐藏该面经吗</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" @click="handleHide"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ElMessage } from "element-plus";
const route = useRoute();
let uniqid = route.params.id;
let user = inject("userInfo");
let isNeedLogin = inject("isNeedLogin");
const goLogin = inject("goLogin");
useHead({ script: [{ src: "https://app.gter.net/bottom?tpl=header&menukey=mj" }, { src: "https://app.gter.net/bottom?tpl=footer,popupnotification", body: true }] });
let contentRightRef = ref(null);
let contentRightHeight = ref(null);
onMounted(() => {
window.addEventListener("scroll", handleScroll);
getDetails();
// openObserverBottom()
// 在元素挂载后执行回调函数
nextTick(() => {
// 创建 ResizeObserver 实例
const observer = new ResizeObserver((entries) => {
for (const entry of entries) {
// 元素的高度变化
contentRightHeight.value = entry.contentRect.height;
}
});
// 监听元素的变化
observer.observe(contentRightRef.value);
});
clearBottom();
});
let floorAreaState = ref(false); // 底部操作显示状态
watch(
() => route.path,
(newValue, oldValue) => {
// 在这里处理route.path的变化
if (typeof window !== "undefined" && route.path) {
if (window._hmt) window._hmt.push(["_trackPageview", route.fullPath]);
if (window._czc) {
let location = window.location;
let contentUrl = location.pathname + location.hash;
let refererUrl = "/";
window._czc.push(["_trackPageview", contentUrl, refererUrl]);
window._czc.push(["_setAutoPageview", false]);
}
}
}
);
// 开启一个监听最底部是否在可视窗口内
const openObserverBottom = () => {
// 获取目标元素的引用
const target = document.querySelector("section.index-footer");
if (!target) {
openObserverBottom();
return;
}
// 创建一个 Intersection Observer 实例
const observer = new IntersectionObserver(
(entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) floorAreaState.value = false;
else floorAreaState.value = true;
});
},
{
root: null,
threshold: 0.5,
}
);
// 开始观察目标元素
observer.observe(target);
};
// 清除底部的次数
let clearBottomCount = 0;
// 清除 底部
const clearBottom = () => {
const indexFooter = document.querySelector("section.index-footer");
if (!indexFooter) {
clearBottomCount++;
setTimeout(() => clearBottom(), 200);
return;
}
if (clearBottomCount == 5) return;
indexFooter.style.display = "none";
};
// 清空全部数据
const clearAllData = () => {
uniqid = route.params.id;
info.value = {};
qrcode.value = "";
iscollection.value = 0;
isdisplay.value = true;
islike.value = 0;
ismyself.value = 0;
relatedlist.value = [];
relatedcount.value = 0;
pitchIndex.value = null;
seo.value = {};
commentCount.value = 0;
commentPage.value = 1;
commentList.value = [];
commentLoading = false;
token = "";
};
let floorRightState = ref(false); // 右下角 的二维码显示状态
// 处理右下角 鼠标经过箭头 展示二维码
const handleFloorRight = (value) => {
floorRightState.value = value;
};
// 转发弹窗 的 显示状态
let transmitBoxState = ref(false);
let info = ref({});
let qrcode = ref(""); // 分享二维码
let iscollection = ref(0); // 是否收藏
let isdisplay = ref(true); // 是否隐藏
let islike = ref(0); // 是否点赞
let ismyself = ref(0); // 是否是作者
let detailsLoading = ref(false); // 详情加载中
const getDetails = () => {
if (detailsLoading.value) return;
detailsLoading.value = true;
detailsHttp({ uniqid }).then((res) => {
if (res.code != 200) {
ElMessage.error(res.message);
setTimeout(() => goToURL("/index.html", false), 1000);
return;
}
let data = res.data;
token = data["token"];
info.value = data["info"];
seo.value = data.seo;
iscollection.value = data.iscollection || 0;
isdisplay.value = data.isdisplay;
islike.value = data.islike;
ismyself.value = data.ismyself;
qrcode.value = data["share"]["qrcode"];
if (relatedlist.value.length == 0) getRelatedlistHttp();
else CalculateSelectedList();
if (JSON.stringify(coinConfig.value) == "{}") getCoinInfo();
detailsLoading.value = false;
getCommentListHttp();
getRiposte();
});
};
// 计算选中的列表
const CalculateSelectedList = () => {
let targetRelatedlist = [...relatedlist.value];
targetRelatedlist.forEach((element, index) => {
if (element["uniqid"] == uniqid) pitchIndex.value = index;
});
if (pitchIndex.value == null) {
targetRelatedlist.unshift({
avatar: info.value["avatar"],
interviewtime: timestampToDate(info.value["interviewtime"]),
profession: info.value["profession"],
project: info.value["project"],
releasetime: info.value["releasetime"],
subject: info.value["subject"],
uniqid: uniqid,
username: info.value["nickname"],
});
relatedlist.value = targetRelatedlist;
pitchIndex.value = 0;
}
};
// 左侧列表数据
let relatedlist = ref([]);
let relatedcount = ref(0);
let relatedpage = ref(1);
let relatedloading = false;
let pitchIndex = ref(null); // 列表选中 index
const getRelatedlistHttp = () => {
if (relatedloading || relatedpage.value == 0 || !token) return;
relatedloading = true;
relatedlistHttp({ token, page: relatedpage.value })
.then((res) => {
if (res.code != 200) return;
let data = res.data;
relatedlist.value = relatedlist.value.concat(data.data);
relatedcount.value = data.count;
if (relatedlist.value.length >= data["count"]) relatedpage.value = 0;
else relatedpage.value++;
CalculateSelectedList();
})
.finally(() => {
relatedloading = false;
});
};
let seo = ref({});
let commentCount = ref(0);
let commentComments = ref(0); // 所有的评论数
let commentPage = ref(1);
let commentList = ref([]);
let commentLoading = false;
let token = "";
let isEmptyState = ref(false); // 评论是否为空
// 获取详情评论数据
const getCommentListHttp = () => {
if (commentPage.value == 0 || commentLoading || detailsLoading.value) return;
commentLoading = true;
if (commentPage.value != 1) getRelatedlistHttp();
detailsCommentListHttp({
page: commentPage.value,
childlimit: 1,
limit: 10,
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++;
// https://bbs.gter.net/static/image/smiley/lxh/rose.gif
})
.finally(() => (commentLoading = false));
};
// 获取剩下的子评论
const alsoCommentsData = (index, ind) => {
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,
}).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;
});
};
// 全部的启动到底部
const handleCommentsScroll = (e) => {
const el = e.target;
if (el.scrollHeight - el.scrollTop >= el.clientHeight + 40) return;
getCommentListHttp();
};
// 评论点赞
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);
});
};
let scrollTopValue = ref(0);
// 监听滚动到底部
const handleScroll = () => {
// return
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
scrollTopValue.value = scrollTop;
// return
const scrollHeight = document.documentElement.scrollHeight;
const clientHeight = document.documentElement.clientHeight;
// 列表下 滑动到底部 获取新数据
if (scrollTop + clientHeight >= scrollHeight - 40) {
getCommentListHttp();
// 整个页面滚动到底部时 判断左边是否有滚动条 没有则需要加载左边数据的下一页
const mjList = document.querySelector(".content .left .mj-list");
if (!(mjList.scrollHeight > mjList.clientHeight || mjList.scrollWidth > mjList.clientWidth)) getRelatedlistHttp();
}
};
// 打开 回答-评论 的子评论
const openAnswerCommentsChild = (index, i) => {
if (isNeedLogin.value) {
goLogin();
return;
}
closeAnswerCommentsChild();
if (i == null) commentList.value[index]["childState"] = true;
else commentList.value[index]["child"][i]["childState"] = true;
};
// 关闭 回答-评论 的子评论
const closeAnswerCommentsChild = () => {
commentInput.value = "";
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"] = "";
});
}
});
};
// 讨论的输入框
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
let image = {};
if (i != null) {
parentid = targetCommentList[index]["child"][i]["id"];
image = commentList.value[index]["child"][i]["picture"] || {};
} else if (index != null) {
parentid = targetCommentList[index]["id"];
image = commentList.value[index]["picture"] || {};
} else image = picture.value;
detailsSubmitommentListHttp({
content,
token,
parentid,
image: image ? { aid: image.aid, url: image.url } : null,
}).then((res) => {
if (res.code != 200) {
ElMessage.error(res.message);
return;
}
let data = res.data;
isdisplay.value = true;
if (i != null) {
let targetData = {
id: data["commentid"],
content,
isauthor: 1,
islike: 0,
likenum: 0,
reply: {
nickname: targetCommentList[index]["child"][i]["nickname"],
},
...data,
image,
};
targetCommentList[index]["child"].unshift(targetData);
targetCommentList[index]["childnum"]++;
} else {
let targetData = {
id: data["commentid"],
content,
isauthor: 1,
islike: 0,
likenum: 0,
...data,
child: [],
image,
};
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 = "";
isEmptyState.value = false; // 取消有可能的 没有评论
closeAnswerCommentsChild();
picture.value = {};
ElMessage({
message: res.message,
type: "success",
});
if (!isdisplay.value) isdisplay.value = true;
});
};
// 处理时间戳数据
const timestampToDate = (timestamp) => {
const date = new Date(timestamp * 1000); // 如果你的时间戳是秒级的需要乘以1000
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, "0"); // getMonth() 返回的月份是从0开始计数的
const day = date.getDate().toString().padStart(2, "0");
return `${year}-${month}-${day}`;
};
onUnmounted(() => window.removeEventListener("scroll", handleScroll));
// 获取完整 url
const getFullUrl = () => {
if (typeof window === "undefined") return;
return window.location.href;
};
// 复制
let copyText = (text) => {
if (navigator.clipboard) {
copyText = () => {
navigator.clipboard.writeText(text);
ElMessage({
message: "复制成功",
type: "success",
});
};
} else {
copyText = () => {
var tempInput = document.createElement("input");
tempInput.value = text;
document.body.appendChild(tempInput);
tempInput.select();
document.execCommand("copy");
document.body.removeChild(tempInput);
ElMessage({
message: "复制成功",
type: "success",
});
};
}
copyText();
};
// 点击点赞
const handleLike = () => {
if (islike.value) {
ElMessage.error("不可取消点赞");
return;
}
if (isNeedLogin.value) {
goLogin();
return;
}
operateLikeHttp({ token }).then((res) => {
if (res.code != 200) return;
let data = res.data;
info.value["likenum"] = data["count"];
islike.value = data["status"];
isdisplay.value = true;
ElMessage.success(res.message);
});
};
let topHeadRef = ref(null);
// 点击 收藏
const handleCollect = () => {
if (isNeedLogin.value) {
goLogin();
return;
}
topHeadRef.value.count = {};
operateCollectHttp({ token }).then((res) => {
if (res.code != 200) {
ElMessage.error(res["message"]);
return;
}
let data = res.data;
info.value["favnum"] = data["count"];
iscollection.value = data["status"];
ElMessage.success(res["message"]);
});
};
const router = useRouter();
// 处理点击列表
const handleItem = (uni) => {
// if (uni == route?.params?.id) return
// router.push(`/details/${uni}`)
// console.log(uniqid, uni);
if (uniqid == uni) return;
uniqid = uni;
// info.value = {}
info.value["message"] = "";
info.value["subject"] = "";
info.value["profession"] = "";
qrcode.value = "";
iscollection.value = 0;
isdisplay.value = true;
islike.value = 0;
ismyself.value = 0;
commentCount.value = 0;
commentPage.value = 1;
commentList.value = [];
commentLoading = false;
token = "";
// clearAllData()
nextTick(() => getDetails());
replaceState(uni);
window.scrollTo({
top: 0,
behavior: "smooth",
});
};
// 修改 url
const replaceState = (uni) => {
if (typeof window === "undefined") return;
// 替换当前URL但不刷新页面
window.history.pushState({}, "", `${window.location.origin}/details/${uni}`);
};
let reportAlertShow = ref(false);
let reportToken = ref("");
// 点击打开举报
const report = (token) => {
if (isNeedLogin.value) {
goLogin();
return;
}
reportToken.value = token;
reportAlertShow.value = true;
};
provide("reportAlertShow", reportAlertShow);
provide("clearAllData", clearAllData);
provide("getDetails", getDetails);
// seo的
if (process.server) {
try {
await detailsHttp({ uniqid }).then((res) => {
if (res.code != 200) {
ElMessage.error(res.message);
return;
}
let data = res.data;
token = data["token"];
info.value = data["info"];
seo.value = data.seo;
iscollection.value = data.iscollection;
isdisplay.value = data.isdisplay;
islike.value = data.islike;
ismyself.value = data.ismyself;
qrcode.value = data["share"]["qrcode"];
if (relatedlist.value.length == 0) getRelatedlistHttp();
else CalculateSelectedList();
detailsLoading.value = false;
getCommentListHttp();
});
await relatedlistHttp({ token, page: 1 }).then((res) => {
if (res.code != 200) return;
let data = res.data;
relatedlist.value = data.data;
relatedcount.value = data.count;
CalculateSelectedList();
});
} catch (error) {}
}
const isBrowser = computed(() => {
return process.client; // 使用 process.client 判断是否在浏览器环境下
});
// 点击发送信息
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}`);
// redirectToExternalWebsite(`https://bbs.gter.net/home.php?mod=space&showmsg=1&uid=${uin}`)
};
// 点击ta的主页
const TAHomePage = (uin) => {
redirectToExternalWebsite(`https://bbs.gter.net/home.php?mod=space&uid=${uin}`);
};
// 跳转 url
const redirectToExternalWebsite = (url) => {
const link = document.createElement("a");
link.href = url;
link.target = "_blank";
link.click();
};
// 全部的启动到底部
const handleListScroll = (e) => {
const el = e.target;
if (el.scrollHeight - el.scrollTop >= el.clientHeight + 40) return;
getRelatedlistHttp();
};
const commentBoxRef = ref(null);
// 点进滚动到评论
const handleScrollComments = () => {
const element = commentBoxRef.value;
if (!element) return;
window.scrollTo({
top: element.offsetTop - 85 || 0,
behavior: "smooth",
});
return;
const rect = element.getBoundingClientRect();
let elementTop = 0;
elementTop = rect.top - window.innerHeight / 2;
if (rect.top > window.innerHeight) {
elementTop = rect.top - window.innerHeight / 2;
window.scrollTo({
top: elementTop || 0,
behavior: "smooth",
});
} else {
elementTop = window.innerHeight / 2 + rect.top / 2;
window.scrollTo({
top: elementTop || 0,
behavior: "smooth",
});
}
// window.scrollTo({
// top: elementTop,
// behavior: "smooth",
// })
};
// 打开评论的 信息框
const openAvatarPopover = (index, i) => {
if (i != null) commentList.value[index]["child"][i]["popoverState"] = true;
else commentList.value[index]["popoverState"] = true;
};
let isInsertCoinsOperationShow = ref(false); // 投票弹窗
let coinMybalance = ref({}); // 我的寄托币数量
let coinConfig = ref({}); // 投币的配置
let coinConnum = ref(0); // 寄托币数量
let coinRanklist = ref({}); // 投币排行榜
let coinAmount = ref(null); // 投币选择的金额
let coinCustomAmount = ref(null); // 投币自定义的金额
let defaultcoinnum = ref(0); // 投币金额默认寄托币数
let coinMessage = ref(""); // 投币时的说点什么
let postCoinSbmitState = false; // 投币中
// 获取寄托币
const getCoinInfo = () => {
coinHttp({ token }).then((res) => {
if (res.code != 200) return;
let data = res.data;
coinConfig.value = data.config;
coinMybalance.value = data.mybalance;
coinRanklist.value = data.ranklist;
defaultcoinnum.value = data.defaultcoinnum;
});
};
// 打开寄托币弹窗
const openCoinOperation = () => {
if (isNeedLogin.value) {
goLogin();
return;
}
isInsertCoinsOperationShow.value = true;
};
// 处理投币的选择选项
const coinSelectAmountDispose = (amount) => {
coinAmount.value = amount;
};
// 提交寄托币
const postCoinSbmit = () => {
if (postCoinSbmitState) return;
postCoinSbmitState = true;
coinsubmitHttp({
token,
coinnum: coinAmount.value,
message: coinMessage.value,
})
.then((res) => {
if (res.code != 200) {
ElMessage.error(res.message);
return;
}
let data = res.data;
info.value.coins = info.value.coins + data["coinnum"];
coinMybalance.value = coinMybalance.value - data["coinnum"];
coinAmount.value = null;
isInsertCoinsOperationShow.value = false;
ElMessage.success(res.message);
if (data.comment) {
const userInfoWin = window.userInfoWin || {};
commentComments.value = data.comment.count;
let newComment = {
avatar: userInfoWin["avatar"],
child: [],
childnum: 0,
content: coinMessage.value,
id: parseInt(data.comment.commentid),
...data.comment,
islike: 0,
likenum: 0,
nickname: userInfoWin["nickname"] || "匿名用户",
parentid: 0,
reply: [],
timestamp: generateTime(),
};
commentList.value.unshift(newComment);
}
coinMessage.value = "";
coinrankingList.value = [];
})
.finally(() => (postCoinSbmitState = false));
};
let coinrankingList = ref([]); // 投币排行榜数据
let coinrankingState = ref(false); // 投币排行榜 弹窗状态
let coinrankingLoading = false; // 投币排行榜加载中
const getCoinranking = () => {
if (coinrankingLoading) return;
coinrankingLoading = true;
coinrankingHttp({ token })
.then((res) => {
if (res.code != 200) return;
const data = res.data;
coinrankingList.value = data;
coinrankingState.value = true;
})
.finally(() => (coinrankingLoading = false));
};
// 打开寄托币排行榜
const openCoinRankList = () => {
if (info.value.coins == 0) return;
if (coinrankingList.value.length == 0) getCoinranking();
else coinrankingState.value = true;
};
// 生成时间
const generateTime = () => {
let date = new Date();
let Y = date.getFullYear() + "-";
let M = (date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1) + "-";
let D = (date.getDate() < 10 ? "0" + date.getDate() : date.getDate()) + " ";
let h = (date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) + ":";
let m = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes();
return "" + Y + M + D + h + m;
};
provide("coinrankingState", coinrankingState);
provide("openCoinOperation", openCoinOperation);
// 处理点击转发统计
const handleShare = () => shareHttp({ token });
// 登录判断
const loginJudgment = () => {
if (isNeedLogin.value) goLogin();
};
// 取消了同页面的收藏
const unbookmarkSamePage = () => {
iscollection.value = 0;
info.value.favnum--;
};
provide("unbookmarkSamePage", unbookmarkSamePage);
let labelObj = {
offer: "Offer",
vote: "投票",
mj: "面经",
thread: "帖子",
ask: "回答",
};
let postCommentFocusState = ref(false);
const postInputRef = ref(null);
const postCommentFocusBlur = () => {
const refref = postInputRef.value;
setTimeout(() => {
postCommentFocusState.value = false;
nextTick(() => {
let targetDom = refref.querySelector(".el-textarea__inner");
targetDom.style.height = "41px";
});
}, 200);
};
let ripostelist = ref([]);
let ripostecount = ref({});
let riposteoptions = ref({});
provide("riposteoptions", riposteoptions);
const getRiposte = () => {
getRiposteHttp({ token }).then((res) => {
if (res.code != 200) return;
let data = res.data;
ripostecount.value = data.count || {};
ripostelist.value = data.list || [];
riposteoptions.value = data.options || [];
if (ripostelist.value.length <= 3) randomEmoji();
});
};
let randomEmojis = ref([]); // 随机 五个 emoji
provide("randomEmojis", randomEmojis);
// 随机 7 个Emoji
const randomEmoji = () => {
let emojiList = ripostelist.value;
// 需要排除的 Emoji
let exclude = [];
emojiList.forEach((element) => {
exclude.push(element.item);
});
let selectedList = []; // 待选择 Emoji To be selected
// 默认是有点赞的
for (const key in riposteoptions.value[0].data) {
if (key != "c150") selectedList.push(key);
}
const random = [];
if (!exclude.includes("c150")) random.push("c150"); // 添加第一个点赞 emoji
selectedList = selectedList.filter((itemB) => !exclude.includes(itemB));
// 生成随机索引,确保不重复
let indexes = [];
while (indexes.length < 7) {
let randomIndex = Math.floor(Math.random() * selectedList.length);
if (indexes.indexOf(randomIndex) === -1) {
indexes.push(randomIndex);
random.push(selectedList[randomIndex]);
}
}
randomEmojis.value = random;
};
// 拼接 回应需要的 字符
const jointriposte = (item) => {
return `&#x${item};`;
};
provide("jointriposte", jointriposte);
let riposteHttpState = false; // 回应加载中
// 选择 emoji
const selectEomji = (item) => {
if (isNeedLogin.value) {
goLogin();
return;
}
if (riposteHttpState) return;
riposteHttpState = true;
riposteSubmitHttp({ token, item })
.then((res) => {
if (res.code != 200) {
ElMessage.error(res.message);
return;
}
let data = res.data;
handleEmojiData(data);
})
.finally(() => {
riposteHttpState = false;
});
};
provide("selectEomji", selectEomji);
// 选中 在 Emoji 弹窗中 选择
const selectEomjiPop = (key, isroll) => {
if (isNeedLogin.value) {
goLogin();
return;
}
let emojiList = ripostelist.value;
// 判断 是否已经 有了
const index = emojiList.findIndex((item) => item.item == key);
if (index != -1 && emojiList[index].selected) return;
if (riposteHttpState) return;
riposteHttpState = true;
riposteSubmitHttp({ token, item: key })
.then((res) => {
if (res.code != 200) {
ElMessage.error(res.message);
return;
}
if (isroll) {
rollRiposte();
}
let data = res.data;
respondListState.value = false;
handleEmojiData(data);
})
.finally(() => {
riposteHttpState = false;
});
};
provide("selectEomjiPop", selectEomjiPop);
// 专门处理 展示列表的 数据结构
const handleEmojiData = (data) => {
let emojiList = ripostelist.value;
let isnew = true;
emojiList.forEach((element, index) => {
if (element.item == data.item) {
isnew = false;
if (element.selected) element.num--;
else element.num++;
element.selected = !element.selected;
}
});
// 代表是新数据
if (isnew) {
emojiList.push({
item: data.item,
num: 1,
selected: true,
});
}
let newArray = [];
emojiList.forEach((item) => {
if (item.num > 0) newArray.push(item);
});
if (newArray.length < 3) randomEmoji();
ripostecount.value = data.count;
ripostelist.value = newArray;
if (!isdisplay.value) isdisplay.value = true;
};
// 选择回应
const selectListEomji = (index) => {
if (isNeedLogin.value) {
goLogin();
return;
}
let emojiList = ripostelist.value;
let target = emojiList[index];
if (riposteHttpState) return;
riposteHttpState = true;
riposteSubmitHttp({ token, item: target.item })
.then((res) => {
if (res.code != 200) {
ElMessage.error(res.message);
return;
}
let data = res.data;
handleEmojiData(data);
})
.finally(() => {
riposteHttpState = false;
});
};
let respondPopListState = ref(false); // 回应列表弹窗状态
let respondDetail = ref({}); // 已回应列表
// 打开回应弹窗列表
const openPopList = () => {
respondPopListState.value = true;
getRespondDetail();
};
// 关闭回应弹窗列表
const closePopList = () => {
respondPopListState.value = false;
};
// 回应详情
const getRespondDetail = () => {
riposteDetailHttp({ token }).then((res) => {
if (res.code != 200) return;
respondDetail.value = res.data;
});
};
// 点击回应列表的
const selectEomjiListPop = (key) => {
// let respondDetail = respondDetail.value
let target = respondDetail.value[key];
riposteSubmitHttp({ token, item: target.item }).then((res) => {
if (res.code != 200) {
ElMessage.error(res.message);
return;
}
let data = res.data;
handleEmojiData(data);
if (target.selected) {
target.user = target.user.filter((item) => item.uin != data.uin);
} else {
target.user.push(data);
}
let emojiList = ripostelist.value;
if (target.user.length == 0) {
emojiList = emojiList.filter((item) => item.item != key);
delete respondDetail.value[key];
} else {
target.selected = !target.selected;
respondDetail.value[key] = target;
}
ripostelist.value = emojiList;
});
};
let respondListState = ref(false);
// 切换限制回应的弹窗
const cutRespondState = (value) => {
respondListState.value = value;
};
const respondtitle = ref(null);
const rollRiposte = () => {
const respondBox = respondtitle.value;
// 获取元素的位置信息
const rect = respondBox.getBoundingClientRect();
// 计算节点距离浏览器视口顶部的距离
const distanceToViewportTop = rect.top + window.scrollY - 60;
window.scrollTo({
top: distanceToViewportTop,
behavior: "smooth",
});
};
let picture = ref({});
let emojiState = ref(false);
let emojiMaskState = ref(false);
const emojiData = ["😀", "😁", "😆", "😅", "😂", "😉", "😍", "🥰", "😋", "😜", "🤪", "😎", "🤩", "🥳", "😔", "🙁", "😭", "😡", "😳", "🤗", "🤔", "🤭", "🤫", "😯", "😵", "🙄", "🥴", "🤢", "🤑", "🤠", "👌", "✌️", "🤟", "🤘", "🤙", "👍", "👎", "✊", "👏", "🤝", "🙏", "💪", "❤️", "💔", "🌹", "🥀", "🎉", "🎁", "🧧", "🌙", "⭐", "🌍", "💌", "📬", "🚗", "🚕", "🚲", "🛵", "🚀", "🚁", "⛵", "🚢", "🍎", "🍐", "🍊", "🍉", "🍓", "🍑", "🍔", "🍟", "🍕", "🥪", "🍜", "🍡", "🍨", "🍦", "🎂", "🍰", "🍭", "🍿", "🍩", "🧃", "🍹"];
// 打开 Emoji
const openEmoji = (index, i) => {
if (isNeedLogin.value) {
goLogin();
return;
}
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;
editEmojiState.value = false;
};
// 选择 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 {
commentInputTop.value += key;
}
};
// 自动输入框增高
const autoResize = (e) => {
e.target.style.height = "auto"; // 重置高度
e.target.style.height = `${e.target.scrollHeight}px`; // 设置为内容高度
};
const maxSize = 20 * 1024 * 1024; // 20MB
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) {
ElMessage({
message: "文件大小不能超过 20MB",
type: "error",
});
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const base64 = e.target.result;
uploadImg(base64).then((res) => {
const obj = {
base64,
...res,
};
if (editCommentState.value) editPicture.value = obj;
else {
if (ii != undefined) commentList.value[index].child[ii]["picture"] = obj;
else if (index != undefined) commentList.value[index]["picture"] = obj;
else picture.value = obj;
}
ElMessage.success("上传成功");
});
};
reader.readAsDataURL(file);
}
}
};
const handleFileUpload = (event, index, i) => {
closeEmoji();
const file = event.target.files[0]; // 获取选择的文件
if (!file) return;
if (file.size > maxSize) {
ElMessage({
message: "文件大小不能超过 20MB",
type: "error",
});
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const base64 = e.target.result;
uploadImg(base64).then((res) => {
const obj = {
base64,
...res,
};
if (editCommentState.value) editPicture.value = obj;
else {
if (i != undefined) commentList.value[index].child[i]["picture"] = obj;
else if (index != undefined) commentList.value[index]["picture"] = obj;
else picture.value = obj;
}
ElMessage({
message: "上传成功",
type: "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 = {};
};
// 上传图片 获取图片url
const uploadImg = (base64) => {
return new Promise((resolve, reject) => {
// detailLoading.value = true
commonUploadHttp({
data: base64,
})
.then((res) => {
if (res.code != 200) {
ElMessage({
message: res.message || "上传失败",
type: "error",
});
return;
}
let data = res.data;
resolve(data);
})
.catch((err) => {
console.log("err", err);
});
// .finally(() => (detailLoading.value = false))
});
};
let dialogSrc = ref(""); // 大图的src
// 处理点击答案图片 展开大图
const handleAnswerText = (e) => {
if (e.target.tagName === "IMG") {
var src = e.target.getAttribute("src");
dialogSrc.value = src;
window.addEventListener("keydown", handleKeydown);
}
};
// 大图的监听 esc 键盘按钮
const handleKeydown = (event) => {
if (event.key !== "Escape") return;
dialogSrc.value = "";
window.removeEventListener("keydown", handleKeydown); // 取消监听
};
let permissions = ref([]);
onMounted(() => {
setTimeout(() => {
permissions.value = window["permissions"] || [];
// permissions.value = ["mj.hide", "comment.edit", "comment.delete"]
}, 1000);
});
let dialogVisible = ref(false);
const openHide = () => (dialogVisible.value = true);
// 点击隐藏
const handleHide = () => {
mjHideHttp({ token }).then((res) => {
dialogVisible.value = false;
ElMessage.success(res.message || "隐藏成功");
router.replace("/index.html");
});
};
// 点击删除
const commentDelete = (token, index, i) => {
commentDeleteHttp({
token,
}).then((res) => {
if (res.code != 200) {
ElMessage.error(res.message);
return;
}
if (i >= 0) {
commentList.value[index].child.splice(i, 1);
commentList.value[index].childnum -= 1;
console.log("childnum", commentList.value[index]);
} else {
commentComments.value -= commentList.value[index].childnum;
commentList.value.splice(index, 1);
}
commentComments.value -= 1;
});
};
const judgeLogin = () => {
if (isNeedLogin.value) goLogin();
};
let editCommentState = ref(false);
let editToken = "";
let editPicture = ref({});
let editInput = ref("");
let editEmojiState = ref(false);
const editInputRef = ref(null);
const openEdit = (token, index, i) => {
const list = JSON.parse(JSON.stringify(commentList.value));
let target = {};
if (i != null) target = list[index]["child"][i];
else target = list[index];
console.log(token, index, i, target);
editToken = target.token || "";
editInput.value = target.content || "";
editPicture.value = target.image || {};
editCommentState.value = true;
nextTick(() => {
editInputRef.value.style.height = `${editInputRef.value.scrollHeight}px`;
});
};
const closeEdit = () => {
editPicture.value = {};
editToken = "";
editInput.value = "";
editCommentState.value = false;
};
// 打开 Emoji
const openEditEmoji = (index, i) => {
if (isNeedLogin.value) {
goLogin();
return;
}
editEmojiState.value = true;
};
const selectEditEmoji = (key) => {
closeEmoji();
editInput.value += key;
};
const postEditComment = () => {
if (isNeedLogin.value) {
goLogin();
return;
}
const image = editPicture.value;
commentsEditSubmit({
content: editInput.value,
token: editToken,
image: image ? { aid: image.aid, url: image.url } : null,
}).then((res) => {
if (res.code != 200) {
ElMessage.error(res.message);
return;
}
commentList.value.forEach((element) => {
if (element.token == editToken) {
element["content"] = editInput.value;
element["image"] = image;
}
element.child &&
element.child.forEach((ele) => {
if (ele.token == editToken) {
ele["content"] = editInput.value;
ele["image"] = image;
}
});
});
editPicture.value = {};
editToken = "";
editCommentState.value = false;
editEmojiState.value = false;
ElMessage.success(res.message);
});
};
const closeEditEmoji = () => (editEmojiState.value = false);
const handleEditFile = () => {
editEmojiState.value = false;
judgeLogin();
};
const closeEditFileUpload = () => (editPicture.value = {});
</script>
<style lang="less" scoped>
@import url(@/assets/css/details.css);
</style>
<style lang="less">
@import url(@/assets/css/detailsPop.css);
</style>