vbea 1 rok pred
rodič
commit
50855946ea

+ 11 - 0
WebApp-Lite/uni_modules/x-skeleton/changelog.md

@@ -0,0 +1,11 @@
+## 1.0.4(2023-09-02)
+1、优化一些显示的细节问题
+## 1.0.3(2023-09-01)
+1、新增动画、时间、颜色等相关属性
+## 1.0.2(2023-08-31)
+1、回滚到v1.0.0版本,暂时取消动画时间的修改属性
+## 1.0.1(2023-08-31)
+1、新增动画持续时间属性
+2、新增淡出持续时间属性
+## 1.0.0(2023-08-31)
+第一个版本

+ 147 - 0
WebApp-Lite/uni_modules/x-skeleton/components/x-skeleton/x-skeleton-configs.js

@@ -0,0 +1,147 @@
+export default {
+	methods: {
+		bannerConfigs() {
+			return {
+				padding: '30rpx',
+				gridRows: 1,
+				gridColumns: 1,
+				gridRowsGap: '40rpx',
+				gridColumnsGap: '24rpx',
+				itemDirection: 'row',
+				itemGap: '30rpx',
+				itemAlign: 'center',
+				headShow: true,
+				headWidth: '100%',
+				headHeight: '300rpx',
+				headBorderRadius: '20rpx',
+				textShow: false,
+				textRows: 3,
+				textRowsGap: '20rpx',
+				textWidth: '100%',
+				textHeight: '30rpx',
+				textBorderRadius: '6rpx',
+				...this.configs
+			}
+		},
+		
+		infoConfigs() {
+			return {
+				padding: '30rpx',
+				gridRows: 1,
+				gridColumns: 1,
+				gridRowsGap: '50rpx',
+				gridColumnsGap: '24rpx',
+				itemDirection: 'row',
+				itemGap: '30rpx',
+				itemAlign: 'flex-start',
+				headShow: true,
+				headWidth: '100rpx',
+				headHeight: '100rpx',
+				headBorderRadius: '50%',
+				textShow: true,
+				textRows: 4,
+				textRowsGap: '32rpx',
+				textWidth: ['50%', '100%', '100%', '80%'],
+				textHeight: ['40rpx', '24rpx', '24rpx', '24rpx'],
+				textBorderRadius: '30rpx',
+				...this.configs
+			}
+		},
+		
+		textConfigs() {
+			return {
+				padding: '30rpx',
+				gridRows: 1,
+				gridColumns: 1,
+				gridRowsGap: '50rpx',
+				gridColumnsGap: '24rpx',
+				itemDirection: 'row',
+				itemGap: '30rpx',
+				itemAlign: 'flex-start',
+				headShow: false,
+				headWidth: '100rpx',
+				headHeight: '100rpx',
+				headBorderRadius: '50%',
+				textShow: true,
+				textRows: 4,
+				textRowsGap: '30rpx',
+				textWidth: ['50%', '100%', '100%', '80%'],
+				textHeight: '32rpx',
+				textBorderRadius: '32rpx',
+				...this.configs
+			}
+		},
+		
+		menuConfigs() {
+			return {
+				padding: '30rpx',
+				gridRows: 2,
+				gridColumns: 5,
+				gridRowsGap: '40rpx',
+				gridColumnsGap: '40rpx',
+				itemDirection: 'column',
+				itemGap: '16rpx',
+				itemAlign: 'center',
+				headShow: true,
+				headWidth: '100rpx',
+				headHeight: '100rpx',
+				headBorderRadius: '50%',
+				textShow: true,
+				textRows: 1,
+				textRowsGap: '0rpx',
+				textWidth: '100%',
+				textHeight: '24rpx',
+				textBorderRadius: '32rpx',
+				...this.configs
+			} 
+		},
+		
+		listConfigs() {
+			return {
+				padding: '30rpx',
+				gridRows: 2,
+				gridColumns: 1,
+				gridRowsGap: '50rpx',
+				gridColumnsGap: '24rpx',
+				itemDirection: 'row',
+				itemGap: '30rpx',
+				itemAlign: 'flex-start',
+				headShow: true,
+				headWidth: '200rpx',
+				headHeight: '200rpx',
+				headBorderRadius: '16rpx',
+				textShow: true,
+				textRows: 4,
+				textRowsGap: '32rpx',
+				textWidth: ['50%', '100%', '100%', '80%'],
+				textHeight: ['38rpx', '24rpx', '24rpx', '24rpx'],
+				textBorderRadius: '32rpx',
+				...this.configs
+			} 
+		},
+		
+		waterfallConfigs() {
+			return {
+				padding: '30rpx',
+				gridRows: 2,
+				gridColumns: 2,
+				gridRowsGap: '40rpx',
+				gridColumnsGap: '24rpx',
+				itemDirection: 'column',
+				itemGap: '16rpx',
+				itemAlign: 'center',
+				headShow: true,
+				headWidth: '100%',
+				headHeight: '400rpx',
+				headBorderRadius: '12rpx',
+				textShow: true,
+				textRows: 3,
+				textRowsGap: '12rpx',
+				textWidth: ['40%', '85%', '60%'],
+				textHeight: ['30rpx', '20rpx', '20rpx'],
+				textBorderRadius: '6rpx',
+				...this.configs
+			} 
+		},
+	}
+}

