simpleasm/frontend/src/components/LevelComplete.vue

107 lines
4.2 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>
<Teleport to="body">
<div class="overlay" @click.self="$emit('close')">
<div class="confetti" ref="confettiEl"></div>
<div class="card">
<div class="badge">&#127881;</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' }">&#9733;</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')">下一关 &rarr;</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>