<template> <Head> <Title>{{ `${seo["title"] || "投票"} - 寄托天下出国留学网` }}</Title> <Meta name="keyword" :content="seo['keyword']" /> <Meta name="description" :content="seo['description']" /> </Head> <TopHead ref="topHeadRef"></TopHead> <div class="content flexflex" :style="{ '--main-color': colourValue[uniqidIndex]['main'], '--bg-color': colourValue[uniqidIndex]['bg'], '--bc-color': colourValue[uniqidIndex]['bc'] }"> <div class="header flexacenter"> <span>{{ info.title }}</span> <!-- <ClientOnly> <span class="views flexcenter"> <img class="eye-icon" src="@/assets/img/eye-icon.svg" /> {{ info.views }} </span> </ClientOnly> --> </div> <div class="left"> <div class="info flexacenter"> <div class="info-left flexacenter"> <el-popover placement="bottom-start" :width="140" trigger="click" popper-class="avatar-box-popper" :show-arrow="false"> <template #reference> <div class="flexcenter"> <img class="avatar" :src="info.avatar" /> <div class="username">{{ info.nickname }}</div> </div> </template> <div class="avatar-box flexflex" v-if="info['uin']"> <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="post-time" v-if="info.releasetime">{{ handleDate(info.releasetime) }}发布</div> </div> <div class="info-right flexacenter" v-if="info['status'] == 1"> <div class="cut-off">{{ handleDeadline(info.deadline) }}结束</div> <div class="state">进行中</div> </div> <div class="info-right flexacenter" v-else> <div class="cut-off" v-if="info.deadline">已于{{ info.deadline }}结束</div> <div class="state over">已结束</div> </div> </div> <div class="message">{{ info.message }}</div> <div class="hint">{{ info.status == 1 && isvote == 0 ? `已有 ${info.votes || 0} 人参与,` : `共有 ${info.votes || 0} 人参与` }} {{ `${isvote == 1 ? "你已投票" : info.status == 1 ? "参与投票即可查看实时结果" : ""}` }}</div> <ClientOnly> <div class="option-list flexflex" v-if="info['status'] == 1 && isvote == 0"> <div class="option-item flexflex" v-for="(item, index) in option" :key="item.id" @click="handleVote(item.id, index)"> <div class="serial flexcenter">{{ index + 1 }}</div> <span class="flex1">{{ item.value }} </span> </div> </div> <div class="option-area" v-else> <div class="option-item flexflex" :class="{ 'pitch': item.selected, 'cursor-no': info.status == 0 }" v-for="(item, index) in option" :key="item.id" @click="handleUnvoteVote(index, item.selected)"> <div class="flexflex" style="padding: 2px 0px;"> <div class="option-number flexcenter">{{ index + 1 }}</div> <img class="tick-icon" src="@/assets/img/tick-black.svg" /> <div class="option-content flex1">{{ item.value }}</div> </div> <div class="option-progress flexacenter"> <div class="option-progress-step" :style="{ width: item.percentage + '%' }"></div> <div class="option-progress-value">{{ item.count }}</div> </div> </div> </div> </ClientOnly> </div> <div class="right"><DetailsComments ref="commentsRef" :token="token"></DetailsComments></div> <DetailsArea></DetailsArea> </div> <el-dialog class="default-popup options-popup" v-model="cancelPopoverState" width="488px" align-center> <div class="options-popup-text">您要取消投票吗?</div> <div class="options-popup-btn flexflex"> <div class="options-popup-item options-no flexcenter" @click="unvoteVote">取消投票</div> <div class="options-popup-item options-yes flexcenter" @click="cancelPopoverState = false">不取消</div> </div> </el-dialog> </template> <script setup> useHead({ script: [{ src: "https://app.gter.net/bottom?tpl=header&menukey=mj" }, { src: "https://app.gter.net/bottom?tpl=footer", body: true }] }) import { useRoute, useRouter } from "vue-router" import { ElMessage } from "element-plus" const route = useRoute() const router = useRouter() let isNeedLogin = inject("isNeedLogin") const goLogin = inject("goLogin") let id = route.params.id const uniqidEnd = id.charAt(id.length - 1) const uniqidIndex = base62ToDecimal(uniqidEnd) % 6 onMounted(() => { getDetails() clearBottom() }) let info = ref({}) let qrcode = ref("") // 分享二维码 let iscollection = ref(0) // 是否收藏 let islike = ref(0) // 是否点赞 let ismyself = ref(0) // 是否是作者 let detailsLoading = ref(false) // 详情加载中 let isvote = ref(0) // 是否已经投票 let option = ref([]) let token = ref("") let cancelPopoverState = ref(false) // 取消投票弹窗 let isLoaded = ref(false) // 是否加载了 let haveVotedValue = ref("") // 已投的值 provide("info", info) provide("islike", islike) provide("iscollection", iscollection) provide("token", token) provide("qrcode", qrcode) provide("isLoaded", isLoaded) provide("haveVotedValue", haveVotedValue) const getDetails = async () => { // const { data1, pending1, error1, refresh1 } = await useAsyncData("mountains", () => // $fetch("https://vote.gter.net/api/details", { // params: { // uniqid: id, // }, // }) // ) // const { data, pending, error, refresh } = await useFetch(`https://vote.gter.net/api/details`, { // key: "details", // body: { // uniqid: id, // }, // method: "post", // }) // const { data, pending, error, refresh } = await useFetch(`https://vote.gter.net/api/details`, { // method: "post", // body: { // uniqid: id, // }, // }) // let datadata = data.value.data // info.value = datadata["info"] // isvote.value = datadata["isvote"] // iscollection.value = datadata["iscollection"] // islike.value = datadata["islike"] // ismyself.value = datadata["ismyself"] // option.value = datadata["option"] // qrcode.value = datadata.share?.qrcode // token.value = datadata["token"] // seo.value = datadata.seo // isLoaded.value = true // datadata["option"].forEach(element => { // if (element.selected) haveVotedValue.value = element.value // }) // return detailsHttp({ uniqid: id }).then(res => { if (res.code != 200) { ElMessage.error(res.message) goToURL("/index.html", false) return } let data = res.data info.value = data["info"] isvote.value = data["isvote"] iscollection.value = data["iscollection"] islike.value = data["islike"] ismyself.value = data["ismyself"] option.value = data["option"] qrcode.value = data.share?.qrcode token.value = data["token"] seo.value = data.seo isLoaded.value = true data["option"].forEach(element => { if (element.selected) haveVotedValue.value = element.value }) }) } // 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]) // } // } // } // ) provide("getDetails", getDetails) // 点击发送信息 const sendMessage = 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() } provide("sendMessage", sendMessage) provide("TAHomePage", TAHomePage) const commentsRef = ref(null) let voteLoading = false // 处理点击投票的中转 const handleVotesTransfer = index => { const target = option.value[index] if (info.value.status == 1 && isvote.value == 0) handleVote(target.id, index) else handleUnvoteVote(index) } // 处理点击投票 const handleVote = (token, index) => { if (isNeedLogin.value) { goLogin() return } if (voteLoading) return voteLoading = true topHeadRef.value.count = {} operationCollectHttp({ token }) .then(res => { if (res.code != 200) { ElMessage.error(res.message) return } let data = res.data let optionList = data["optionList"] || [] optionList.forEach(element => { element["selected"] = 0 }) optionList[index]["selected"] = 1 option.value = optionList isvote.value = 1 info.value.votes = data["votes"] const value = optionList[index]["value"] haveVotedValue.value = value commentsRef.value.changeCommentVoteoption(value) ElMessage.success(res.message) if (index != optionList.length - 1) commentsRef.value.reviewsComment(optionList[index]["value"]) }) .finally(() => (voteLoading = false)) } let unvoteVoteIndex = null // 选项下标 // 点击 取消投票 const handleUnvoteVote = (index, selected) => { if (isNeedLogin.value) { goLogin() return } if (selected == 0 || info.value.status == 0) return cancelPopoverState.value = true unvoteVoteIndex = index } const unvoteVote = () => { if (isNeedLogin.value) { goLogin() return } const token = option.value[unvoteVoteIndex].id if (voteLoading) return voteLoading = true topHeadRef.value.count = {} unvoteCollectHttp({ token }) .then(res => { if (res.code != 200) { ElMessage.error(res.message) return } let data = res.data let optionList = data["optionList"] || [] // optionList[unvoteVoteIndex]["selected"] = 0 optionList.forEach(element => { element["selected"] = 0 }) option.value = optionList isvote.value = 0 info.value.votes = data["votes"] cancelPopoverState.value = false commentsRef.value.wipeCommentVoteoption() }) .finally(() => (voteLoading = false)) } const clearAllData = () => { info.value = {} qrcode.value = "" iscollection.value = 0 islike.value = 0 ismyself.value = 0 isvote.value = 0 option.value = [] } provide("clearAllData", clearAllData) // 取消了同页面的收藏 const unbookmarkSamePage = () => { iscollection.value = 0 info.value.favs-- } provide("unbookmarkSamePage", unbookmarkSamePage) // 删除同页面的投票需要跳转到 首页 const unbookmark = () => router.push("/index.html") provide("unbookmark", unbookmark) let seo = ref({}) // 清除底部的次数 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" } let topHeadRef = ref(null) provide("topHeadRef", topHeadRef) // 底部导航栏 的 点击评论输入值 let floorCommentInput = ref("") // 底部导航栏 的 点击发送评论 type input back const floorCommentBtn = type => { if (type == "input") commentsRef.value.bottomNavigationBar(floorCommentInput.value) else floorCommentInput.value = "" } provide("floorCommentInput", floorCommentInput) provide("floorCommentBtn", floorCommentBtn) // 只刷新数据 const refreshDataOnly = () => { clearAllData() getDetails() } provide("refreshDataOnly", refreshDataOnly) try { if (process.server) { // // const { data, pending, error, refresh } = await useFetch("https://vote.gter.net/api/details", { // // query: { uniqid: id }, // // method: "post", // // server: true, // // }) // // const { data, pending, error, refresh } = await useFetch(`https://vote.gter.net/api/details`, { // // method: "post", // // body: { // // uniqid: id + "111", // // }, // // }) // const { data, pending, error, refresh } = await useFetch(`https://vote.gter.net/api/details`, { // key: "details", // body: { // uniqid: id, // }, // method: "post", // }) // console.log("-------------分割线-------------------") await detailsHttp({ uniqid: id }).then(res => { // console.log("res", res) if (res.code != 200) { ElMessage.error(res.message) router.push("/index.html") return } let data = res.data info.value = data["info"] option.value = data["option"] isvote.value = data["isvote"] seo.value = data.seo }) } } catch (error) {} </script> <style scoped lang="less"> .content { width: 1200px; // height: 500px; margin: 0 auto; border-radius: 16px; background: #fff; flex-wrap: wrap; // min-height: 100vh; --main-color: rgba(44, 186, 230, 1); --bg-color: rgba(234, 245, 248, 1); --bc-color: rgba(213, 235, 242, 1); .header { width: 100%; height: 80px; padding: 0 30px; border-bottom: 1px solid #ebebeb; font-weight: 650; font-size: 20px; color: #000000; line-height: 20px; justify-content: space-between; .views { font-size: 12px; color: #aaa; font-weight: 400; .eye-icon { margin-right: 5px; } } } .left { width: 658px; min-height: calc(100vh - 165px); padding: 30px 42px 100px 30px; border-right: 16px solid #f6f6f6; .info { font-size: 13px; justify-content: space-between; margin-bottom: 24px; .info-left { .avatar { width: 24px; height: 24px; margin-right: 10px; cursor: pointer; border-radius: 50%; } .username { color: #333; margin-right: 10px; cursor: pointer; } .post-time { line-height: 22px; color: #aaa; } } .info-right { .cut-off { color: #aaa; } .state { height: 20px; line-height: 20px; padding: 0 7px; color: #fff; background: var(--main-color); border-radius: 25px; font-size: 12px; margin-left: 10px; &.over { background: rgba(51, 51, 51, 1); } } } } .message { font-size: 14px; line-height: 24px; color: #333; margin-bottom: 30px; word-wrap: break-word; white-space: break-spaces } .hint { font-size: 13px; line-height: 22px; color: #aaaaaa; margin-bottom: 16px; } .tick-icon { width: 14px; height: 14px; margin-top: 3px; margin-right: 6px; } .option-list { flex-direction: column; .option-item { width: 570px; // height: 40px; // background-color: var(--bg-color); border: 1px solid var(--bc-color); border-radius: 10px; word-break: break-all; font-size: 14px; line-height: 20px; color: #333333; padding: 9px 15px; cursor: pointer; position: relative; overflow: hidden; z-index: 1; &::after { background-color: var(--bg-color); content: ""; width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: -1; // border: 1px solid var(--bc-color); } &:hover::after { background-color: var(--main-color); opacity: 0.156862745098039; } &:not(:last-of-type) { margin-bottom: 10px; } &.pitch { .option-number { display: none; } .tick-icon { display: block; } .option-content { color: #000000; font-weight: 650; } } .serial { width: 14px; height: 14px; border-radius: 50%; background: var(--main-color); font-size: 11px; color: #ffffff; margin-top: 3px; margin-right: 6px; } .option-progress, .option-number, .tick-icon { display: none; } } } .option-area { width: 570px; // height: 318px; background-color: var(--bg-color); border: 1px solid var(--bc-color); border-radius: 10px; padding: 8px 0; .option-item { padding: 7px 15px 10px; flex-direction: column; word-break: break-all; cursor: no-drop; &:not(:last-of-type) { border-bottom: 1px solid var(--bc-color); } &.pitch { cursor: pointer; .option-number { display: none; } .tick-icon { display: block; } .option-content { font-weight: 650; color: #000000; } } &.cursor-no { cursor: no-drop; } .serial { display: none; } .option-number { font-size: 11px; color: #ffffff; width: 14px; height: 14px; background-color: var(--main-color); border-radius: 50%; margin-right: 6px; margin-top: 3px; } .tick-icon { display: none; } .option-content { font-size: 14px; color: #333; line-height: 20px; word-break: break-word; } .option-progress { height: 5px; width: 100%; justify-content: flex-end; // display: none; margin-top: 3px; .option-progress-step { width: 24%; background-color: var(--main-color); opacity: 0.49803922; height: 4px; border-radius: 66px; margin-right: 14px; } .option-progress-value { font-size: 12px; color: var(--main-color); line-height: 20px; } } } } } .right { flex: 1; padding-top: 22px; padding-left: 42px; } } </style> <style lang="less"> .default-popup { .el-dialog__header { padding: 0; .el-dialog__headerbtn { width: 36px; height: 36px; } } .el-dialog__body { padding: 0; } } .options-popup { border-radius: 10px; padding: 44px 74px; .options-popup-text { font-size: 14px; color: #333333; text-align: center; margin-bottom: 71px; } .options-popup-btn { justify-content: space-between; .options-popup-item { font-size: 13px; width: 160px; height: 40px; border-radius: 150px; border: 1px solid; cursor: pointer; &.options-yes { background-color: rgba(249, 93, 93, 1); border-color: rgba(249, 93, 93, 1); color: #fff; margin-left: 20px; } &.options-no { background-color: #fff; border-color: rgba(170, 170, 170, 1); color: #333; } } } } </style>