221 lines
4.9 KiB
Vue
Raw Normal View History

2025-07-19 20:00:08 +08:00
<template>
<view class="loading-skeleton">
<view v-for="n in count" :key="n" class="skeleton-item" :class="type">
<!-- 列表类型骨架屏 -->
<template v-if="type === 'list'">
<view class="skeleton-header">
<view class="skeleton-avatar"></view>
<view class="skeleton-content">
<view class="skeleton-line long"></view>
<view class="skeleton-line short"></view>
</view>
<view class="skeleton-badge"></view>
</view>
<view class="skeleton-body">
<view class="skeleton-line"></view>
<view class="skeleton-line"></view>
<view class="skeleton-line medium"></view>
</view>
<view class="skeleton-footer">
<view class="skeleton-button"></view>
<view class="skeleton-button"></view>
<view class="skeleton-button"></view>
</view>
</template>
<!-- 卡片类型骨架屏 -->
<template v-else-if="type === 'card'">
<view class="skeleton-card-header">
<view class="skeleton-circle"></view>
<view class="skeleton-lines">
<view class="skeleton-line"></view>
<view class="skeleton-line short"></view>
</view>
</view>
<view class="skeleton-card-body">
<view class="skeleton-line"></view>
<view class="skeleton-line"></view>
</view>
</template>
<!-- 简单类型骨架屏 -->
<template v-else>
<view class="skeleton-line"></view>
<view class="skeleton-line medium"></view>
<view class="skeleton-line short"></view>
</template>
</view>
</view>
</template>
<script>
export default {
name: 'LoadingSkeleton',
props: {
type: {
type: String,
default: 'simple', // simple, list, card
validator: value => ['simple', 'list', 'card'].includes(value)
},
count: {
type: Number,
default: 3
}
}
}
</script>
<style lang="scss" scoped>
.loading-skeleton {
padding: 20rpx;
}
.skeleton-item {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
&.list {
.skeleton-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.skeleton-avatar {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
margin-right: 20rpx;
}
.skeleton-content {
flex: 1;
.skeleton-line {
&.long {
width: 80%;
}
&.short {
width: 50%;
margin-top: 8rpx;
}
}
}
.skeleton-badge {
width: 60rpx;
height: 30rpx;
border-radius: 15rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
}
.skeleton-body {
margin-bottom: 20rpx;
.skeleton-line {
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 0;
}
&.medium {
width: 70%;
}
}
}
.skeleton-footer {
display: flex;
gap: 12rpx;
.skeleton-button {
flex: 1;
height: 60rpx;
border-radius: 30rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
}
}
}
&.card {
.skeleton-card-header {
display: flex;
align-items: center;
margin-bottom: 20rpx;
.skeleton-circle {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
margin-right: 20rpx;
}
.skeleton-lines {
flex: 1;
.skeleton-line {
margin-bottom: 8rpx;
&.short {
width: 60%;
}
}
}
}
.skeleton-card-body {
.skeleton-line {
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 0;
}
}
}
}
}
.skeleton-line {
height: 24rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4rpx;
&.short {
width: 40%;
}
&.medium {
width: 60%;
}
&.long {
width: 80%;
}
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
</style>