RamEx-Flow / front /src /components /LoadingSpinner.vue
zdy10046's picture
add backend code
57528c5
Raw
History Blame Contribute Delete
3.23 kB
<template>
<div class="loading-container" v-if="isLoading">
<div class="loading-spinner">
<div class="spinner-ring" ref="ring1"></div>
<div class="spinner-ring" ref="ring2"></div>
<div class="spinner-ring" ref="ring3"></div>
<div class="spinner-dot" ref="dot"></div>
</div>
<p class="loading-text">{{ loadingText }}</p>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { gsap } from 'gsap'
// 定义组件名称
defineOptions({
name: 'LoadingSpinner'
})
interface Props {
isLoading: boolean
loadingText?: string
}
const props = withDefaults(defineProps<Props>(), {
loadingText: '加载中...'
})
const ring1 = ref<HTMLElement>()
const ring2 = ref<HTMLElement>()
const ring3 = ref<HTMLElement>()
const dot = ref<HTMLElement>()
onMounted(() => {
if (props.isLoading) {
startAnimation()
}
})
watch(() => props.isLoading, (newVal) => {
if (newVal) {
startAnimation()
} else {
stopAnimation()
}
})
const startAnimation = () => {
if (!ring1.value || !ring2.value || !ring3.value || !dot.value) return
// 外环动画
gsap.to(ring1.value, {
rotation: 360,
duration: 2,
ease: "none",
repeat: -1
})
// 中环动画
gsap.to(ring2.value, {
rotation: -360,
duration: 1.5,
ease: "none",
repeat: -1
})
// 内环动画
gsap.to(ring3.value, {
rotation: 360,
duration: 1,
ease: "none",
repeat: -1
})
// 中心点脉冲动画
gsap.to(dot.value, {
scale: 1.2,
duration: 0.8,
ease: "power2.inOut",
repeat: -1,
yoyo: true
})
}
const stopAnimation = () => {
gsap.killTweensOf([ring1.value, ring2.value, ring3.value, dot.value])
}
</script>
<style scoped>
.loading-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
}
.loading-spinner {
position: relative;
width: 80px;
height: 80px;
margin-bottom: 2rem;
}
.spinner-ring {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 3px solid transparent;
border-radius: 50%;
}
.spinner-ring:nth-child(1) {
border-top-color: var(--ramex-accent);
border-right-color: var(--ramex-accent);
}
.spinner-ring:nth-child(2) {
width: 60px;
height: 60px;
top: 10px;
left: 10px;
border-top-color: var(--ramex-text-secondary);
border-left-color: var(--ramex-text-secondary);
}
.spinner-ring:nth-child(3) {
width: 40px;
height: 40px;
top: 20px;
left: 20px;
border-top-color: var(--ramex-text-muted);
border-right-color: var(--ramex-text-muted);
}
.spinner-dot {
position: absolute;
top: 50%;
left: 50%;
width: 12px;
height: 12px;
background: var(--ramex-accent);
border-radius: var(--ramex-radius);
transform: translate(-50%, -50%);
}
.loading-text {
font-size: 1.1rem;
font-weight: 500;
color: var(--ramex-text-secondary);
margin: 0;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 0.6;
}
50% {
opacity: 1;
}
}
</style>