mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-06 23:21:16 +08:00
311 lines
7.5 KiB
Vue
311 lines
7.5 KiB
Vue
<template>
|
|
<div class="post-prize-container" v-if="lottery">
|
|
<div class="prize-content">
|
|
<div class="prize-info">
|
|
<div class="prize-info-left">
|
|
<div class="prize-icon">
|
|
<BaseImage
|
|
class="prize-icon-img"
|
|
v-if="lottery.prizeIcon"
|
|
:src="lottery.prizeIcon"
|
|
alt="prize"
|
|
/>
|
|
<i v-else class="fa-solid fa-gift default-prize-icon"></i>
|
|
</div>
|
|
<div class="prize-name">{{ lottery.prizeDescription }}</div>
|
|
<div class="prize-count">x {{ lottery.prizeCount }}</div>
|
|
</div>
|
|
<div class="prize-end-time prize-info-right">
|
|
<div v-if="!isMobile" class="prize-end-time-title">离结束还有</div>
|
|
<div class="prize-end-time-value">{{ countdown }}</div>
|
|
<div v-if="!isMobile" class="join-prize-button-container-desktop">
|
|
<div
|
|
v-if="loggedIn && !hasJoined && !lotteryEnded"
|
|
class="join-prize-button"
|
|
@click="joinLottery"
|
|
>
|
|
<div class="join-prize-button-text">
|
|
参与抽奖 <i class="fas fa-coins"></i> {{ lottery.pointCost }}
|
|
</div>
|
|
</div>
|
|
<div v-else-if="hasJoined" class="join-prize-button-disabled">
|
|
<div class="join-prize-button-text">已参与</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="isMobile" class="join-prize-button-container-mobile">
|
|
<div
|
|
v-if="loggedIn && !hasJoined && !lotteryEnded"
|
|
class="join-prize-button"
|
|
@click="joinLottery"
|
|
>
|
|
<div class="join-prize-button-text">
|
|
参与抽奖 <i class="fas fa-coins"></i> {{ lottery.pointCost }}
|
|
</div>
|
|
</div>
|
|
<div v-else-if="hasJoined" class="join-prize-button-disabled">
|
|
<div class="join-prize-button-text">已参与</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="prize-member-container">
|
|
<BaseImage
|
|
v-for="p in lotteryParticipants"
|
|
:key="p.id"
|
|
class="prize-member-avatar"
|
|
:src="p.avatar"
|
|
alt="avatar"
|
|
@click="gotoUser(p.id)"
|
|
/>
|
|
<div v-if="lotteryEnded && lotteryWinners.length" class="prize-member-winner">
|
|
<i class="fas fa-medal medal-icon"></i>
|
|
<span class="prize-member-winner-name">获奖者: </span>
|
|
<BaseImage
|
|
v-for="w in lotteryWinners"
|
|
:key="w.id"
|
|
class="prize-member-avatar"
|
|
:src="w.avatar"
|
|
alt="avatar"
|
|
@click="gotoUser(w.id)"
|
|
/>
|
|
<div v-if="lotteryWinners.length === 1" class="prize-member-winner-name">
|
|
{{ lotteryWinners[0].username }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
|
import { getToken, authState } from '~/utils/auth'
|
|
import { toast } from '~/main'
|
|
import { useRuntimeConfig } from '#imports'
|
|
import { useIsMobile } from '~/utils/screen'
|
|
|
|
const props = defineProps({
|
|
lottery: { type: Object, required: true },
|
|
postId: { type: [String, Number], required: true },
|
|
})
|
|
const emit = defineEmits(['refresh'])
|
|
|
|
const isMobile = useIsMobile()
|
|
const loggedIn = computed(() => authState.loggedIn)
|
|
const lotteryParticipants = computed(() => props.lottery?.participants || [])
|
|
const lotteryWinners = computed(() => props.lottery?.winners || [])
|
|
const lotteryEnded = computed(() => {
|
|
if (!props.lottery || !props.lottery.endTime) return false
|
|
return new Date(props.lottery.endTime).getTime() <= Date.now()
|
|
})
|
|
const hasJoined = computed(() => {
|
|
if (!loggedIn.value) return false
|
|
return lotteryParticipants.value.some((p) => p.id === Number(authState.userId))
|
|
})
|
|
|
|
const countdown = ref('00:00:00')
|
|
let timer = null
|
|
const updateCountdown = () => {
|
|
if (!props.lottery || !props.lottery.endTime) {
|
|
countdown.value = '00:00:00'
|
|
return
|
|
}
|
|
const diff = new Date(props.lottery.endTime).getTime() - Date.now()
|
|
if (diff <= 0) {
|
|
countdown.value = '00:00:00'
|
|
if (timer) {
|
|
clearInterval(timer)
|
|
timer = null
|
|
}
|
|
return
|
|
}
|
|
const h = String(Math.floor(diff / 3600000)).padStart(2, '0')
|
|
const m = String(Math.floor((diff % 3600000) / 60000)).padStart(2, '0')
|
|
const s = String(Math.floor((diff % 60000) / 1000)).padStart(2, '0')
|
|
countdown.value = `${h}:${m}:${s}`
|
|
}
|
|
const startCountdown = () => {
|
|
updateCountdown()
|
|
if (timer) clearInterval(timer)
|
|
timer = setInterval(updateCountdown, 1000)
|
|
}
|
|
watch(
|
|
() => props.lottery?.endTime,
|
|
() => {
|
|
if (props.lottery && props.lottery.endTime) startCountdown()
|
|
},
|
|
)
|
|
onMounted(() => {
|
|
if (props.lottery && props.lottery.endTime) startCountdown()
|
|
})
|
|
onBeforeUnmount(() => {
|
|
if (timer) clearInterval(timer)
|
|
})
|
|
|
|
const gotoUser = (id) => navigateTo(`/users/${id}`, { replace: true })
|
|
|
|
const config = useRuntimeConfig()
|
|
const API_BASE_URL = config.public.apiBaseUrl
|
|
const joinLottery = async () => {
|
|
const token = getToken()
|
|
if (!token) {
|
|
toast.error('请先登录')
|
|
return
|
|
}
|
|
const res = await fetch(`${API_BASE_URL}/api/posts/${props.postId}/lottery/join`, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
})
|
|
const data = await res.json().catch(() => ({}))
|
|
if (res.ok) {
|
|
toast.success('已参与抽奖')
|
|
emit('refresh')
|
|
} else {
|
|
toast.error(data.error || '操作失败')
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.post-prize-container {
|
|
margin-top: 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
background-color: var(--lottery-background-color);
|
|
border-radius: 10px;
|
|
padding: 10px;
|
|
}
|
|
|
|
.prize-info {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
width: 100%;
|
|
align-items: center;
|
|
}
|
|
|
|
.join-prize-button-container-mobile {
|
|
margin-top: 15px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.prize-icon {
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
|
|
.default-prize-icon {
|
|
font-size: 24px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.prize-icon-img {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.prize-name {
|
|
font-size: 13px;
|
|
opacity: 0.7;
|
|
margin-left: 10px;
|
|
}
|
|
|
|
.prize-count {
|
|
font-size: 13px;
|
|
font-weight: bold;
|
|
opacity: 0.7;
|
|
margin-left: 10px;
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.prize-end-time {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
font-size: 13px;
|
|
opacity: 0.7;
|
|
margin-left: 10px;
|
|
}
|
|
|
|
.prize-end-time-title {
|
|
font-size: 13px;
|
|
opacity: 0.7;
|
|
margin-right: 5px;
|
|
}
|
|
|
|
.prize-end-time-value {
|
|
font-size: 13px;
|
|
font-weight: bold;
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.prize-info-left,
|
|
.prize-info-right {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
}
|
|
|
|
.join-prize-button {
|
|
margin-left: 10px;
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
padding: 5px 10px;
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
text-align: center;
|
|
}
|
|
|
|
.join-prize-button:hover {
|
|
background-color: var(--primary-color-hover);
|
|
}
|
|
|
|
.join-prize-button-disabled {
|
|
text-align: center;
|
|
margin-left: 10px;
|
|
background-color: var(--primary-color);
|
|
color: white;
|
|
padding: 5px 10px;
|
|
border-radius: 8px;
|
|
background-color: var(--primary-color-disabled);
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.prize-member-avatar {
|
|
width: 30px;
|
|
height: 30px;
|
|
margin-left: 3px;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.prize-member-winner {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: 5px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.medal-icon {
|
|
font-size: 16px;
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.prize-member-winner-name {
|
|
font-size: 13px;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.join-prize-button,
|
|
.join-prize-button-disabled {
|
|
margin-left: 0;
|
|
}
|
|
}
|
|
</style>
|