restrict-read-skeleton.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. <script>
  2. export default {
  3. name: 'restrict-read-skeleton',
  4. props: {
  5. loading: {
  6. type: Boolean,
  7. default: true
  8. },
  9. hover: {
  10. type: Boolean,
  11. default: false
  12. },
  13. buttonText: {
  14. type: String,
  15. default: '刷新'
  16. },
  17. buttonColor: {
  18. type: String,
  19. default: '#07c160'
  20. },
  21. buttonSize: {
  22. type: String,
  23. default: 'normal', // 'small', 'normal', 'large'
  24. validator: value => ['small', 'normal', 'large'].includes(value)
  25. },
  26. lines: {
  27. type: Number,
  28. default: 4,
  29. validator: value => value >= 1 && value <= 6
  30. },
  31. skeletonColor: {
  32. type: String,
  33. default: '#f5f5f5'
  34. },
  35. skeletonHighlight: {
  36. type: String,
  37. default: '#e8e8e8'
  38. },
  39. animationDuration: {
  40. type: Number,
  41. default: 1.5
  42. },
  43. showButton: {
  44. type: Boolean,
  45. default: true
  46. },
  47. tipText: {
  48. type: String,
  49. default: '' // 默认不显示提示文字
  50. },
  51. tipColor: {
  52. type: String,
  53. default: '#666666' // 提示文字颜色
  54. },
  55. tipSize: {
  56. type: Number,
  57. default: 24 // 提示文字大小,单位rpx
  58. }
  59. },
  60. methods: {
  61. handleRefresh() {
  62. this.$emit('refresh');
  63. },
  64. onTouchStart() {
  65. this.$emit('touchstart');
  66. },
  67. onTouchEnd() {
  68. this.$emit('touchend');
  69. }
  70. }
  71. };
  72. </script>
  73. <template>
  74. <!-- 骨架屏容器 -->
  75. <view class="container">
  76. <!-- 骨架屏内容 -->
  77. <view class="skeleton" v-if="loading">
  78. <!-- 内容区域 -->
  79. <view class="skeleton-body">
  80. <view
  81. v-for="(item, index) in Array(lines).fill(0)"
  82. :key="index"
  83. class="skeleton-line"
  84. :class="{
  85. 'short': index === lines - 2,
  86. 'shorter': index === lines - 1
  87. }"
  88. :style="{
  89. background: `linear-gradient(90deg, ${skeletonColor} 25%, ${skeletonHighlight} 50%, ${skeletonColor} 75%)`,
  90. animationDuration: `${animationDuration}s`
  91. }">
  92. </view>
  93. </view>
  94. </view>
  95. <!-- 实际内容 -->
  96. <view v-else>
  97. <slot></slot>
  98. </view>
  99. <!-- 提示文字和按钮容器 -->
  100. <view v-if="showButton" class="button-container">
  101. <!-- 提示文字 -->
  102. <text
  103. v-if="tipText"
  104. class="tip-text"
  105. :style="{
  106. color: tipColor,
  107. fontSize: tipSize + 'rpx'
  108. }"
  109. >{{ tipText }}</text>
  110. <!-- 按钮 -->
  111. <button
  112. class="overlay-button"
  113. :class="[buttonSize, { 'button-hover': hover }]"
  114. hover-class="none"
  115. @touchstart="onTouchStart"
  116. @touchend="onTouchEnd"
  117. @click="handleRefresh"
  118. :style="{ backgroundColor: buttonColor }"
  119. >
  120. {{ buttonText }}
  121. </button>
  122. </view>
  123. </view>
  124. </template>
  125. <style scoped lang="scss">
  126. /* 容器样式 */
  127. .container {
  128. position: relative;
  129. width: 100%;
  130. min-height: 200rpx;
  131. background-color: #ffffff;
  132. border-radius: 16rpx;
  133. overflow: hidden;
  134. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
  135. transition: all 0.3s ease;
  136. &:hover {
  137. box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
  138. }
  139. }
  140. /* 骨架屏样式 */
  141. .skeleton {
  142. width: 100%;
  143. height: 100%;
  144. padding: 30rpx;
  145. &-body {
  146. margin: 20rpx 0;
  147. }
  148. &-line {
  149. height: 32rpx;
  150. background-size: 400% 100%;
  151. border-radius: 8rpx;
  152. margin-bottom: 20rpx;
  153. animation: skeleton-loading 1.5s ease infinite;
  154. &.short {
  155. width: 70%;
  156. }
  157. &.shorter {
  158. width: 50%;
  159. }
  160. }
  161. }
  162. /* 按钮容器样式 */
  163. .button-container {
  164. position: absolute;
  165. top: 50%;
  166. left: 50%;
  167. transform: translate(-50%, -50%);
  168. display: flex;
  169. flex-direction: column;
  170. align-items: center;
  171. z-index: 2;
  172. }
  173. /* 提示文字样式 */
  174. .tip-text {
  175. margin-bottom: 20rpx;
  176. text-align: center;
  177. line-height: 1.4;
  178. }
  179. /* 按钮样式 */
  180. .overlay-button {
  181. position: relative; // 改为相对定位
  182. transform: none; // 移除transform
  183. color: white;
  184. border-radius: 50rpx;
  185. padding: 0 40rpx;
  186. height: 80rpx;
  187. line-height: 80rpx;
  188. font-size: 28rpx;
  189. box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
  190. transition: all 0.3s ease;
  191. display: flex;
  192. align-items: center;
  193. justify-content: center;
  194. cursor: pointer;
  195. &:active {
  196. transform: scale(0.95); // 简化active状态的transform
  197. }
  198. &.small {
  199. height: 60rpx;
  200. line-height: 60rpx;
  201. font-size: 24rpx;
  202. padding: 0 30rpx;
  203. }
  204. &.large {
  205. height: 100rpx;
  206. line-height: 100rpx;
  207. font-size: 32rpx;
  208. padding: 0 50rpx;
  209. }
  210. }
  211. .button-hover {
  212. transform: translateY(-4rpx); // 简化hover状态的transform
  213. box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.15);
  214. }
  215. /* 骨架屏动画 */
  216. @keyframes skeleton-loading {
  217. 0% {
  218. background-position: 100% 50%;
  219. }
  220. 100% {
  221. background-position: 0 50%;
  222. }
  223. }
  224. </style>