123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- <template>
- <view class="u-skeleton" :class="[`u-skeleton--${type}`, `u-skeleton--${theme}`]" v-if="loading">
- <template v-if="type === 'paragraph'">
- <view class="u-skeleton-paragraph">
- <view
- class="u-skeleton-paragraph-line"
- v-for="(item, index) in paragraphRows"
- :key="index"
- :style="{ width: `${lineWidth}%` }"
- ></view>
- </view>
- </template>
-
- <template v-else-if="type === 'card'">
- <view class="u-skeleton-card">
- <view class="u-skeleton-card-header"></view>
- <view class="u-skeleton-card-content">
- <view class="u-skeleton-card-row"></view>
- <view class="u-skeleton-card-row" :style="{ width: '70%' }"></view>
- <view class="u-skeleton-card-row" :style="{ width: '90%' }"></view>
- </view>
- </view>
- </template>
-
- <template v-else-if="type === 'list'">
- <view class="u-skeleton-list-item" v-for="i in 3" :key="i">
- <view class="u-skeleton-list-avatar"></view>
- <view class="u-skeleton-list-content">
- <view class="u-skeleton-list-title"></view>
- <view class="u-skeleton-list-desc"></view>
- </view>
- </view>
- </template>
-
- <template v-else>
- <slot></slot>
- </template>
- </view>
- </template>
- <script setup>
- import { computed } from 'vue'
- const props = defineProps({
- loading: {
- type: Boolean,
- default: true
- },
- type: {
- type: String,
- default: 'paragraph',
- validator: value => ['paragraph', 'card', 'list', 'custom'].includes(value)
- },
- rows: {
- type: Number,
- default: 3
- },
- theme: {
- type: String,
- default: 'primary',
- validator: value => ['primary', 'dark', 'light'].includes(value)
- },
- lineWidth: {
- type: Number,
- default: 100
- }
- })
- const paragraphRows = computed(() => {
- return Array.from({ length: props.rows })
- })
- </script>
- <style lang="less">
- @keyframes u-skeleton-blink {
- 0% { background-color: #f0f0f0; }
- 50% { background-color: #e0e0e0; }
- 100% { background-color: #f0f0f0; }
- }
- .u-skeleton {
- padding: 16rpx;
- border-radius: 8rpx;
- overflow: hidden;
-
- &--primary {
- background-color: #f7f7f7;
- }
-
- &--dark {
- background-color: #f0f0f0;
- }
-
- &--light {
- background-color: #ffffff;
- }
-
- &::before {
- content: "";
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- animation: u-skeleton-blink 1.5s infinite ease-in-out;
- z-index: 1;
- }
- }
- .u-skeleton-paragraph {
- padding: 20rpx;
-
- &-line {
- height: 24rpx;
- background-color: #e9e9e9;
- margin-bottom: 20rpx;
- border-radius: 4rpx;
- }
- }
- .u-skeleton-card {
- padding: 24rpx;
- background-color: #fff;
- border-radius: 8rpx;
- margin-bottom: 16rpx;
-
- &-header {
- height: 120rpx;
- background-color: #e9e9e9;
- border-radius: 8rpx;
- margin-bottom: 20rpx;
- }
-
- &-content {
- padding: 0 10rpx;
-
- &-row {
- height: 24rpx;
- background-color: #e9e9e9;
- border-radius: 4rpx;
- margin-bottom: 16rpx;
- }
- }
- }
- .u-skeleton-list {
- background-color: #fff;
- border-radius: 8rpx;
- padding: 16rpx;
-
- &-item {
- display: flex;
- padding: 20rpx 0;
- border-bottom: 1rpx solid #eee;
-
- &:last-child {
- border-bottom: none;
- }
-
- &-avatar {
- width: 64rpx;
- height: 64rpx;
- background-color: #e9e9e9;
- border-radius: 50%;
- margin-right: 16rpx;
- }
-
- &-content {
- flex: 1;
-
- &-title {
- width: 40%;
- height: 20rpx;
- background-color: #e9e9e9;
- border-radius: 4rpx;
- margin-bottom: 12rpx;
- }
-
- &-desc {
- width: 70%;
- height: 16rpx;
- background-color: #e9e9e9;
- border-radius: 4rpx;
- }
- }
- }
- }
- </style>
|