|
|
- <template>
- <view class="uv-skeleton">
- <view v-if="loading">
- <view v-for="(item, index) in elements" :key="index">
- <!-- 横向并列布局 -->
- <view class="uv-skeleton__group" :style="[style(item)]" v-if="item.type=='flex'">
- <view v-for="(item2,index2) in item.children" :class="[item2.clas]" :style="[style(item2)]" :key="index2">
- <view v-for="(item3,index3) in item2.elements" :key="index3">
- <!-- 垂直高度站位 -->
- <view :style="{height: $uv.addUnit(item3.height,'rpx')}" v-if="item3.type=='gap'"></view>
- <view :class="[item3.clas, roundClass, animateClass]" :style="[style(item3)]" v-else ref="skeleton"></view>
- </view>
- </view>
- </view>
- <!-- 自定义骨架屏内容 -->
- <!-- ps 自定义扩展说明: -->
- <!-- 如果你需要自定义模板,可以参照这个custom的写法,增加一个skeleton配置类型,编写布局和样式就可以了 -->
- <!-- 注意事项:为了保证骨架效果和动态效果的一致,扩展时,在你希望实际展示在页面中的元素上加上 :class="[style, animateClass]" 和 :style="[style(item)]" 和 ref="skeleton" -->
- <view :class="[item.clas, animateClass]" :style="[style(item)]" ref="skeleton" v-else-if="item.type == 'custom'"></view>
- <!-- 垂直高度站位 -->
- <view :style="{height: $uv.addUnit(item.height,'rpx')}" v-else-if="item.type=='gap'"></view>
- <!-- 其他基本单位 line avatar 等 -->
- <view :class="[item.clas, roundClass, animateClass]" :style="[style(item)]" v-else ref="skeleton"></view>
- </view>
- </view>
- <view v-else>
- <slot />
- </view>
- </view>
- </template>
- <script>
- import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
- import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
- // #ifdef APP-NVUE
- const animation = weex.requireModule('animation');
- // #endif
- export default {
- name: 'uv-skeletons',
- mixins: [mpMixin, mixin],
- props: {
- // 是否显示骨架
- loading: {
- type: Boolean,
- default: true
- },
- // 骨架内容 具体说明参考文档:
- skeleton: {
- type: Array,
- default: () => []
- },
- // 是否开启动画效果
- animate: {
- type: Boolean,
- default: true
- },
- // 是否圆角骨架风格
- round: {
- type: Boolean,
- default: false
- },
- ...uni.$uv?.props?.skeleton
- },
- data() {
- return {
- elements: [],
- opacity: 0.5,
- duration: 600
- }
- },
- computed: {
- animateClass() {
- return this.animate ? 'uv-skeleton--animation' : 'uv-skeleton--static';
- },
- roundClass() {
- return this.round ? 'uv-skeleton--round' : 'uv-skeleton--radius';
- },
- style(item) {
- return item => {
- const style = {};
- return this.$uv.deepMerge(style, this.$uv.addStyle(item.style));
- }
- }
- },
- created() {
- this.init();
- // #ifdef APP-NVUE
- if (this.loading && this.animate) {
- this.$uv.sleep(50).then(res => {
- this.createAnimation(this.opacity);
- })
- }
- // #endif
- },
- methods: {
- /**
- * 初始化数据
- */
- init() {
- let elements = [];
- if (!this.$uv.test.array(this.skeleton)) return this.$uv.error('skeleton参数必须为数组形式,参考文档示例:');
- this.skeleton.forEach(el => {
- const elClass = this.getElCounts(el);
- elements = elements.concat(elClass);
- })
- this.elements = [...elements];
- },
- /**
- * 处理骨架屏参数内容
- * @param {Object} el 每项数据
- */
- getElCounts(el) {
- let elements = [];
- let children = [];
- if (this.$uv.test.number(el)) {
- elements.push({
- type: 'gap',
- height: el
- });
- return elements;
- } else {
- const type = el.type ? el.type : 'line';
- const num = el.num ? el.num : 1;
- const style = el.style ? el.style : {};
- const styleIsArray = this.$uv.test.array(style);
- const gap = el.gap ? el.gap : '20rpx';
- const child = el.children ? el.children : [];
- for (let i = 0; i < child.length; i++) {
- children[i] = {
- clas: child[i].type.indexOf('avatar') > -1 || child[i].type.indexOf('custom') > -1 ? '' : 'uv-skeleton__group__sub',
- elements: this.getElCounts(child[i])
- };
- }
- for (let i = 0; i < num; i++) {
- if (gap && i < num && i > 0) {
- elements.push({
- type: 'gap',
- height: gap
- });
- }
- elements.push({
- clas: `uv-skeleton__${type}`,
- type,
- style: styleIsArray ? style[i] : style,
- gap,
- children
- });
- }
- return elements;
- }
- },
- /**
- * 创建动画
- */
- createAnimation(opacity = 1) {
- // loading结束之后或未开启动画不执行
- if (!this.loading || !this.animate) return;
- let count = 0;
- const skeletons = this.$refs.skeleton;
- skeletons.forEach(item => {
- animation.transition(item, {
- styles: {
- opacity: opacity,
- },
- duration: this.duration
- }, () => {
- count++;
- if (count >= skeletons.length) {
- setTimeout(() => {
- this.createAnimation(opacity < 1 ? 1 : this.opacity);
- }, 200)
- }
- });
- })
- }
- }
- }
- </script>
- <style scoped lang="scss">
- @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
- @mixin background {
- /* #ifdef APP-NVUE */
- background-color: #e6e6e6;
- /* #endif */
- /* #ifndef APP-NVUE */
- background: linear-gradient(90deg, #F1F2F4 25%, #e6e6e6 37%, #F1F2F4 50%);
- background-size: 400% 100%;
- /* #endif */
- }
- .uv-skeleton__line {
- height: 32rpx;
- // margin-bottom: 30rpx;
- }
- .uv-skeleton__line--sm {
- height: 24rpx;
- margin-bottom: 30rpx;
- }
- .uv-skeleton__line--lg {
- height: 48rpx;
- margin-bottom: 30rpx;
- }
- .uv-skeleton--static {
- @include background;
- }
- .uv-skeleton--radius {
- border-radius: 8rpx;
- }
- .uv-skeleton--round {
- border-radius: 30rpx;
- }
- .uv-skeleton__avatar {
- width: 100rpx;
- height: 100rpx;
- border-radius: 50rpx;
- }
- .uv-skeleton__avatar--sm {
- width: 50rpx;
- height: 50rpx;
- border-radius: 25rpx;
- }
- .uv-skeleton__avatar--lg {
- width: 200rpx;
- height: 200rpx;
- border-radius: 100rpx;
- }
- .uv-skeleton__group {
- @include flex;
- &__sub {
- flex: 1;
- }
- }
- .uv-skeleton--animation {
- @include background;
- /* #ifndef APP-NVUE */
- animation: skeleton 1.8s ease infinite
- /* #endif */
- }
- /* #ifndef APP-NVUE */
- @keyframes skeleton {
- 0% {
- background-position: 100% 50%
- }
- 100% {
- background-position: 0 50%
- }
- }
- /* #endif */
- </style>
|