|
|
<template> |
|
|
<div class="loading-container" :class="{ overlay: overlay }"> |
|
|
<div class="loading-content"> |
|
|
|
|
|
<div class="loading-spinner" :class="size"> |
|
|
<div v-for="i in 12" :key="i" class="spinner-bar" :style="{ '--delay': `${i * 0.1}s` }"></div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div v-if="text" class="loading-text">{{ text }}</div> |
|
|
</div> |
|
|
</div> |
|
|
</template> |
|
|
|
|
|
<script setup> |
|
|
defineProps({ |
|
|
|
|
|
overlay: { |
|
|
type: Boolean, |
|
|
default: false |
|
|
}, |
|
|
|
|
|
|
|
|
text: { |
|
|
type: String, |
|
|
default: '' |
|
|
}, |
|
|
|
|
|
|
|
|
size: { |
|
|
type: String, |
|
|
default: 'medium', |
|
|
validator: (value) => ['small', 'medium', 'large'].includes(value) |
|
|
} |
|
|
}) |
|
|
</script> |
|
|
|
|
|
<style scoped> |
|
|
.loading-container { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
padding: 20px; |
|
|
} |
|
|
|
|
|
.loading-container.overlay { |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
bottom: 0; |
|
|
background: rgba(0, 0, 0, 0.6); |
|
|
backdrop-filter: blur(4px); |
|
|
z-index: 9999; |
|
|
} |
|
|
|
|
|
.loading-content { |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
gap: 16px; |
|
|
} |
|
|
|
|
|
.loading-spinner { |
|
|
position: relative; |
|
|
display: inline-block; |
|
|
} |
|
|
|
|
|
.loading-spinner.small { |
|
|
width: 20px; |
|
|
height: 20px; |
|
|
} |
|
|
|
|
|
.loading-spinner.medium { |
|
|
width: 32px; |
|
|
height: 32px; |
|
|
} |
|
|
|
|
|
.loading-spinner.large { |
|
|
width: 48px; |
|
|
height: 48px; |
|
|
} |
|
|
|
|
|
.spinner-bar { |
|
|
position: absolute; |
|
|
width: 2px; |
|
|
height: 25%; |
|
|
background: var(--accent-red); |
|
|
border-radius: 1px; |
|
|
opacity: 0; |
|
|
animation: spin 1.2s linear infinite; |
|
|
animation-delay: var(--delay); |
|
|
transform-origin: 50% 200%; |
|
|
} |
|
|
|
|
|
.spinner-bar:nth-child(1) { transform: rotate(0deg); } |
|
|
.spinner-bar:nth-child(2) { transform: rotate(30deg); } |
|
|
.spinner-bar:nth-child(3) { transform: rotate(60deg); } |
|
|
.spinner-bar:nth-child(4) { transform: rotate(90deg); } |
|
|
.spinner-bar:nth-child(5) { transform: rotate(120deg); } |
|
|
.spinner-bar:nth-child(6) { transform: rotate(150deg); } |
|
|
.spinner-bar:nth-child(7) { transform: rotate(180deg); } |
|
|
.spinner-bar:nth-child(8) { transform: rotate(210deg); } |
|
|
.spinner-bar:nth-child(9) { transform: rotate(240deg); } |
|
|
.spinner-bar:nth-child(10) { transform: rotate(270deg); } |
|
|
.spinner-bar:nth-child(11) { transform: rotate(300deg); } |
|
|
.spinner-bar:nth-child(12) { transform: rotate(330deg); } |
|
|
|
|
|
@keyframes spin { |
|
|
0%, 40%, 100% { |
|
|
opacity: 0.3; |
|
|
} |
|
|
20% { |
|
|
opacity: 1; |
|
|
} |
|
|
} |
|
|
|
|
|
.loading-text { |
|
|
font-size: 14px; |
|
|
color: var(--text-secondary); |
|
|
text-align: center; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
|
|
|
@media (max-width: 375px) { |
|
|
.loading-container { |
|
|
padding: 16px; |
|
|
} |
|
|
|
|
|
.loading-content { |
|
|
gap: 12px; |
|
|
} |
|
|
|
|
|
.loading-text { |
|
|
font-size: 13px; |
|
|
} |
|
|
} |
|
|
</style> |