Files
lucky_shop/uni_modules/x-skeleton/components/x-skeleton/x-skeleton.vue
2025-10-27 15:55:29 +08:00

324 lines
8.8 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="x-skeleton" :style="variableStr">
<!-- 骨架屏 -->
<view
v-if="skeletonLoading"
class="x-skeleton__wrapper"
:class="[ startFadeOut && 'fade-out' ]"
:style="{ padding: skeletonConfigs.padding }"
>
<view
v-for="(row, rowIndex) in gridRowsArr" :key="rowIndex"
class="x-skeleton__wrapper__rows"
:style="{ marginBottom: rowIndex < gridRowsArr.length - 1 ? skeletonConfigs.gridRowsGap : 0 }"
>
<view
v-for="(column, columnIndex) in gridColumnsArr" :key="columnIndex"
class="x-skeleton__wrapper__columns"
:style="{
flexDirection: skeletonConfigs.itemDirection,
alignItems: skeletonConfigs.itemAlign,
marginRight: columnIndex < gridColumnsArr.length - 1 ? skeletonConfigs.gridColumnsGap : 0,
}"
>
<view
v-if="skeletonConfigs.headShow"
class="x-skeleton__wrapper__head"
:class="[ animate && 'animate' ]"
:style="{
width: skeletonConfigs.headWidth,
height: skeletonConfigs.headHeight,
borderRadius: skeletonConfigs.headBorderRadius,
marginRight: (skeletonConfigs.itemDirection == 'row' && skeletonConfigs.textShow) ? skeletonConfigs.itemGap : 0,
marginBottom: (skeletonConfigs.itemDirection == 'column' && skeletonConfigs.textShow) ? skeletonConfigs.itemGap : 0
}"
></view>
<view v-if="skeletonConfigs.textShow" class="x-skeleton__wrapper__text">
<view
v-for="(text, textIndex) in textRowsArr" :key="textIndex"
class="x-skeleton__wrapper__text__row"
:class="[animate && 'animate']"
:style="{
width: text.width,
height: text.height,
borderRadius: skeletonConfigs.textBorderRadius,
marginBottom: textIndex < textRowsArr.length - 1 ? skeletonConfigs.textRowsGap : 0
}"
></view>
</view>
</view>
</view>
</view>
<!-- 插槽 -->
<view v-else>
<slot></slot>
</view>
</view>
</template>
<script>
import XSkeletonConfigs from './x-skeleton-configs.js'
export default {
name: "x-skeleton",
mixins: [ XSkeletonConfigs ],
props: {
// 骨架屏类型
type: {
type: String,
default: '' //banner轮播图、info个人信息、text段落、menu菜单、list列表、waterfall瀑布流
},
// 是否展示骨架组件
loading: {
type: Boolean,
default: true
},
// 是否开启动画效果
animate: {
type: Boolean,
default: true
},
// 动画效果持续时间,单位秒
animateTime: {
type: Number | String,
default: 1.8
},
// 是否开启淡出动画
fadeOut: {
type: Boolean,
default: true
},
// 淡出效果持续时间,单位秒
fadeOutTime: {
type: Number | String,
default: 0.5
},
// 骨架的背景色
bgColor: {
type: String,
default: '#EAEDF5'
},
// 骨架的动画高亮背景色
highlightBgColor: {
type: String,
default: '#F9FAFF'
},
// 自定义配置
configs: {
type: Object,
default: () => {
return {
// padding: '30rpx', //内边距
// gridRows: 3, //行数
// gridColumns: 2, //列数
// gridRowsGap: '40rpx', //行间隔
// gridColumnsGap: '24rpx', //竖间距
// itemDirection: 'column', //head与text之间的排列方向row、column
// itemGap: '16rpx', //head与text之间的间隔
// itemAlign: 'center', //head与text之间的纵轴对齐方式center、flex-start、flex-end、baseline
// headShow: true, //head是否展示
// headWidth: '100%', //head宽度支持百分比
// headHeight: '400rpx', //head高度
// headBorderRadius: '12rpx', //head圆角支持百分比
// textShow: true, //文本是否展示
// textRows: 3, //文本的行数
// textRowsGap: '12rpx', //文本间距
// textWidth: ['40%', '85%', '60%'], //文本的宽度,可以为百分比,数值,带单位字符串等,可通过数组传入指定每个段落行的宽度
// textHeight: ['30rpx', '20rpx', '20rpx'], //文本的高度,可以为数值,带单位字符串等,可通过数组传入指定每个段落行的高度
// textBorderRadius: '6rpx', //文本的圆角,支持百分比
}
}
}
},
computed: {
gridRowsArr() {
return new Array(Number(this.skeletonConfigs?.gridRows || []));
},
gridColumnsArr() {
return new Array(Number(this.skeletonConfigs?.gridColumns || []));
},
textRowsArr() {
if (!this.skeletonConfigs?.textShow) return [];
if (/%$/.test(this.skeletonConfigs.textHeight)) {
console.error('x-skeleton: textHeight参数不支持百分比单位');
}
const rows = []
for (let i = 0; i < this.skeletonConfigs.textRows; i++) {
const { gridRows, textWidth, textHeight } = this.skeletonConfigs;
let item = {},
// 需要预防超出数组边界的情况
rowWidth = this.isArray(textWidth) ? (textWidth[i] || (i === gridRows - 1 ? '70%' : '100%')) : i === gridRows - 1 ? '70%' : textWidth,
rowHeight = this.isArray(textHeight) ? (textHeight[i] || '30rpx') : textHeight
// 非百分比的宽度时,调整像素单位
if (/%$/.test(rowWidth)) {
item.width = rowWidth;
} else {
item.width = this.addUnit(rowWidth)
}
item.height = this.addUnit(rowHeight)
rows.push(item)
}
return rows
},
variableStr() {
let keys = ['animateTime', 'fadeOutTime', 'bgColor', 'highlightBgColor'];
let str = keys.map(item => {
if (item.indexOf('Time') > -1) {
return `--${item}:${this[item]}s`
} else {
return `--${item}:${this[item]}`
}
}).join(";");
return str;
}
},
watch: {
loading: {
immediate: true,
handler(value) {
if (value) {
this.skeletonLoading = true;
} else {
if (this.fadeOut) {
this.startFadeOut = true;
setTimeout(() => {
this.skeletonLoading = false;
this.startFadeOut = false;
}, this.fadeOutTime * 1000);
} else {
this.skeletonLoading = false;
}
}
}
},
type: {
immediate: true,
handler(value) {
if (value === 'banner') {
this.skeletonConfigs = this.bannerConfigs();
} else if (value === 'info') {
this.skeletonConfigs = this.infoConfigs();
} else if (value === 'text') {
this.skeletonConfigs = this.textConfigs();
} else if (value === 'menu') {
this.skeletonConfigs = this.menuConfigs();
} else if (value === 'list') {
this.skeletonConfigs = this.listConfigs();
} else if (value === 'waterfall') {
this.skeletonConfigs = this.waterfallConfigs();
} else {
this.skeletonConfigs = this.configs || {};
}
}
}
},
data() {
return {
skeletonConfigs: this.configs || {},
skeletonLoading: this.loading,
startFadeOut: false,
width: 0
};
},
mounted() {
},
methods: {
/**
* @description 是否为数组
* @param {object} value 需要判断的对象
*/
isArray(value) {
if (typeof Array.isArray === 'function') {
return Array.isArray(value)
}
return Object.prototype.toString.call(value) === '[object Array]'
},
/**
* @description 添加单位如果有rpxupx%px等单位结尾或者值为auto直接返回否则加上px单位结尾
* @param {string|number} value 需要添加单位的值
* @param {string} unit 添加的单位名 比如px
*/
addUnit(value = 'auto', unit = 'px') {
value = String(value);
// 用uView内置验证规则中的number判断是否为数值
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value) ? `${value}${unit}` : value;
}
}
}
</script>
<style lang="scss" scoped>
@mixin background {
background: linear-gradient(90deg, var(--bgColor) 25%, var(--highlightBgColor) 37%, var(--bgColor) 50%);
background-size: 400% 100%;
}
.x-skeleton {
width: 100%;
box-sizing: border-box;
.x-skeleton__wrapper {
display: flex;
flex-direction: column;
&__rows {
display: flex;
align-items: center;
justify-content: space-between;
}
&__columns {
display: flex;
align-items: center;
flex: 1;
}
&__head {
width: 100%;
@include background;
}
&__text {
flex: 1;
width: 100%;
&__row {
@include background;
}
}
}
.fade-out {
opacity: 0;
animation: fadeOutAnim var(--fadeOutTime);
}
@keyframes fadeOutAnim {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.animate {
animation: skeletonAnim var(--animateTime) ease infinite;
}
@keyframes skeletonAnim {
0% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}
}
</style>