122 lines
2.6 KiB
Vue
122 lines
2.6 KiB
Vue
|
<template>
|
||
|
<div :class="containerClasses">
|
||
|
<!-- 旋转加载器 -->
|
||
|
<div
|
||
|
v-if="type === 'spinner'"
|
||
|
:class="[
|
||
|
'animate-spin rounded-full border-2 border-current border-t-transparent',
|
||
|
sizeClasses,
|
||
|
colorClasses
|
||
|
]"
|
||
|
/>
|
||
|
|
||
|
<!-- 脉冲加载器 -->
|
||
|
<div
|
||
|
v-else-if="type === 'pulse'"
|
||
|
:class="[
|
||
|
'animate-pulse rounded-full bg-current',
|
||
|
sizeClasses,
|
||
|
colorClasses
|
||
|
]"
|
||
|
/>
|
||
|
|
||
|
<!-- 点状加载器 -->
|
||
|
<div v-else-if="type === 'dots'" :class="['flex space-x-1', colorClasses]">
|
||
|
<div
|
||
|
v-for="i in 3"
|
||
|
:key="i"
|
||
|
:class="[
|
||
|
'rounded-full bg-current animate-bounce',
|
||
|
dotSizeClasses
|
||
|
]"
|
||
|
:style="{ animationDelay: `${(i - 1) * 0.1}s` }"
|
||
|
/>
|
||
|
</div>
|
||
|
|
||
|
<!-- 骨架屏加载器 -->
|
||
|
<div v-else-if="type === 'skeleton'" class="animate-pulse space-y-3">
|
||
|
<div class="h-4 bg-gray-200 rounded w-3/4"></div>
|
||
|
<div class="h-4 bg-gray-200 rounded w-1/2"></div>
|
||
|
<div class="h-4 bg-gray-200 rounded w-5/6"></div>
|
||
|
</div>
|
||
|
|
||
|
<!-- 加载文本 -->
|
||
|
<div v-if="text" :class="['mt-2 text-sm', colorClasses]">
|
||
|
{{ text }}
|
||
|
</div>
|
||
|
</div>
|
||
|
</template>
|
||
|
|
||
|
<script setup lang="ts">
|
||
|
import { computed } from 'vue'
|
||
|
|
||
|
interface Props {
|
||
|
type?: 'spinner' | 'pulse' | 'dots' | 'skeleton'
|
||
|
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
||
|
color?: 'primary' | 'secondary' | 'gray' | 'white'
|
||
|
text?: string
|
||
|
center?: boolean
|
||
|
overlay?: boolean
|
||
|
}
|
||
|
|
||
|
const props = withDefaults(defineProps<Props>(), {
|
||
|
type: 'spinner',
|
||
|
size: 'md',
|
||
|
color: 'primary',
|
||
|
center: false,
|
||
|
overlay: false
|
||
|
})
|
||
|
|
||
|
// 容器类
|
||
|
const containerClasses = computed(() => {
|
||
|
const classes = []
|
||
|
|
||
|
if (props.center) {
|
||
|
classes.push('flex flex-col items-center justify-center')
|
||
|
}
|
||
|
|
||
|
if (props.overlay) {
|
||
|
classes.push(
|
||
|
'fixed inset-0 bg-black bg-opacity-50 z-50',
|
||
|
'flex items-center justify-center'
|
||
|
)
|
||
|
}
|
||
|
|
||
|
return classes
|
||
|
})
|
||
|
|
||
|
// 尺寸类
|
||
|
const sizeClasses = computed(() => {
|
||
|
const sizes = {
|
||
|
xs: 'w-3 h-3',
|
||
|
sm: 'w-4 h-4',
|
||
|
md: 'w-6 h-6',
|
||
|
lg: 'w-8 h-8',
|
||
|
xl: 'w-12 h-12'
|
||
|
}
|
||
|
return sizes[props.size]
|
||
|
})
|
||
|
|
||
|
// 点状加载器尺寸
|
||
|
const dotSizeClasses = computed(() => {
|
||
|
const sizes = {
|
||
|
xs: 'w-1 h-1',
|
||
|
sm: 'w-1.5 h-1.5',
|
||
|
md: 'w-2 h-2',
|
||
|
lg: 'w-3 h-3',
|
||
|
xl: 'w-4 h-4'
|
||
|
}
|
||
|
return sizes[props.size]
|
||
|
})
|
||
|
|
||
|
// 颜色类
|
||
|
const colorClasses = computed(() => {
|
||
|
const colors = {
|
||
|
primary: 'text-primary',
|
||
|
secondary: 'text-text-secondary',
|
||
|
gray: 'text-gray-400',
|
||
|
white: 'text-white'
|
||
|
}
|
||
|
return colors[props.color]
|
||
|
})
|
||
|
</script>
|