OfficeSystem/node_modules/@climblee/uv-ui/components/uv-skeletons/uv-skeletons.vue
WindowBird 7ab9e16c35 init
2025-10-30 16:42:12 +08:00

247 lines
6.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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 '../../libs/mixin/mpMixin.js'
import mixin from '../../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 '../../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>