+ 319 - 0
WebApp-Lite/uni_modules/x-skeleton/components/x-skeleton/x-skeleton.vue

@@ -0,0 +1,319 @@
+<template>
+  <view class="x-skeleton" :style="variableStr">
+    <!-- 骨架屏 -->
+    <view v-if="skeletonLoading" class="x-skeleton__wrapper" :class="[ startFadeOut && 'fade-out' ]"
+      :style="{ padding: skeletonConfigs.padding, background: background }">
+      <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" :style="skeletonConfigs.textRowStyle">
+            <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, 
+                flex: skeletonConfigs.textItmesFlex,
+								borderRadius: skeletonConfigs.textBorderRadius,
+                marginLeft: textIndex > 0 ? skeletonConfigs.textItmesDivide : 0,
+								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.3
+      },
+      // 骨架的背景色
+      bgColor: {
+        type: String,
+        default: '#EAEDF5'
+      },
+      background: {
+        type: String,
+        default: '#FFFFFF'
+      },
+      // 骨架的动画高亮背景色
+      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 添加单位,如果有rpx,upx,%,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>

+ 84 - 0
WebApp-Lite/uni_modules/x-skeleton/package.json

@@ -0,0 +1,84 @@
+{
+  "id": "x-skeleton",
+  "displayName": "skeleton骨架屏(可任意配置-易用-灵活-动画)",
+  "version": "2.0.0",
+  "description": "x-skeleton骨架屏可随意配置内容,扩展性强,简单易用,内含常用的骨架类型",
+  "keywords": [
+    "skeleton",
+    "骨架屏",
+    "加载效果",
+    "vue",
+    "微信小程序"
+],
+  "repository": "",
+  "engines": {
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "插件不采集任何数据",
+      "permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "Vue": {
+          "vue2": "y",
+          "vue3": "y"
+        },
+        "App": {
+          "app-vue": "u",
+          "app-nvue": "u"
+        },
+        "H5-mobile": {
+          "Safari": "u",
+          "Android Browser": "u",
+          "微信浏览器(Android)": "u",
+          "QQ浏览器(Android)": "u"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "u"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "u",
+          "百度": "u",
+          "字节跳动": "u",
+          "QQ": "u",
+          "钉钉": "u",
+          "快手": "u",
+          "飞书": "u",
+          "京东": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        }
+      }
+    }
+  }
+}

+ 93 - 0
WebApp-Lite/uni_modules/x-skeleton/readme.md

