<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) }} {{ ite }}</div> <div class="vote-item">{{ numberToEnclosed(3) }} …</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> <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 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="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> </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['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 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" @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['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="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" @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="handleHide" 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> </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}`) 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) }) // .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 = ["comment.edit", "comment.delete"] }, 1000) }) // 点击隐藏 const handleHide = () => { const userConfirmed = window.confirm("确定要隐藏该面经?") if (!userConfirmed) return mjHideHttp({ token }) } // 点击删除 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 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>