mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-06 23:21:16 +08:00
feat: add medal popup
This commit is contained in:
@@ -6,24 +6,36 @@
|
||||
text="建站送奶茶活动火热进行中,快来参与吧!"
|
||||
@close="closeMilkTeaPopup"
|
||||
/>
|
||||
<MedalPopup
|
||||
:visible="showMedalPopup"
|
||||
:medals="newMedals"
|
||||
@close="closeMedalPopup"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActivityPopup from '~/components/ActivityPopup.vue'
|
||||
import MedalPopup from '~/components/MedalPopup.vue'
|
||||
import { API_BASE_URL } from '~/main'
|
||||
import { authState } from '~/utils/auth'
|
||||
|
||||
export default {
|
||||
name: 'GlobalPopups',
|
||||
components: { ActivityPopup },
|
||||
components: { ActivityPopup, MedalPopup },
|
||||
data () {
|
||||
return {
|
||||
showMilkTeaPopup: false,
|
||||
milkTeaIcon: ''
|
||||
milkTeaIcon: '',
|
||||
showMedalPopup: false,
|
||||
newMedals: []
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
await this.checkMilkTeaActivity()
|
||||
if (!this.showMilkTeaPopup) {
|
||||
await this.checkNewMedals()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async checkMilkTeaActivity () {
|
||||
@@ -47,7 +59,34 @@ export default {
|
||||
if (!process.client) return
|
||||
localStorage.setItem('milkTeaActivityPopupShown', 'true')
|
||||
this.showMilkTeaPopup = false
|
||||
this.checkNewMedals()
|
||||
},
|
||||
async checkNewMedals () {
|
||||
if (!process.client) return
|
||||
if (!authState.loggedIn || !authState.userId) return
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/api/medals?userId=${authState.userId}`)
|
||||
if (res.ok) {
|
||||
const medals = await res.json()
|
||||
const seen = JSON.parse(localStorage.getItem('seenMedals') || '[]')
|
||||
const m = medals.filter(i => i.completed && !seen.includes(i.type))
|
||||
if (m.length > 0) {
|
||||
this.newMedals = m
|
||||
this.showMedalPopup = true
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore errors
|
||||
}
|
||||
},
|
||||
closeMedalPopup () {
|
||||
if (!process.client) return
|
||||
const seen = new Set(JSON.parse(localStorage.getItem('seenMedals') || '[]'))
|
||||
this.newMedals.forEach(m => seen.add(m.type))
|
||||
localStorage.setItem('seenMedals', JSON.stringify([...seen]))
|
||||
this.showMedalPopup = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
113
frontend_nuxt/components/MedalPopup.vue
Normal file
113
frontend_nuxt/components/MedalPopup.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<BasePopup :visible="visible" @close="close">
|
||||
<div class="medal-popup">
|
||||
<div class="medal-popup-title">恭喜你获得以下勋章</div>
|
||||
<div class="medal-popup-list">
|
||||
<div v-for="medal in medals" :key="medal.type" class="medal-popup-item">
|
||||
<img :src="medal.icon" :alt="medal.title" class="medal-popup-item-icon" />
|
||||
<div class="medal-popup-item-title">{{ medal.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="medal-popup-actions">
|
||||
<div class="medal-popup-button" @click="gotoMedals">去看看</div>
|
||||
<div class="medal-popup-close" @click="close">知道了</div>
|
||||
</div>
|
||||
</div>
|
||||
</BasePopup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BasePopup from '~/components/BasePopup.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { authState } from '~/utils/auth'
|
||||
|
||||
export default {
|
||||
name: 'MedalPopup',
|
||||
components: { BasePopup },
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
medals: { type: Array, default: () => [] }
|
||||
},
|
||||
emits: ['close'],
|
||||
setup (props, { emit }) {
|
||||
const router = useRouter()
|
||||
const gotoMedals = () => {
|
||||
emit('close')
|
||||
if (authState.username) {
|
||||
router.push(`/users/${authState.username}?tab=achievements`)
|
||||
} else {
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
const close = () => emit('close')
|
||||
return { gotoMedals, close }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.medal-popup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 10px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.medal-popup-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.medal-popup-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.medal-popup-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.medal-popup-item-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.medal-popup-actions {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.medal-popup-button {
|
||||
background-color: var(--primary-color);
|
||||
color: #fff;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.medal-popup-button:hover {
|
||||
background-color: var(--primary-color-hover);
|
||||
}
|
||||
|
||||
.medal-popup-close {
|
||||
cursor: pointer;
|
||||
color: var(--primary-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.medal-popup-close:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -288,7 +288,11 @@ export default {
|
||||
const subscribed = ref(false)
|
||||
const isLoading = ref(true)
|
||||
const tabLoading = ref(false)
|
||||
const selectedTab = ref('summary')
|
||||
const selectedTab = ref(
|
||||
['summary', 'timeline', 'following', 'achievements'].includes(route.query.tab)
|
||||
? route.query.tab
|
||||
: 'summary'
|
||||
)
|
||||
const followTab = ref('followers')
|
||||
|
||||
const levelInfo = computed(() => {
|
||||
@@ -473,6 +477,7 @@ export default {
|
||||
onMounted(init)
|
||||
|
||||
watch(selectedTab, async val => {
|
||||
router.replace({ query: { ...route.query, tab: val } })
|
||||
if (val === 'timeline' && timelineItems.value.length === 0) {
|
||||
await loadTimeline()
|
||||
} else if (val === 'following' && followers.value.length === 0 && followings.value.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user