@@ -0,0 +1,93 @@
+# x-skeleton
+
+# 功能介绍
+- 支持 H5、微信小程序,其他端未测试过
+- 使用简单、灵活,高度自定义
+- 加载时支持动画
+- 消失时加了动画,不再生硬切换页面
+- 支持绝大部分常用场景:
+1、轮播图 
+2、个人信息 
+3、段落 
+4、菜单 
+5、列表 
+6、瀑布流
+7、自定义...
+
+# 属性说明
+
+| 参数    | 说明                                                | 类型    | 默认值 | 可选值 |
+| ------- | --------------------------------------------------- | ------- | ------ | --- |
+| type | 骨架类型,为空时是完全自定义 | String | - |banner轮播图、info个人信息、text段落、menu菜单、list列表、waterfall瀑布流|
+| loading | 是否显示骨架占位图,设置为false将会展示子组件内容 | Boolean | true |true、false|
+| animate | 是否开启动画效果 | Boolean | true |true、false|
+| animateTime | 动画效果持续时间,单位秒 | Number \| String | 1.8 |-|
+| fadeOut | 是否开启淡出动画 | Boolean | true |true、false|
+| fadeOutTime | 淡出效果持续时间,单位秒 | Number \| String | 0.5 |-|
+| bgColor | 骨架的背景色 | String | #EAEDF5 |-|
+| highlightBgColor | 骨架的动画高亮背景色 | String | #F9FAFF |-|
+| configs | 自定义配置,具体看下方 | Object | {} |-|
+
+## configs参数说明
+
+| 参数    | 说明                                                | 类型  |
+| ------- | --------------------------------------------------- | ------- |
+| padding | 骨架内边距,同 css 的 padding | String |
+| gridRows | 行数 | Number |
+| gridColumns | 列数 | Number |
+| gridRowsGap | 行间隔 | String |
+| gridColumnsGap | 竖间距 | String |
+| itemDirection | head与text之间的排列方向(row、column) | String |
+| itemGap | head与text之间的间隔 | String |
+| itemAlign | head与text之间的纵轴对齐方式,同 flex 的align-items(center、flex-start、flex-end等) | String |
+| headShow | head是否展示 | Boolean |
+| headWidth | head宽度,支持百分比 | String |
+| headHeight | head高度 | String |
+| headBorderRadius | head圆角,支持百分比 | String |
+| textShow | text是否展示 | Boolean |
+| textRows | text的行数 | Number |
+| textRowsGap | text间距 | String |
+| textWidth | text的宽度,可以为百分比,数值,带单位字符串等,可通过数组传入指定每个段落行的宽度 | String \| Array \| Number |
+| textHeight | text的高度,可以为数值,带单位字符串等,可通过数组传入指定每个段落行的高度 | String \| Array \| Number |
+| textBorderRadius | text的圆角,支持百分比 | String |
+
+大部分情况下,直接指定相应的 type 已经够用了,如果大家想进行样式的微调、完全自定义可通过设置 configs 来实现。
+
+简单解释一下这些参数(右边有结构布局图示):
+
+布局总共分成 4 块,分别是 grid、item、head、text。
+
+1、grid:包含 item,指定每一行有多少个 item,每一列有多少个 item
+
+2、item:包含 head、text,可设置他们之间的排列方式、间距
+
+3、head:一个 item 只有一个 head,可设置宽高、圆角
+
+4、text:一个 item 可以有多行 text,可分别设置宽高、圆角、间距
+
+
+
+# 使用示例
+
+```html
+<x-skeleton type="banner" :loading="loading">
+	<view>我是轮播图</view>
+</x-skeleton>
+```
+
+```js
+export default {
+	data() {
+		return {
+			loading: true,
+		}
+	},
+	onLoad() {
+		setTimeout(() => {
+			this.loading = false;
+		}, 2000);
+	},
+}
+```
+
+更多用法请下载查看示例代码,有问题可以留言