107 lines
4.2 KiB
Vue
107 lines
4.2 KiB
Vue
<template>
|
||
<Teleport to="body">
|
||
<div class="overlay" @click.self="$emit('close')">
|
||
<div class="confetti" ref="confettiEl"></div>
|
||
<div class="card">
|
||
<div class="badge">🎉</div>
|
||
<h2>恭喜通关!</h2>
|
||
<p class="lname">{{ level.title }}</p>
|
||
|
||
<div class="stars-row">
|
||
<span v-for="s in 3" :key="s"
|
||
class="star" :class="{ earned: s <= stars }"
|
||
:style="{ animationDelay: s * 0.3 + 's' }">★</span>
|
||
</div>
|
||
|
||
<div class="stats">
|
||
<div class="stat"><span class="sl">代码行数</span><span class="sv">{{ instructionCount }}</span></div>
|
||
<div class="stat"><span class="sl">获得星星</span><span class="sv">{{ stars }} / 3</span></div>
|
||
</div>
|
||
|
||
<div class="actions">
|
||
<button class="btn-retry" @click="$emit('retry')">再试一次</button>
|
||
<button v-if="level.id < 10" class="btn-next" @click="$emit('next')">下一关 →</button>
|
||
<button v-else class="btn-next" @click="$emit('close')">返回关卡</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Teleport>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from 'vue'
|
||
defineProps({ level: Object, stars: Number, instructionCount: Number })
|
||
defineEmits(['next', 'retry', 'close'])
|
||
|
||
const confettiEl = ref(null)
|
||
|
||
onMounted(() => {
|
||
if (!confettiEl.value) return
|
||
const colors = ['#3b82f6','#06b6d4','#10b981','#f59e0b','#ef4444','#8b5cf6','#ec4899']
|
||
for (let i = 0; i < 60; i++) {
|
||
const p = document.createElement('div')
|
||
p.className = 'cp'
|
||
p.style.left = `${Math.random() * 100}%`
|
||
p.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)]
|
||
p.style.animationDuration = `${2 + Math.random() * 3}s`
|
||
p.style.animationDelay = `${Math.random() * 0.8}s`
|
||
p.style.width = `${6 + Math.random() * 8}px`
|
||
p.style.height = `${6 + Math.random() * 8}px`
|
||
confettiEl.value.appendChild(p)
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.overlay {
|
||
position: fixed; inset: 0;
|
||
background: rgba(0,0,0,0.7);
|
||
display: flex; align-items: center; justify-content: center;
|
||
z-index: 1000;
|
||
animation: fadeIn 0.3s;
|
||
}
|
||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||
|
||
.confetti { position: fixed; inset: 0; pointer-events: none; overflow: hidden; }
|
||
:deep(.cp) {
|
||
position: absolute; top: -20px; border-radius: 2px;
|
||
animation: cf linear forwards;
|
||
}
|
||
@keyframes cf {
|
||
0% { transform: translateY(0) rotate(0deg); opacity: 1; }
|
||
100% { transform: translateY(100vh) rotate(720deg); opacity: 0; }
|
||
}
|
||
|
||
.card {
|
||
background: var(--bg-card); border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg); padding: 40px;
|
||
text-align: center; max-width: 400px; width: 90%;
|
||
position: relative; z-index: 1;
|
||
animation: cardPop 0.4s cubic-bezier(0.175,0.885,0.32,1.275);
|
||
}
|
||
@keyframes cardPop { from { transform: scale(0.8); opacity: 0; } to { transform: scale(1); opacity: 1; } }
|
||
|
||
.badge { font-size: 56px; margin-bottom: 4px; }
|
||
h2 { font-size: 24px; margin-bottom: 4px; }
|
||
.lname { color: var(--text-secondary); margin-bottom: 24px; }
|
||
|
||
.stars-row { display: flex; justify-content: center; gap: 12px; margin-bottom: 24px; }
|
||
.star {
|
||
font-size: 48px; color: var(--border);
|
||
animation: sp 0.5s cubic-bezier(0.175,0.885,0.32,1.275) both;
|
||
}
|
||
.star.earned { color: var(--accent-yellow); text-shadow: 0 0 20px rgba(245,158,11,0.5); }
|
||
@keyframes sp { 0% { transform: scale(0) rotate(-180deg); opacity: 0; } 100% { transform: scale(1) rotate(0); opacity: 1; } }
|
||
|
||
.stats { display: flex; justify-content: center; gap: 32px; margin-bottom: 28px; }
|
||
.stat { text-align: center; }
|
||
.sl { display: block; font-size: 12px; color: var(--text-muted); margin-bottom: 4px; }
|
||
.sv { font-family: var(--font-mono); font-size: 20px; font-weight: 600; }
|
||
|
||
.actions { display: flex; gap: 12px; justify-content: center; }
|
||
.btn-retry { padding: 10px 24px; background: var(--bg-surface); color: var(--text-primary); border: 1px solid var(--border); }
|
||
.btn-retry:hover { background: var(--bg-hover); }
|
||
.btn-next { padding: 10px 24px; background: linear-gradient(135deg, var(--accent-blue), var(--accent-cyan)); color: white; font-weight: 600; }
|
||
.btn-next:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(59,130,246,0.3); }
|
||
</style>
|