PC-vote/pages/details/[id].vue
2024-07-10 18:52:39 +08:00

761 lines
24 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>
<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>
</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">
<div class="respond">
<div class="respond-title flexacenter">
回应
<div class="respond-amount">21</div>
</div>
<div class="respond-no flexflex">
<div class="respond-no-box flex1 flexflex">
<div class="item" v-for="item in 8" :key="item">&#xc150;</div>
</div>
<RespondAdd></RespondAdd>
</div>
<div class="respond-box">
<div class="item flexacenter pitch" v-for="item in 18" :key="item">
<div class="code flexacenter">&#xc150;</div>
14
</div>
<RespondAdd></RespondAdd>
<div class="respond-select flexflex">
<div class="respond-select-box flex1 flexflex">
<div class="respond-select-item" v-for="item in 5" :key="item">&#xc150;</div>
</div>
<RespondAdd></RespondAdd>
</div>
</div>
</div>
<DetailsComments ref="commentsRef" :token="token"></DetailsComments>
</div>
<DetailsArea @closeDiscussInputFields="closeDiscussInputFields"></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=vote" }, { src: "https://app.gter.net/bottom?tpl=footer", body: true }] })
import { useRoute, useRouter } from "vue-router"
import { ElMessage } from "element-plus"
import { da } from "element-plus/es/locale"
const route = useRoute()
const router = useRouter()
let isNeedLogin = inject("isNeedLogin")
const goLogin = inject("goLogin")
let id = route.params.id
let uniqidIndex = ref(0)
if (route.query.colorI) uniqidIndex.value = route.query.colorI
else uniqidIndex.value = Math.floor(Math.random() * 6)
if (uniqidIndex.value > 6) uniqidIndex = 0
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 () => {
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
})
})
}
provide("getDetails", getDetails)
// 点击发送信息
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}`)
}
// 点击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.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)
// 点击底部调用关闭讨论输入框
const closeDiscussInputFields = () => {
commentsRef.value.closeAnswerCommentsChild()
}
try {
if (process.server) {
await detailsHttp({ uniqid: id }).then(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">
@font-face {
font-family: "emojifont";
src: url("https://oss.x-php.com/static/riposte/emojifont-sbix.ttf");
}
.content {
width: 1200px;
margin: 0 auto;
border-radius: 16px;
background: #fff;
flex-wrap: wrap;
--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;
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;
}
&: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;
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;
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;
.respond {
.respond-title {
font-size: 16px;
line-height: 20px;
font-family: "PingFangSC-Semibold", "PingFang SC Semibold", "PingFang SC", sans-serif;
font-weight: 650;
color: #000000;
margin-bottom: 20px;
.respond-amount {
color: #555555;
font-weight: 400;
margin-left: 8px;
}
}
.respond-no {
width: 377px;
height: 30px;
background-color: rgba(255, 255, 255, 1);
border: 1px solid rgba(235, 235, 235, 1);
border-radius: 208px;
margin-bottom: 10px;
position: relative;
.respond-no-box {
justify-content: space-around;
.item {
line-height: 30px;
font-size: 16px;
font-family: "emojifont";
cursor: pointer;
}
}
}
.respond-box {
display: flex;
flex-wrap: wrap;
position: relative;
.item {
font-size: 12px;
color: #555555;
height: 30px;
border: 1px solid rgba(215, 215, 215, 1);
border-radius: 8px;
padding: 0 6px;
display: inline-flex;
margin-right: 10px;
margin-bottom: 10px;
cursor: pointer;
&.pitch {
border: none;
background: rgba(246, 246, 246, 1);
}
.code {
margin-right: 4px;
line-height: 30px;
font-size: 16px;
font-family: "emojifont";
}
}
.respond-select {
width: 250px;
height: 30px;
background-color: rgba(255, 255, 255, 1);
border: 1px solid rgba(235, 235, 235, 1);
border-radius: 208px;
.respond-select-box {
justify-content: space-around;
.respond-select-item {
cursor: pointer;
font-size: 16px;
font-family: "emojifont";
line-height: 30px;
}
}
/deep/ .respond-add {
background: rgba(98, 177, 255, 1);
}
}
}
}
}
}
</style>
<style lang="less">
.default-popup {
.el-dialog__header {
padding: 0;
.el-dialog__headerbtn {
width: 36px;
height: 36px;
}
}
.el-dialog__body {
padding: 0;
}
}
</style>