SkeletonBlock.vue 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. <template>
  2. <view class="skeleton-wrapper">
  3. <block v-for="(item, index) in computedItems" :key="index">
  4. <!-- Banner: 通栏大矩块 -->
  5. <view v-if="type === 'banner'" class="skeleton-banner" :style="bannerStyle"></view>
  6. <!-- Card: 中等卡片 + 内部小矩块 -->
  7. <view v-else-if="type === 'card'" class="skeleton-card" :style="cardStyle">
  8. <view class="skeleton-card-inner"></view>
  9. <view class="skeleton-card-line"></view>
  10. <view class="skeleton-card-line skeleton-card-line--short"></view>
  11. </view>
  12. <!-- Row: 单行横条 -->
  13. <view v-else-if="type === 'row'" class="skeleton-row" :style="rowStyle"></view>
  14. <!-- Circle: 圆形 -->
  15. <view v-else-if="type === 'circle'" class="skeleton-circle" :style="circleStyle"></view>
  16. <!-- Text: 多行文字占位 -->
  17. <view v-else class="skeleton-text-group">
  18. <view
  19. v-for="(row, ri) in textRows"
  20. :key="ri"
  21. class="skeleton-text-row"
  22. :style="{ width: row.width }"
  23. ></view>
  24. </view>
  25. </block>
  26. </view>
  27. </template>
  28. <script>
  29. export default {
  30. name: 'SkeletonBlock',
  31. props: {
  32. type: {
  33. type: String,
  34. default: 'row'
  35. },
  36. width: {
  37. type: String,
  38. default: ''
  39. },
  40. height: {
  41. type: String,
  42. default: ''
  43. },
  44. count: {
  45. type: Number,
  46. default: 1
  47. }
  48. },
  49. computed: {
  50. computedItems() {
  51. var arr = []
  52. for (var i = 0; i < this.count; i++) {
  53. arr.push(i)
  54. }
  55. return arr
  56. },
  57. baseShimmer() {
  58. // Reuse design system shimmer from uni.scss
  59. return 'linear-gradient(90deg, #E2E8F0 25%, #EDF2F7 50%, #E2E8F0 75%)'
  60. },
  61. bannerStyle() {
  62. var style = 'width: 100%; height: 200rpx; border-radius: 16rpx;'
  63. if (this.width) {
  64. style += 'width: ' + this.width + ';'
  65. }
  66. if (this.height) {
  67. style += 'height: ' + this.height + ';'
  68. }
  69. return style
  70. },
  71. cardStyle() {
  72. var style = 'width: 100%; height: 160rpx;'
  73. if (this.width) {
  74. style += 'width: ' + this.width + ';'
  75. }
  76. if (this.height) {
  77. style += 'height: ' + this.height + ';'
  78. }
  79. return style
  80. },
  81. rowStyle() {
  82. var style = 'width: 100%; height: 32rpx; margin-bottom: 12rpx;'
  83. if (this.width) {
  84. style += 'width: ' + this.width + ';'
  85. }
  86. if (this.height) {
  87. style += 'height: ' + this.height + ';'
  88. }
  89. return style
  90. },
  91. circleStyle() {
  92. var style = 'width: 80rpx; height: 80rpx; border-radius: 50%;'
  93. if (this.width) {
  94. style += 'width: ' + this.width + ';'
  95. }
  96. if (this.height) {
  97. style += 'height: ' + this.height + ';'
  98. }
  99. return style
  100. },
  101. textRows() {
  102. return [
  103. { width: '100%' },
  104. { width: '100%' },
  105. { width: '88%' },
  106. { width: '60%' }
  107. ]
  108. }
  109. }
  110. }
  111. </script>
  112. <style scoped>
  113. .skeleton-wrapper {
  114. display: flex;
  115. flex-direction: column;
  116. }
  117. /* ===== Shimmer 基础 ===== */
  118. .skeleton-banner,
  119. .skeleton-card,
  120. .skeleton-row,
  121. .skeleton-circle,
  122. .skeleton-card-inner,
  123. .skeleton-card-line,
  124. .skeleton-text-row {
  125. background: linear-gradient(90deg, #E2E8F0 25%, #F5F9FC 50%, #E2E8F0 75%);
  126. background-size: 200% 100%;
  127. animation: skeleton-shimmer 1.5s ease-in-out infinite;
  128. will-change: transform;
  129. }
  130. @keyframes skeleton-shimmer {
  131. 0% {
  132. background-position: -200% 0;
  133. }
  134. 100% {
  135. background-position: 200% 0;
  136. }
  137. }
  138. /* ===== Banner ===== */
  139. .skeleton-banner {
  140. margin-bottom: 20rpx;
  141. }
  142. /* ===== Card ===== */
  143. .skeleton-card {
  144. background: none !important;
  145. border-radius: 16rpx;
  146. padding: 20rpx;
  147. margin-bottom: 20rpx;
  148. display: flex;
  149. flex-direction: column;
  150. justify-content: space-between;
  151. border: 1rpx solid #E2E8F0;
  152. }
  153. .skeleton-card-inner {
  154. width: 60%;
  155. height: 48rpx;
  156. border-radius: 8rpx;
  157. }
  158. .skeleton-card-line {
  159. width: 100%;
  160. height: 20rpx;
  161. border-radius: 6rpx;
  162. margin-top: 10rpx;
  163. }
  164. .skeleton-card-line--short {
  165. width: 40%;
  166. }
  167. /* ===== Row ===== */
  168. .skeleton-row {
  169. border-radius: 8rpx;
  170. }
  171. /* ===== Circle ===== */
  172. .skeleton-circle {
  173. }
  174. /* ===== Text ===== */
  175. .skeleton-text-group {
  176. display: flex;
  177. flex-direction: column;
  178. gap: 12rpx;
  179. }
  180. .skeleton-text-row {
  181. height: 24rpx;
  182. border-radius: 6rpx;
  183. }
  184. </style>