裂变星小程序-25.03.04
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

246 lines
6.3 KiB

3 months ago
  1. <template>
  2. <view class="uv-skeleton">
  3. <view v-if="loading">
  4. <view v-for="(item, index) in elements" :key="index">
  5. <!-- 横向并列布局 -->
  6. <view class="uv-skeleton__group" :style="[style(item)]" v-if="item.type=='flex'">
  7. <view v-for="(item2,index2) in item.children" :class="[item2.clas]" :style="[style(item2)]" :key="index2">
  8. <view v-for="(item3,index3) in item2.elements" :key="index3">
  9. <!-- 垂直高度站位 -->
  10. <view :style="{height: $uv.addUnit(item3.height,'rpx')}" v-if="item3.type=='gap'"></view>
  11. <view :class="[item3.clas, roundClass, animateClass]" :style="[style(item3)]" v-else ref="skeleton"></view>
  12. </view>
  13. </view>
  14. </view>
  15. <!-- 自定义骨架屏内容 -->
  16. <!-- ps 自定义扩展说明 -->
  17. <!-- 如果你需要自定义模板可以参照这个custom的写法增加一个skeleton配置类型编写布局和样式就可以了 -->
  18. <!-- 注意事项为了保证骨架效果和动态效果的一致扩展时在你希望实际展示在页面中的元素上加上 :class="[style, animateClass]" :style="[style(item)]" ref="skeleton" -->
  19. <view :class="[item.clas, animateClass]" :style="[style(item)]" ref="skeleton" v-else-if="item.type == 'custom'"></view>
  20. <!-- 垂直高度站位 -->
  21. <view :style="{height: $uv.addUnit(item.height,'rpx')}" v-else-if="item.type=='gap'"></view>
  22. <!-- 其他基本单位 line avatar -->
  23. <view :class="[item.clas, roundClass, animateClass]" :style="[style(item)]" v-else ref="skeleton"></view>
  24. </view>
  25. </view>
  26. <view v-else>
  27. <slot />
  28. </view>
  29. </view>
  30. </template>
  31. <script>
  32. import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
  33. import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
  34. // #ifdef APP-NVUE
  35. const animation = weex.requireModule('animation');
  36. // #endif
  37. export default {
  38. name: 'uv-skeletons',
  39. mixins: [mpMixin, mixin],
  40. props: {
  41. // 是否显示骨架
  42. loading: {
  43. type: Boolean,
  44. default: true
  45. },
  46. // 骨架内容 具体说明参考文档:
  47. skeleton: {
  48. type: Array,
  49. default: () => []
  50. },
  51. // 是否开启动画效果
  52. animate: {
  53. type: Boolean,
  54. default: true
  55. },
  56. // 是否圆角骨架风格
  57. round: {
  58. type: Boolean,
  59. default: false
  60. },
  61. ...uni.$uv?.props?.skeleton
  62. },
  63. data() {
  64. return {
  65. elements: [],
  66. opacity: 0.5,
  67. duration: 600
  68. }
  69. },
  70. computed: {
  71. animateClass() {
  72. return this.animate ? 'uv-skeleton--animation' : 'uv-skeleton--static';
  73. },
  74. roundClass() {
  75. return this.round ? 'uv-skeleton--round' : 'uv-skeleton--radius';
  76. },
  77. style(item) {
  78. return item => {
  79. const style = {};
  80. return this.$uv.deepMerge(style, this.$uv.addStyle(item.style));
  81. }
  82. }
  83. },
  84. created() {
  85. this.init();
  86. // #ifdef APP-NVUE
  87. if (this.loading && this.animate) {
  88. this.$uv.sleep(50).then(res => {
  89. this.createAnimation(this.opacity);
  90. })
  91. }
  92. // #endif
  93. },
  94. methods: {
  95. /**
  96. * 初始化数据
  97. */
  98. init() {
  99. let elements = [];
  100. if (!this.$uv.test.array(this.skeleton)) return this.$uv.error('skeleton参数必须为数组形式,参考文档示例:');
  101. this.skeleton.forEach(el => {
  102. const elClass = this.getElCounts(el);
  103. elements = elements.concat(elClass);
  104. })
  105. this.elements = [...elements];
  106. },
  107. /**
  108. * 处理骨架屏参数内容
  109. * @param {Object} el 每项数据
  110. */
  111. getElCounts(el) {
  112. let elements = [];
  113. let children = [];
  114. if (this.$uv.test.number(el)) {
  115. elements.push({
  116. type: 'gap',
  117. height: el
  118. });
  119. return elements;
  120. } else {
  121. const type = el.type ? el.type : 'line';
  122. const num = el.num ? el.num : 1;
  123. const style = el.style ? el.style : {};
  124. const styleIsArray = this.$uv.test.array(style);
  125. const gap = el.gap ? el.gap : '20rpx';
  126. const child = el.children ? el.children : [];
  127. for (let i = 0; i < child.length; i++) {
  128. children[i] = {
  129. clas: child[i].type.indexOf('avatar') > -1 || child[i].type.indexOf('custom') > -1 ? '' : 'uv-skeleton__group__sub',
  130. elements: this.getElCounts(child[i])
  131. };
  132. }
  133. for (let i = 0; i < num; i++) {
  134. if (gap && i < num && i > 0) {
  135. elements.push({
  136. type: 'gap',
  137. height: gap
  138. });
  139. }
  140. elements.push({
  141. clas: `uv-skeleton__${type}`,
  142. type,
  143. style: styleIsArray ? style[i] : style,
  144. gap,
  145. children
  146. });
  147. }
  148. return elements;
  149. }
  150. },
  151. /**
  152. * 创建动画
  153. */
  154. createAnimation(opacity = 1) {
  155. // loading结束之后或未开启动画不执行
  156. if (!this.loading || !this.animate) return;
  157. let count = 0;
  158. const skeletons = this.$refs.skeleton;
  159. skeletons.forEach(item => {
  160. animation.transition(item, {
  161. styles: {
  162. opacity: opacity,
  163. },
  164. duration: this.duration
  165. }, () => {
  166. count++;
  167. if (count >= skeletons.length) {
  168. setTimeout(() => {
  169. this.createAnimation(opacity < 1 ? 1 : this.opacity);
  170. }, 200)
  171. }
  172. });
  173. })
  174. }
  175. }
  176. }
  177. </script>
  178. <style scoped lang="scss">
  179. @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
  180. @mixin background {
  181. /* #ifdef APP-NVUE */
  182. background-color: #e6e6e6;
  183. /* #endif */
  184. /* #ifndef APP-NVUE */
  185. background: linear-gradient(90deg, #F1F2F4 25%, #e6e6e6 37%, #F1F2F4 50%);
  186. background-size: 400% 100%;
  187. /* #endif */
  188. }
  189. .uv-skeleton__line {
  190. height: 32rpx;
  191. // margin-bottom: 30rpx;
  192. }
  193. .uv-skeleton__line--sm {
  194. height: 24rpx;
  195. margin-bottom: 30rpx;
  196. }
  197. .uv-skeleton__line--lg {
  198. height: 48rpx;
  199. margin-bottom: 30rpx;
  200. }
  201. .uv-skeleton--static {
  202. @include background;
  203. }
  204. .uv-skeleton--radius {
  205. border-radius: 8rpx;
  206. }
  207. .uv-skeleton--round {
  208. border-radius: 30rpx;
  209. }
  210. .uv-skeleton__avatar {
  211. width: 100rpx;
  212. height: 100rpx;
  213. border-radius: 50rpx;
  214. }
  215. .uv-skeleton__avatar--sm {
  216. width: 50rpx;
  217. height: 50rpx;
  218. border-radius: 25rpx;
  219. }
  220. .uv-skeleton__avatar--lg {
  221. width: 200rpx;
  222. height: 200rpx;
  223. border-radius: 100rpx;
  224. }
  225. .uv-skeleton__group {
  226. @include flex;
  227. &__sub {
  228. flex: 1;
  229. }
  230. }
  231. .uv-skeleton--animation {
  232. @include background;
  233. /* #ifndef APP-NVUE */
  234. animation: skeleton 1.8s ease infinite
  235. /* #endif */
  236. }
  237. /* #ifndef APP-NVUE */
  238. @keyframes skeleton {
  239. 0% {
  240. background-position: 100% 50%
  241. }
  242. 100% {
  243. background-position: 0 50%
  244. }
  245. }
  246. /* #endif */
  247. </style>