From 1f52b95d278ffb24487ec201ed81044a06689261 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?LAPTOP-D7TKRI82=5C=E9=82=93?= <52643018@qq.com>
Date: Mon, 21 Jun 2021 16:16:08 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BB=84=E4=BB=B6=EF=BC=8C?=
=?UTF-8?q?=E6=96=B0=E5=BB=BA=E7=9B=AE=E5=BD=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
components/me-tabs/me-tabs.vue | 198 +++++
.../mescroll-uni/components/mescroll-down.css | 55 ++
.../mescroll-uni/components/mescroll-down.vue | 47 ++
.../components/mescroll-empty.vue | 90 ++
.../mescroll-uni/components/mescroll-top.vue | 83 ++
.../mescroll-uni/components/mescroll-up.css | 47 ++
.../mescroll-uni/components/mescroll-up.vue | 39 +
components/mescroll-uni/mescroll-body.css | 14 +
components/mescroll-uni/mescroll-body.vue | 339 ++++++++
components/mescroll-uni/mescroll-empty.png | Bin 0 -> 30433 bytes
components/mescroll-uni/mescroll-mixins.js | 74 ++
.../mescroll-uni/mescroll-uni-option.js | 33 +
components/mescroll-uni/mescroll-uni.css | 36 +
components/mescroll-uni/mescroll-uni.js | 788 ++++++++++++++++++
components/mescroll-uni/mescroll-uni.vue | 424 ++++++++++
.../mescroll-uni/mixins/mescroll-comp.js | 50 ++
.../mescroll-uni/mixins/mescroll-more-item.js | 51 ++
.../mescroll-uni/mixins/mescroll-more.js | 56 ++
components/mescroll-uni/wxs/mixins.js | 102 +++
components/mescroll-uni/wxs/renderjs.js | 92 ++
components/mescroll-uni/wxs/wxs.wxs | 268 ++++++
components/my-nocontent/my-nocontent.vue | 27 +
components/my-uni-skeleton/README.md | 77 ++
components/my-uni-skeleton/index.vue | 166 ++++
pages.json | 12 +-
static/{ => tabbar}/center-selected.png | Bin
static/{ => tabbar}/center.png | Bin
static/{ => tabbar}/index-selected.png | Bin
static/{ => tabbar}/index.png | Bin
29 files changed, 3162 insertions(+), 6 deletions(-)
create mode 100644 components/me-tabs/me-tabs.vue
create mode 100644 components/mescroll-uni/components/mescroll-down.css
create mode 100644 components/mescroll-uni/components/mescroll-down.vue
create mode 100644 components/mescroll-uni/components/mescroll-empty.vue
create mode 100644 components/mescroll-uni/components/mescroll-top.vue
create mode 100644 components/mescroll-uni/components/mescroll-up.css
create mode 100644 components/mescroll-uni/components/mescroll-up.vue
create mode 100644 components/mescroll-uni/mescroll-body.css
create mode 100644 components/mescroll-uni/mescroll-body.vue
create mode 100644 components/mescroll-uni/mescroll-empty.png
create mode 100644 components/mescroll-uni/mescroll-mixins.js
create mode 100644 components/mescroll-uni/mescroll-uni-option.js
create mode 100644 components/mescroll-uni/mescroll-uni.css
create mode 100644 components/mescroll-uni/mescroll-uni.js
create mode 100644 components/mescroll-uni/mescroll-uni.vue
create mode 100644 components/mescroll-uni/mixins/mescroll-comp.js
create mode 100644 components/mescroll-uni/mixins/mescroll-more-item.js
create mode 100644 components/mescroll-uni/mixins/mescroll-more.js
create mode 100644 components/mescroll-uni/wxs/mixins.js
create mode 100644 components/mescroll-uni/wxs/renderjs.js
create mode 100644 components/mescroll-uni/wxs/wxs.wxs
create mode 100644 components/my-nocontent/my-nocontent.vue
create mode 100644 components/my-uni-skeleton/README.md
create mode 100644 components/my-uni-skeleton/index.vue
rename static/{ => tabbar}/center-selected.png (100%)
rename static/{ => tabbar}/center.png (100%)
rename static/{ => tabbar}/index-selected.png (100%)
rename static/{ => tabbar}/index.png (100%)
diff --git a/components/me-tabs/me-tabs.vue b/components/me-tabs/me-tabs.vue
new file mode 100644
index 0000000..fbe228d
--- /dev/null
+++ b/components/me-tabs/me-tabs.vue
@@ -0,0 +1,198 @@
+
+
+
+
+
+
+
+ {{getTabName(tab)}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/mescroll-uni/components/mescroll-down.css b/components/mescroll-uni/components/mescroll-down.css
new file mode 100644
index 0000000..72bf106
--- /dev/null
+++ b/components/mescroll-uni/components/mescroll-down.css
@@ -0,0 +1,55 @@
+/* 下拉刷新区域 */
+.mescroll-downwarp {
+ position: absolute;
+ top: -100%;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ text-align: center;
+}
+
+/* 下拉刷新--内容区,定位于区域底部 */
+.mescroll-downwarp .downwarp-content {
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ min-height: 60rpx;
+ padding: 20rpx 0;
+ text-align: center;
+}
+
+/* 下拉刷新--提示文本 */
+.mescroll-downwarp .downwarp-tip {
+ display: inline-block;
+ font-size: 28rpx;
+ vertical-align: middle;
+ margin-left: 16rpx;
+ /* color: gray; 已在style设置color,此处删去*/
+}
+
+/* 下拉刷新--旋转进度条 */
+.mescroll-downwarp .downwarp-progress {
+ display: inline-block;
+ width: 32rpx;
+ height: 32rpx;
+ border-radius: 50%;
+ border: 2rpx solid gray;
+ border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
+ vertical-align: middle;
+}
+
+/* 旋转动画 */
+.mescroll-downwarp .mescroll-rotate {
+ animation: mescrollDownRotate 0.6s linear infinite;
+}
+
+@keyframes mescrollDownRotate {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
\ No newline at end of file
diff --git a/components/mescroll-uni/components/mescroll-down.vue b/components/mescroll-uni/components/mescroll-down.vue
new file mode 100644
index 0000000..9fd1567
--- /dev/null
+++ b/components/mescroll-uni/components/mescroll-down.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
+ {{downText}}
+
+
+
+
+
+
+
diff --git a/components/mescroll-uni/components/mescroll-empty.vue b/components/mescroll-uni/components/mescroll-empty.vue
new file mode 100644
index 0000000..e51f824
--- /dev/null
+++ b/components/mescroll-uni/components/mescroll-empty.vue
@@ -0,0 +1,90 @@
+
+
+
+
+ {{ tip }}
+ {{ option.btnText }}
+
+
+
+
+
+
diff --git a/components/mescroll-uni/components/mescroll-top.vue b/components/mescroll-uni/components/mescroll-top.vue
new file mode 100644
index 0000000..5115fd8
--- /dev/null
+++ b/components/mescroll-uni/components/mescroll-top.vue
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
diff --git a/components/mescroll-uni/components/mescroll-up.css b/components/mescroll-uni/components/mescroll-up.css
new file mode 100644
index 0000000..cbf48cd
--- /dev/null
+++ b/components/mescroll-uni/components/mescroll-up.css
@@ -0,0 +1,47 @@
+/* 上拉加载区域 */
+.mescroll-upwarp {
+ box-sizing: border-box;
+ min-height: 110rpx;
+ padding: 30rpx 0;
+ text-align: center;
+ clear: both;
+}
+
+/*提示文本 */
+.mescroll-upwarp .upwarp-tip,
+.mescroll-upwarp .upwarp-nodata {
+ display: inline-block;
+ font-size: 28rpx;
+ vertical-align: middle;
+ /* color: gray; 已在style设置color,此处删去*/
+}
+
+.mescroll-upwarp .upwarp-tip {
+ margin-left: 16rpx;
+}
+
+/*旋转进度条 */
+.mescroll-upwarp .upwarp-progress {
+ display: inline-block;
+ width: 32rpx;
+ height: 32rpx;
+ border-radius: 50%;
+ border: 2rpx solid gray;
+ border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
+ vertical-align: middle;
+}
+
+/* 旋转动画 */
+.mescroll-upwarp .mescroll-rotate {
+ animation: mescrollUpRotate 0.6s linear infinite;
+}
+
+@keyframes mescrollUpRotate {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
\ No newline at end of file
diff --git a/components/mescroll-uni/components/mescroll-up.vue b/components/mescroll-uni/components/mescroll-up.vue
new file mode 100644
index 0000000..11c2e1f
--- /dev/null
+++ b/components/mescroll-uni/components/mescroll-up.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ {{ mOption.textLoading }}
+
+
+ {{ mOption.textNoMore }}
+
+
+
+
+
+
diff --git a/components/mescroll-uni/mescroll-body.css b/components/mescroll-uni/mescroll-body.css
new file mode 100644
index 0000000..9cfa255
--- /dev/null
+++ b/components/mescroll-uni/mescroll-body.css
@@ -0,0 +1,14 @@
+.mescroll-body {
+ position: relative; /* 下拉刷新区域相对自身定位 */
+ height: auto; /* 不可固定高度,否则overflow:hidden导致无法滑动; 同时使设置的最小高生效,实现列表不满屏仍可下拉*/
+ overflow: hidden; /* 当有元素写在mescroll-body标签前面时,可遮住下拉刷新区域 */
+ box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
+}
+
+/* 适配 iPhoneX */
+@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
+ .mescroll-safearea {
+ padding-bottom: constant(safe-area-inset-bottom);
+ padding-bottom: env(safe-area-inset-bottom);
+ }
+}
\ No newline at end of file
diff --git a/components/mescroll-uni/mescroll-body.vue b/components/mescroll-uni/mescroll-body.vue
new file mode 100644
index 0000000..0068248
--- /dev/null
+++ b/components/mescroll-uni/mescroll-body.vue
@@ -0,0 +1,339 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{downText}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{mescroll.optUp.textLoading}}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/mescroll-uni/mescroll-empty.png b/components/mescroll-uni/mescroll-empty.png
new file mode 100644
index 0000000000000000000000000000000000000000..5e08600a6ad49a783bd525a7965051f9a765bdc1
GIT binary patch
literal 30433
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
+ this.mescroll.endSuccess();
+ }, 500)
+ }
+ },
+ // 上拉加载的回调
+ upCallback() {
+ // mixin默认延时500自动结束加载
+ setTimeout(()=>{
+ this.mescroll.endErr();
+ }, 500)
+ }
+ },
+ mounted() {
+ this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况
+ }
+
+}
+
+export default MescrollMixin;
diff --git a/components/mescroll-uni/mescroll-uni-option.js b/components/mescroll-uni/mescroll-uni-option.js
new file mode 100644
index 0000000..2a6e36d
--- /dev/null
+++ b/components/mescroll-uni/mescroll-uni-option.js
@@ -0,0 +1,33 @@
+// 全局配置
+// mescroll-body 和 mescroll-uni 通用
+const GlobalOption = {
+ down: {
+ // 其他down的配置参数也可以写,这里只展示了常用的配置:
+ textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
+ textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
+ textLoading: '加载中 ...', // 加载中的提示文本
+ offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
+ native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+ },
+ up: {
+ // 其他up的配置参数也可以写,这里只展示了常用的配置:
+ textLoading: '加载中 ...', // 加载中的提示文本
+ textNoMore: '-- END --', // 没有更多数据的提示文本
+ offset: 80, // 距底部多远时,触发upCallback
+ toTop: {
+ // 回到顶部按钮,需配置src才显示
+ src: "http://www.mescroll.com/img/mescroll-totop.png?v=1", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
+ offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
+ right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+ },
+ empty: {
+ use: true, // 是否显示空布局
+ icon: require('./mescroll-empty.png'), // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
+ tip: '暂无相关数据' // 提示
+ }
+ }
+}
+
+export default GlobalOption
diff --git a/components/mescroll-uni/mescroll-uni.css b/components/mescroll-uni/mescroll-uni.css
new file mode 100644
index 0000000..39438cd
--- /dev/null
+++ b/components/mescroll-uni/mescroll-uni.css
@@ -0,0 +1,36 @@
+.mescroll-uni-warp{
+ height: 100%;
+}
+
+.mescroll-uni-content{
+ height: 100%;
+}
+
+.mescroll-uni {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ min-height: 200rpx;
+ overflow-y: auto;
+ box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
+}
+
+/* 定位的方式固定高度 */
+.mescroll-uni-fixed{
+ z-index: 1;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ width: auto; /* 使right生效 */
+ height: auto; /* 使bottom生效 */
+}
+
+/* 适配 iPhoneX */
+@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
+ .mescroll-safearea {
+ padding-bottom: constant(safe-area-inset-bottom);
+ padding-bottom: env(safe-area-inset-bottom);
+ }
+}
diff --git a/components/mescroll-uni/mescroll-uni.js b/components/mescroll-uni/mescroll-uni.js
new file mode 100644
index 0000000..8c7a391
--- /dev/null
+++ b/components/mescroll-uni/mescroll-uni.js
@@ -0,0 +1,788 @@
+/* mescroll
+ * version 1.3.1
+ * 2020-07-27 wenju
+ * http://www.mescroll.com
+ */
+
+export default function MeScroll(options, isScrollBody) {
+ let me = this;
+ me.version = '1.3.1'; // mescroll版本号
+ me.options = options || {}; // 配置
+ me.isScrollBody = isScrollBody || false; // 滚动区域是否为原生页面滚动; 默认为scroll-view
+
+ me.isDownScrolling = false; // 是否在执行下拉刷新的回调
+ me.isUpScrolling = false; // 是否在执行上拉加载的回调
+ let hasDownCallback = me.options.down && me.options.down.callback; // 是否配置了down的callback
+
+ // 初始化下拉刷新
+ me.initDownScroll();
+ // 初始化上拉加载,则初始化
+ me.initUpScroll();
+
+ // 自动加载
+ setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
+ // 自动触发下拉刷新 (只有配置了down的callback才自动触发下拉刷新)
+ if ((me.optDown.use || me.optDown.native) && me.optDown.auto && hasDownCallback) {
+ if (me.optDown.autoShowLoading) {
+ me.triggerDownScroll(); // 显示下拉进度,执行下拉回调
+ } else {
+ me.optDown.callback && me.optDown.callback(me); // 不显示下拉进度,直接执行下拉回调
+ }
+ }
+ // 自动触发上拉加载
+ if(!me.isUpAutoLoad){ // 部分小程序(头条小程序)emit是异步, 会导致isUpAutoLoad判断有误, 先延时确保先执行down的callback,再执行up的callback
+ setTimeout(function(){
+ me.optUp.use && me.optUp.auto && !me.isUpAutoLoad && me.triggerUpScroll();
+ },100)
+ }
+ }, 30); // 需让me.optDown.inited和me.optUp.inited先执行
+}
+
+/* 配置参数:下拉刷新 */
+MeScroll.prototype.extendDownScroll = function(optDown) {
+ // 下拉刷新的配置
+ MeScroll.extend(optDown, {
+ use: true, // 是否启用下拉刷新; 默认true
+ auto: true, // 是否在初始化完毕之后自动执行下拉刷新的回调; 默认true
+ native: false, // 是否使用系统自带的下拉刷新; 默认false; 仅mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+ autoShowLoading: false, // 如果设置auto=true(在初始化完毕之后自动执行下拉刷新的回调),那么是否显示下拉刷新的进度; 默认false
+ isLock: false, // 是否锁定下拉刷新,默认false;
+ offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
+ startTop: 100, // scroll-view快速滚动到顶部时,此时的scroll-top可能大于0, 此值用于控制最大的误差
+ inOffsetRate: 1, // 在列表顶部,下拉的距离小于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
+ outOffsetRate: 0.2, // 在列表顶部,下拉的距离大于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
+ bottomOffset: 20, // 当手指touchmove位置在距离body底部20px范围内的时候结束上拉刷新,避免Webview嵌套导致touchend事件不执行
+ minAngle: 45, // 向下滑动最少偏移的角度,取值区间 [0,90];默认45度,即向下滑动的角度大于45度则触发下拉;而小于45度,将不触发下拉,避免与左右滑动的轮播等组件冲突;
+ textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
+ textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
+ textLoading: '加载中 ...', // 加载中的提示文本
+ bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorTop)
+ textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
+ inited: null, // 下拉刷新初始化完毕的回调
+ inOffset: null, // 下拉的距离进入offset范围内那一刻的回调
+ outOffset: null, // 下拉的距离大于offset那一刻的回调
+ onMoving: null, // 下拉过程中的回调,滑动过程一直在执行; rate下拉区域当前高度与指定距离的比值(inOffset: rate<1; outOffset: rate>=1); downHight当前下拉区域的高度
+ beforeLoading: null, // 准备触发下拉刷新的回调: 如果return true,将不触发showLoading和callback回调; 常用来完全自定义下拉刷新, 参考案例【淘宝 v6.8.0】
+ showLoading: null, // 显示下拉刷新进度的回调
+ afterLoading: null, // 显示下拉刷新进度的回调之后,马上要执行的代码 (如: 在wxs中使用)
+ beforeEndDownScroll: null, // 准备结束下拉的回调. 返回结束下拉的延时执行时间,默认0ms; 常用于结束下拉之前再显示另外一小段动画,才去隐藏下拉刷新的场景, 参考案例【dotJump】
+ endDownScroll: null, // 结束下拉刷新的回调
+ afterEndDownScroll: null, // 结束下拉刷新的回调,马上要执行的代码 (如: 在wxs中使用)
+ callback: function(mescroll) {
+ // 下拉刷新的回调;默认重置上拉加载列表为第一页
+ mescroll.resetUpScroll();
+ }
+ })
+}
+
+/* 配置参数:上拉加载 */
+MeScroll.prototype.extendUpScroll = function(optUp) {
+ // 上拉加载的配置
+ MeScroll.extend(optUp, {
+ use: true, // 是否启用上拉加载; 默认true
+ auto: true, // 是否在初始化完毕之后自动执行上拉加载的回调; 默认true
+ isLock: false, // 是否锁定上拉加载,默认false;
+ isBoth: true, // 上拉加载时,如果滑动到列表顶部是否可以同时触发下拉刷新;默认true,两者可同时触发;
+ callback: null, // 上拉加载的回调;function(page,mescroll){ }
+ page: {
+ num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+ size: 10, // 每页数据的数量
+ time: null // 加载第一页数据服务器返回的时间; 防止用户翻页时,后台新增了数据从而导致下一页数据重复;
+ },
+ noMoreSize: 5, // 如果列表已无数据,可设置列表的总数量要大于等于5条才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看
+ offset: 80, // 距底部多远时,触发upCallback
+ textLoading: '加载中 ...', // 加载中的提示文本
+ textNoMore: '-- END --', // 没有更多数据的提示文本
+ bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorBottom)
+ textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
+ inited: null, // 初始化完毕的回调
+ showLoading: null, // 显示加载中的回调
+ showNoMore: null, // 显示无更多数据的回调
+ hideUpScroll: null, // 隐藏上拉加载的回调
+ errDistance: 60, // endErr的时候需往上滑动一段距离,使其往下滑动时再次触发onReachBottom,仅mescroll-body生效
+ toTop: {
+ // 回到顶部按钮,需配置src才显示
+ src: null, // 图片路径,默认null (绝对路径或网络图)
+ offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000
+ duration: 300, // 回到顶部的动画时长,默认300ms (当值为0或300则使用系统自带回到顶部,更流畅; 其他值则通过step模拟,部分机型可能不够流畅,所以非特殊情况不建议修改此项)
+ btnClick: null, // 点击按钮的回调
+ onShow: null, // 是否显示的回调
+ zIndex: 9990, // fixed定位z-index值
+ left: null, // 到左边的距离, 默认null. 此项有值时,right不生效. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+ right: 20, // 到右边的距离, 默认20 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+ bottom: 120, // 到底部的距离, 默认120 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+ safearea: false, // bottom的偏移量是否加上底部安全区的距离, 默认false, 需要适配iPhoneX时使用 (具体的界面如果不配置此项,则取本vue的safearea值)
+ width: 72, // 回到顶部图标的宽度, 默认72 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+ radius: "50%" // 圆角, 默认"50%" (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+ },
+ empty: {
+ use: true, // 是否显示空布局
+ icon: null, // 图标路径
+ tip: '暂无相关数据', // 提示
+ btnText: '', // 按钮
+ btnClick: null, // 点击按钮的回调
+ onShow: null, // 是否显示的回调
+ fixed: false, // 是否使用fixed定位,默认false; 配置fixed为true,以下的top和zIndex才生效 (transform会使fixed失效,最终会降级为absolute)
+ top: "100rpx", // fixed定位的top值 (完整的单位值,如 "10%"; "100rpx")
+ zIndex: 99 // fixed定位z-index值
+ },
+ onScroll: false // 是否监听滚动事件
+ })
+}
+
+/* 配置参数 */
+MeScroll.extend = function(userOption, defaultOption) {
+ if (!userOption) return defaultOption;
+ for (let key in defaultOption) {
+ if (userOption[key] == null) {
+ let def = defaultOption[key];
+ if (def != null && typeof def === 'object') {
+ userOption[key] = MeScroll.extend({}, def); // 深度匹配
+ } else {
+ userOption[key] = def;
+ }
+ } else if (typeof userOption[key] === 'object') {
+ MeScroll.extend(userOption[key], defaultOption[key]); // 深度匹配
+ }
+ }
+ return userOption;
+}
+
+/* 简单判断是否配置了颜色 (非透明,非白色) */
+MeScroll.prototype.hasColor = function(color) {
+ if(!color) return false;
+ let c = color.toLowerCase();
+ return c != "#fff" && c != "#ffffff" && c != "transparent" && c != "white"
+}
+
+/* -------初始化下拉刷新------- */
+MeScroll.prototype.initDownScroll = function() {
+ let me = this;
+ // 配置参数
+ me.optDown = me.options.down || {};
+ if(!me.optDown.textColor && me.hasColor(me.optDown.bgColor)) me.optDown.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
+ me.extendDownScroll(me.optDown);
+
+ // 如果是mescroll-body且配置了native,则禁止自定义的下拉刷新
+ if(me.isScrollBody && me.optDown.native){
+ me.optDown.use = false
+ }else{
+ me.optDown.native = false // 仅mescroll-body支持,mescroll-uni不支持
+ }
+
+ me.downHight = 0; // 下拉区域的高度
+
+ // 在页面中加入下拉布局
+ if (me.optDown.use && me.optDown.inited) {
+ // 初始化完毕的回调
+ setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
+ me.optDown.inited(me);
+ }, 0)
+ }
+}
+
+/* 列表touchstart事件 */
+MeScroll.prototype.touchstartEvent = function(e) {
+ if (!this.optDown.use) return;
+
+ this.startPoint = this.getPoint(e); // 记录起点
+ this.startTop = this.getScrollTop(); // 记录此时的滚动条位置
+ this.startAngle = 0; // 初始角度
+ this.lastPoint = this.startPoint; // 重置上次move的点
+ this.maxTouchmoveY = this.getBodyHeight() - this.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
+ this.inTouchend = false; // 标记不是touchend
+}
+
+/* 列表touchmove事件 */
+MeScroll.prototype.touchmoveEvent = function(e) {
+ if (!this.optDown.use) return;
+ let me = this;
+
+ let scrollTop = me.getScrollTop(); // 当前滚动条的距离
+ let curPoint = me.getPoint(e); // 当前点
+
+ let moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+
+ // 向下拉 && 在顶部
+ // mescroll-body,直接判定在顶部即可
+ // scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
+ // scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
+ if (moveY > 0 && (
+ (me.isScrollBody && scrollTop <= 0)
+ ||
+ (!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
+ )) {
+ // 可下拉的条件
+ if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
+ me.optUp.isBoth))) {
+
+ // 下拉的初始角度是否在配置的范围内
+ if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
+ if (me.startAngle < me.optDown.minAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
+
+ // 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
+ if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
+ me.inTouchend = true; // 标记执行touchend
+ me.touchendEvent(); // 提前触发touchend
+ return;
+ }
+
+ me.preventDefault(e); // 阻止默认事件
+
+ let diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
+
+ // 下拉距离 < 指定距离
+ if (me.downHight < me.optDown.offset) {
+ if (me.movetype !== 1) {
+ me.movetype = 1; // 加入标记,保证只执行一次
+ me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
+
+ // 指定距离 <= 下拉距离
+ } else {
+ if (me.movetype !== 2) {
+ me.movetype = 2; // 加入标记,保证只执行一次
+ me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ if (diff > 0) { // 向下拉
+ me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
+ } else { // 向上收
+ me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
+ }
+ }
+
+ me.downHight = Math.round(me.downHight) // 取整
+ let rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
+ me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
+ }
+ }
+
+ me.lastPoint = curPoint; // 记录本次移动的点
+}
+
+/* 列表touchend事件 */
+MeScroll.prototype.touchendEvent = function(e) {
+ if (!this.optDown.use) return;
+ // 如果下拉区域高度已改变,则需重置回来
+ if (this.isMoveDown) {
+ if (this.downHight >= this.optDown.offset) {
+ // 符合触发刷新的条件
+ this.triggerDownScroll();
+ } else {
+ // 不符合的话 则重置
+ this.downHight = 0;
+ this.endDownScrollCall(this);
+ }
+ this.movetype = 0;
+ this.isMoveDown = false;
+ } else if (!this.isScrollBody && this.getScrollTop() === this.startTop) { // scroll-view到顶/左/右/底的滑动事件
+ let isScrollUp = this.getPoint(e).y - this.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+ // 上滑
+ if (isScrollUp) {
+ // 需检查滑动的角度
+ let angle = this.getAngle(this.getPoint(e), this.startPoint); // 两点之间的角度,区间 [0,90]
+ if (angle > 80) {
+ // 检查并触发上拉
+ this.triggerUpScroll(true);
+ }
+ }
+ }
+}
+
+/* 根据点击滑动事件获取第一个手指的坐标 */
+MeScroll.prototype.getPoint = function(e) {
+ if (!e) {
+ return {
+ x: 0,
+ y: 0
+ }
+ }
+ if (e.touches && e.touches[0]) {
+ return {
+ x: e.touches[0].pageX,
+ y: e.touches[0].pageY
+ }
+ } else if (e.changedTouches && e.changedTouches[0]) {
+ return {
+ x: e.changedTouches[0].pageX,
+ y: e.changedTouches[0].pageY
+ }
+ } else {
+ return {
+ x: e.clientX,
+ y: e.clientY
+ }
+ }
+}
+
+/* 计算两点之间的角度: 区间 [0,90]*/
+MeScroll.prototype.getAngle = function(p1, p2) {
+ let x = Math.abs(p1.x - p2.x);
+ let y = Math.abs(p1.y - p2.y);
+ let z = Math.sqrt(x * x + y * y);
+ let angle = 0;
+ if (z !== 0) {
+ angle = Math.asin(y / z) / Math.PI * 180;
+ }
+ return angle
+}
+
+/* 触发下拉刷新 */
+MeScroll.prototype.triggerDownScroll = function() {
+ if (this.optDown.beforeLoading && this.optDown.beforeLoading(this)) {
+ //return true则处于完全自定义状态
+ } else {
+ this.showDownScroll(); // 下拉刷新中...
+ !this.optDown.native && this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
+ }
+}
+
+/* 显示下拉进度布局 */
+MeScroll.prototype.showDownScroll = function() {
+ this.isDownScrolling = true; // 标记下拉中
+ if (this.optDown.native) {
+ uni.startPullDownRefresh(); // 系统自带的下拉刷新
+ this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
+ } else{
+ this.downHight = this.optDown.offset; // 更新下拉区域高度
+ this.showDownLoadingCall(this.downHight); // 下拉刷新中...
+ }
+}
+
+MeScroll.prototype.showDownLoadingCall = function(downHight) {
+ this.optDown.showLoading && this.optDown.showLoading(this, downHight); // 下拉刷新中...
+ this.optDown.afterLoading && this.optDown.afterLoading(this, downHight); // 下拉刷新中...触发之后马上要执行的代码
+}
+
+/* 显示系统自带的下拉刷新时需要处理的业务 */
+MeScroll.prototype.onPullDownRefresh = function() {
+ this.isDownScrolling = true; // 标记下拉中
+ this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
+ this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
+}
+
+/* 结束下拉刷新 */
+MeScroll.prototype.endDownScroll = function() {
+ if (this.optDown.native) { // 结束原生下拉刷新
+ this.isDownScrolling = false;
+ this.endDownScrollCall(this);
+ uni.stopPullDownRefresh();
+ return
+ }
+ let me = this;
+ // 结束下拉刷新的方法
+ let endScroll = function() {
+ me.downHight = 0;
+ me.isDownScrolling = false;
+ me.endDownScrollCall(me);
+ if(!me.isScrollBody){
+ me.setScrollHeight(0) // scroll-view重置滚动区域,使数据不满屏时仍可检查触发翻页
+ me.scrollTo(0,0) // scroll-view需重置滚动条到顶部,避免startTop大于0时,对下拉刷新的影响
+ }
+ }
+ // 结束下拉刷新时的回调
+ let delay = 0;
+ if (me.optDown.beforeEndDownScroll) delay = me.optDown.beforeEndDownScroll(me); // 结束下拉刷新的延时,单位ms
+ if (typeof delay === 'number' && delay > 0) {
+ setTimeout(endScroll, delay);
+ } else {
+ endScroll();
+ }
+}
+
+MeScroll.prototype.endDownScrollCall = function() {
+ this.optDown.endDownScroll && this.optDown.endDownScroll(this);
+ this.optDown.afterEndDownScroll && this.optDown.afterEndDownScroll(this);
+}
+
+/* 锁定下拉刷新:isLock=ture,null锁定;isLock=false解锁 */
+MeScroll.prototype.lockDownScroll = function(isLock) {
+ if (isLock == null) isLock = true;
+ this.optDown.isLock = isLock;
+}
+
+/* 锁定上拉加载:isLock=ture,null锁定;isLock=false解锁 */
+MeScroll.prototype.lockUpScroll = function(isLock) {
+ if (isLock == null) isLock = true;
+ this.optUp.isLock = isLock;
+}
+
+/* -------初始化上拉加载------- */
+MeScroll.prototype.initUpScroll = function() {
+ let me = this;
+ // 配置参数
+ me.optUp = me.options.up || {use: false}
+ if(!me.optUp.textColor && me.hasColor(me.optUp.bgColor)) me.optUp.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
+ me.extendUpScroll(me.optUp);
+
+ if (me.optUp.use === false) return; // 配置不使用上拉加载时,则不初始化上拉布局
+ me.optUp.hasNext = true; // 如果使用上拉,则默认有下一页
+ me.startNum = me.optUp.page.num + 1; // 记录page开始的页码
+
+ // 初始化完毕的回调
+ if (me.optUp.inited) {
+ setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
+ me.optUp.inited(me);
+ }, 0)
+ }
+}
+
+/*滚动到底部的事件 (仅mescroll-body生效)*/
+MeScroll.prototype.onReachBottom = function() {
+ if (this.isScrollBody && !this.isUpScrolling) { // 只能支持下拉刷新的时候同时可以触发上拉加载,否则滚动到底部就需要上滑一点才能触发onReachBottom
+ if (!this.optUp.isLock && this.optUp.hasNext) {
+ this.triggerUpScroll();
+ }
+ }
+}
+
+/*列表滚动事件 (仅mescroll-body生效)*/
+MeScroll.prototype.onPageScroll = function(e) {
+ if (!this.isScrollBody) return;
+
+ // 更新滚动条的位置 (主要用于判断下拉刷新时,滚动条是否在顶部)
+ this.setScrollTop(e.scrollTop);
+
+ // 顶部按钮的显示隐藏
+ if (e.scrollTop >= this.optUp.toTop.offset) {
+ this.showTopBtn();
+ } else {
+ this.hideTopBtn();
+ }
+}
+
+/*列表滚动事件*/
+MeScroll.prototype.scroll = function(e, onScroll) {
+ // 更新滚动条的位置
+ this.setScrollTop(e.scrollTop);
+ // 更新滚动内容高度
+ this.setScrollHeight(e.scrollHeight);
+
+ // 向上滑还是向下滑动
+ if (this.preScrollY == null) this.preScrollY = 0;
+ this.isScrollUp = e.scrollTop - this.preScrollY > 0;
+ this.preScrollY = e.scrollTop;
+
+ // 上滑 && 检查并触发上拉
+ this.isScrollUp && this.triggerUpScroll(true);
+
+ // 顶部按钮的显示隐藏
+ if (e.scrollTop >= this.optUp.toTop.offset) {
+ this.showTopBtn();
+ } else {
+ this.hideTopBtn();
+ }
+
+ // 滑动监听
+ this.optUp.onScroll && onScroll && onScroll()
+}
+
+/* 触发上拉加载 */
+MeScroll.prototype.triggerUpScroll = function(isCheck) {
+ if (!this.isUpScrolling && this.optUp.use && this.optUp.callback) {
+ // 是否校验在底部; 默认不校验
+ if (isCheck === true) {
+ let canUp = false;
+ // 还有下一页 && 没有锁定 && 不在下拉中
+ if (this.optUp.hasNext && !this.optUp.isLock && !this.isDownScrolling) {
+ if (this.getScrollBottom() <= this.optUp.offset) { // 到底部
+ canUp = true; // 标记可上拉
+ }
+ }
+ if (canUp === false) return;
+ }
+ this.showUpScroll(); // 上拉加载中...
+ this.optUp.page.num++; // 预先加一页,如果失败则减回
+ this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
+ this.num = this.optUp.page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
+ this.size = this.optUp.page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
+ this.time = this.optUp.page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
+ this.optUp.callback(this); // 执行回调,联网加载数据
+ }
+}
+
+/* 显示上拉加载中 */
+MeScroll.prototype.showUpScroll = function() {
+ this.isUpScrolling = true; // 标记上拉加载中
+ this.optUp.showLoading && this.optUp.showLoading(this); // 回调
+}
+
+/* 显示上拉无更多数据 */
+MeScroll.prototype.showNoMore = function() {
+ this.optUp.hasNext = false; // 标记无更多数据
+ this.optUp.showNoMore && this.optUp.showNoMore(this); // 回调
+}
+
+/* 隐藏上拉区域**/
+MeScroll.prototype.hideUpScroll = function() {
+ this.optUp.hideUpScroll && this.optUp.hideUpScroll(this); // 回调
+}
+
+/* 结束上拉加载 */
+MeScroll.prototype.endUpScroll = function(isShowNoMore) {
+ if (isShowNoMore != null) { // isShowNoMore=null,不处理下拉状态,下拉刷新的时候调用
+ if (isShowNoMore) {
+ this.showNoMore(); // isShowNoMore=true,显示无更多数据
+ } else {
+ this.hideUpScroll(); // isShowNoMore=false,隐藏上拉加载
+ }
+ }
+ this.isUpScrolling = false; // 标记结束上拉加载
+}
+
+/* 重置上拉加载列表为第一页
+ *isShowLoading 是否显示进度布局;
+ * 1.默认null,不传参,则显示上拉加载的进度布局
+ * 2.传参true, 则显示下拉刷新的进度布局
+ * 3.传参false,则不显示上拉和下拉的进度 (常用于静默更新列表数据)
+ */
+MeScroll.prototype.resetUpScroll = function(isShowLoading) {
+ if (this.optUp && this.optUp.use) {
+ let page = this.optUp.page;
+ this.prePageNum = page.num; // 缓存重置前的页码,加载失败可退回
+ this.prePageTime = page.time; // 缓存重置前的时间,加载失败可退回
+ page.num = this.startNum; // 重置为第一页
+ page.time = null; // 重置时间为空
+ if (!this.isDownScrolling && isShowLoading !== false) { // 如果不是下拉刷新触发的resetUpScroll并且不配置列表静默更新,则显示进度;
+ if (isShowLoading == null) {
+ this.removeEmpty(); // 移除空布局
+ this.showUpScroll(); // 不传参,默认显示上拉加载的进度布局
+ } else {
+ this.showDownScroll(); // 传true,显示下拉刷新的进度布局,不清空列表
+ }
+ }
+ this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
+ this.num = page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
+ this.size = page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
+ this.time = page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
+ this.optUp.callback && this.optUp.callback(this); // 执行上拉回调
+ }
+}
+
+/* 设置page.num的值 */
+MeScroll.prototype.setPageNum = function(num) {
+ this.optUp.page.num = num - 1;
+}
+
+/* 设置page.size的值 */
+MeScroll.prototype.setPageSize = function(size) {
+ this.optUp.page.size = size;
+}
+
+/* 联网回调成功,结束下拉刷新和上拉加载
+ * dataSize: 当前页的数据量(必传)
+ * totalPage: 总页数(必传)
+ * systime: 服务器时间 (可空)
+ */
+MeScroll.prototype.endByPage = function(dataSize, totalPage, systime) {
+ let hasNext;
+ if (this.optUp.use && totalPage != null) hasNext = this.optUp.page.num < totalPage; // 是否还有下一页
+ this.endSuccess(dataSize, hasNext, systime);
+}
+
+/* 联网回调成功,结束下拉刷新和上拉加载
+ * dataSize: 当前页的数据量(必传)
+ * totalSize: 列表所有数据总数量(必传)
+ * systime: 服务器时间 (可空)
+ */
+MeScroll.prototype.endBySize = function(dataSize, totalSize, systime) {
+ let hasNext;
+ if (this.optUp.use && totalSize != null) {
+ let loadSize = (this.optUp.page.num - 1) * this.optUp.page.size + dataSize; // 已加载的数据总数
+ hasNext = loadSize < totalSize; // 是否还有下一页
+ }
+ this.endSuccess(dataSize, hasNext, systime);
+}
+
+/* 联网回调成功,结束下拉刷新和上拉加载
+ * dataSize: 当前页的数据个数(不是所有页的数据总和),用于上拉加载判断是否还有下一页.如果不传,则会判断还有下一页
+ * hasNext: 是否还有下一页,布尔类型;用来解决这个小问题:比如列表共有20条数据,每页加载10条,共2页.如果只根据dataSize判断,则需翻到第三页才会知道无更多数据,如果传了hasNext,则翻到第二页即可显示无更多数据.
+ * systime: 服务器时间(可空);用来解决这个小问题:当准备翻下一页时,数据库新增了几条记录,此时翻下一页,前面的几条数据会和上一页的重复;这里传入了systime,那么upCallback的page.time就会有值,把page.time传给服务器,让后台过滤新加入的那几条记录
+ */
+MeScroll.prototype.endSuccess = function(dataSize, hasNext, systime) {
+ let me = this;
+ // 结束下拉刷新
+ if (me.isDownScrolling) me.endDownScroll();
+
+ // 结束上拉加载
+ if (me.optUp.use) {
+ let isShowNoMore; // 是否已无更多数据
+ if (dataSize != null) {
+ let pageNum = me.optUp.page.num; // 当前页码
+ let pageSize = me.optUp.page.size; // 每页长度
+ // 如果是第一页
+ if (pageNum === 1) {
+ if (systime) me.optUp.page.time = systime; // 设置加载列表数据第一页的时间
+ }
+ if (dataSize < pageSize || hasNext === false) {
+ // 返回的数据不满一页时,则说明已无更多数据
+ me.optUp.hasNext = false;
+ if (dataSize === 0 && pageNum === 1) {
+ // 如果第一页无任何数据且配置了空布局
+ isShowNoMore = false;
+ me.showEmpty();
+ } else {
+ // 总列表数少于配置的数量,则不显示无更多数据
+ let allDataSize = (pageNum - 1) * pageSize + dataSize;
+ if (allDataSize < me.optUp.noMoreSize) {
+ isShowNoMore = false;
+ } else {
+ isShowNoMore = true;
+ }
+ me.removeEmpty(); // 移除空布局
+ }
+ } else {
+ // 还有下一页
+ isShowNoMore = false;
+ me.optUp.hasNext = true;
+ me.removeEmpty(); // 移除空布局
+ }
+ }
+
+ // 隐藏上拉
+ me.endUpScroll(isShowNoMore);
+ }
+}
+
+/* 回调失败,结束下拉刷新和上拉加载 */
+MeScroll.prototype.endErr = function(errDistance) {
+ // 结束下拉,回调失败重置回原来的页码和时间
+ if (this.isDownScrolling) {
+ let page = this.optUp.page;
+ if (page && this.prePageNum) {
+ page.num = this.prePageNum;
+ page.time = this.prePageTime;
+ }
+ this.endDownScroll();
+ }
+ // 结束上拉,回调失败重置回原来的页码
+ if (this.isUpScrolling) {
+ this.optUp.page.num--;
+ this.endUpScroll(false);
+ // 如果是mescroll-body,则需往回滚一定距离
+ if(this.isScrollBody && errDistance !== 0){ // 不处理0
+ if(!errDistance) errDistance = this.optUp.errDistance; // 不传,则取默认
+ this.scrollTo(this.getScrollTop() - errDistance, 0) // 往上回滚的距离
+ }
+ }
+}
+
+/* 显示空布局 */
+MeScroll.prototype.showEmpty = function() {
+ this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(true)
+}
+
+/* 移除空布局 */
+MeScroll.prototype.removeEmpty = function() {
+ this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(false)
+}
+
+/* 显示回到顶部的按钮 */
+MeScroll.prototype.showTopBtn = function() {
+ if (!this.topBtnShow) {
+ this.topBtnShow = true;
+ this.optUp.toTop.onShow && this.optUp.toTop.onShow(true);
+ }
+}
+
+/* 隐藏回到顶部的按钮 */
+MeScroll.prototype.hideTopBtn = function() {
+ if (this.topBtnShow) {
+ this.topBtnShow = false;
+ this.optUp.toTop.onShow && this.optUp.toTop.onShow(false);
+ }
+}
+
+/* 获取滚动条的位置 */
+MeScroll.prototype.getScrollTop = function() {
+ return this.scrollTop || 0
+}
+
+/* 记录滚动条的位置 */
+MeScroll.prototype.setScrollTop = function(y) {
+ this.scrollTop = y;
+}
+
+/* 滚动到指定位置 */
+MeScroll.prototype.scrollTo = function(y, t) {
+ this.myScrollTo && this.myScrollTo(y, t) // scrollview需自定义回到顶部方法
+}
+
+/* 自定义scrollTo */
+MeScroll.prototype.resetScrollTo = function(myScrollTo) {
+ this.myScrollTo = myScrollTo
+}
+
+/* 滚动条到底部的距离 */
+MeScroll.prototype.getScrollBottom = function() {
+ return this.getScrollHeight() - this.getClientHeight() - this.getScrollTop()
+}
+
+/* 计步器
+ star: 开始值
+ end: 结束值
+ callback(step,timer): 回调step值,计步器timer,可自行通过window.clearInterval(timer)结束计步器;
+ t: 计步时长,传0则直接回调end值;不传则默认300ms
+ rate: 周期;不传则默认30ms计步一次
+ * */
+MeScroll.prototype.getStep = function(star, end, callback, t, rate) {
+ let diff = end - star; // 差值
+ if (t === 0 || diff === 0) {
+ callback && callback(end);
+ return;
+ }
+ t = t || 300; // 时长 300ms
+ rate = rate || 30; // 周期 30ms
+ let count = t / rate; // 次数
+ let step = diff / count; // 步长
+ let i = 0; // 计数
+ let timer = setInterval(function() {
+ if (i < count - 1) {
+ star += step;
+ callback && callback(star, timer);
+ i++;
+ } else {
+ callback && callback(end, timer); // 最后一次直接设置end,避免计算误差
+ clearInterval(timer);
+ }
+ }, rate);
+}
+
+/* 滚动容器的高度 */
+MeScroll.prototype.getClientHeight = function(isReal) {
+ let h = this.clientHeight || 0
+ if (h === 0 && isReal !== true) { // 未获取到容器的高度,可临时取body的高度 (可能会有误差)
+ h = this.getBodyHeight()
+ }
+ return h
+}
+MeScroll.prototype.setClientHeight = function(h) {
+ this.clientHeight = h;
+}
+
+/* 滚动内容的高度 */
+MeScroll.prototype.getScrollHeight = function() {
+ return this.scrollHeight || 0;
+}
+MeScroll.prototype.setScrollHeight = function(h) {
+ this.scrollHeight = h;
+}
+
+/* body的高度 */
+MeScroll.prototype.getBodyHeight = function() {
+ return this.bodyHeight || 0;
+}
+MeScroll.prototype.setBodyHeight = function(h) {
+ this.bodyHeight = h;
+}
+
+/* 阻止浏览器默认滚动事件 */
+MeScroll.prototype.preventDefault = function(e) {
+ // 小程序不支持e.preventDefault, 已在wxs中禁止
+ // app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止, 或使用renderjs禁止
+ // cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
+ if (e && e.cancelable && !e.defaultPrevented) e.preventDefault()
+}
\ No newline at end of file
diff --git a/components/mescroll-uni/mescroll-uni.vue b/components/mescroll-uni/mescroll-uni.vue
new file mode 100644
index 0000000..5186d8c
--- /dev/null
+++ b/components/mescroll-uni/mescroll-uni.vue
@@ -0,0 +1,424 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{downText}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ mescroll.optUp.textLoading }}
+
+
+ {{ mescroll.optUp.textNoMore }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/mescroll-uni/mixins/mescroll-comp.js b/components/mescroll-uni/mixins/mescroll-comp.js
new file mode 100644
index 0000000..6aea07b
--- /dev/null
+++ b/components/mescroll-uni/mixins/mescroll-comp.js
@@ -0,0 +1,50 @@
+/**
+ * mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期:
+ * 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例)
+ * 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例)
+ */
+const MescrollCompMixin = {
+ // 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件 (一级)
+ onPageScroll(e) {
+ this.handlePageScroll(e)
+ },
+ onReachBottom() {
+ this.handleReachBottom()
+ },
+ // 当down的native: true时, 还需传递此方法进到子组件
+ onPullDownRefresh(){
+ this.handlePullDownRefresh()
+ },
+ // mescroll-body写在子子子...组件的情况 (多级)
+ data() {
+ return {
+ mescroll: {
+ onPageScroll: e=>{
+ this.handlePageScroll(e)
+ },
+ onReachBottom: ()=>{
+ this.handleReachBottom()
+ },
+ onPullDownRefresh: ()=>{
+ this.handlePullDownRefresh()
+ }
+ }
+ }
+ },
+ methods:{
+ handlePageScroll(e){
+ let item = this.$refs["mescrollItem"];
+ if(item && item.mescroll) item.mescroll.onPageScroll(e);
+ },
+ handleReachBottom(){
+ let item = this.$refs["mescrollItem"];
+ if(item && item.mescroll) item.mescroll.onReachBottom();
+ },
+ handlePullDownRefresh(){
+ let item = this.$refs["mescrollItem"];
+ if(item && item.mescroll) item.mescroll.onPullDownRefresh();
+ }
+ }
+}
+
+export default MescrollCompMixin;
diff --git a/components/mescroll-uni/mixins/mescroll-more-item.js b/components/mescroll-uni/mixins/mescroll-more-item.js
new file mode 100644
index 0000000..2549158
--- /dev/null
+++ b/components/mescroll-uni/mixins/mescroll-more-item.js
@@ -0,0 +1,51 @@
+/**
+ * mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例)
+ */
+const MescrollMoreItemMixin = {
+ // 支付宝小程序不支持props的mixin,需写在具体的页面中
+ // #ifndef MP-ALIPAY
+ props:{
+ i: Number, // 每个tab页的专属下标
+ index: { // 当前tab的下标
+ type: Number,
+ default(){
+ return 0
+ }
+ }
+ },
+ // #endif
+ data() {
+ return {
+ downOption:{
+ auto:false // 不自动加载
+ },
+ upOption:{
+ auto:false // 不自动加载
+ },
+ isInit: false // 当前tab是否已初始化
+ }
+ },
+ watch:{
+ // 监听下标的变化
+ index(val){
+ if (this.i === val && !this.isInit) {
+ this.isInit = true; // 标记为true
+ this.mescroll && this.mescroll.triggerDownScroll();
+ }
+ }
+ },
+ methods: {
+ // mescroll组件初始化的回调,可获取到mescroll对象 (覆盖mescroll-mixins.js的mescrollInit, 为了标记isInit)
+ mescrollInit(mescroll) {
+ this.mescroll = mescroll;
+ this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序
+ // 自动加载当前tab的数据
+ if(this.i === this.index){
+ this.isInit = true; // 标记为true
+ this.mescroll.triggerDownScroll();
+ }
+ },
+ }
+}
+
+export default MescrollMoreItemMixin;
diff --git a/components/mescroll-uni/mixins/mescroll-more.js b/components/mescroll-uni/mixins/mescroll-more.js
new file mode 100644
index 0000000..142aa75
--- /dev/null
+++ b/components/mescroll-uni/mixins/mescroll-more.js
@@ -0,0 +1,56 @@
+/**
+ * mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期:
+ * 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例)
+ * 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例)
+ */
+const MescrollMoreMixin = {
+ data() {
+ return {
+ tabIndex: 0 // 当前tab下标
+ }
+ },
+ // 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
+ onPageScroll(e) {
+ let mescroll = this.getMescroll(this.tabIndex);
+ mescroll && mescroll.onPageScroll(e);
+ },
+ onReachBottom() {
+ let mescroll = this.getMescroll(this.tabIndex);
+ mescroll && mescroll.onReachBottom();
+ },
+ // 当down的native: true时, 还需传递此方法进到子组件
+ onPullDownRefresh(){
+ let mescroll = this.getMescroll(this.tabIndex);
+ mescroll && mescroll.onPullDownRefresh();
+ },
+ methods:{
+ // 根据下标获取对应子组件的mescroll
+ getMescroll(i){
+ if(!this.mescrollItems) this.mescrollItems = [];
+ if(!this.mescrollItems[i]) {
+ // v-for中的refs
+ let vForItem = this.$refs["mescrollItem"];
+ if(vForItem){
+ this.mescrollItems[i] = vForItem[i]
+ }else{
+ // 普通的refs,不可重复
+ this.mescrollItems[i] = this.$refs["mescrollItem"+i];
+ }
+ }
+ let item = this.mescrollItems[i]
+ return item ? item.mescroll : null
+ },
+ // 切换tab,恢复滚动条位置
+ tabChange(i){
+ let mescroll = this.getMescroll(i);
+ if(mescroll){
+ // 延时(比$nextTick靠谱一些),确保元素已渲染
+ setTimeout(()=>{
+ mescroll.scrollTo(mescroll.getScrollTop(),0)
+ },30)
+ }
+ }
+ }
+}
+
+export default MescrollMoreMixin;
diff --git a/components/mescroll-uni/wxs/mixins.js b/components/mescroll-uni/wxs/mixins.js
new file mode 100644
index 0000000..7f49ff7
--- /dev/null
+++ b/components/mescroll-uni/wxs/mixins.js
@@ -0,0 +1,102 @@
+// 定义在wxs (含renderjs) 逻辑层的数据和方法, 与视图层相互通信
+const WxsMixin = {
+ data() {
+ return {
+ // 传入wxs视图层的数据 (响应式)
+ wxsProp: {
+ optDown:{}, // 下拉刷新的配置
+ scrollTop:0, // 滚动条的距离
+ bodyHeight:0, // body的高度
+ isDownScrolling:false, // 是否正在下拉刷新中
+ isUpScrolling:false, // 是否正在上拉加载中
+ isScrollBody:true, // 是否为mescroll-body滚动
+ isUpBoth:true, // 上拉加载时,是否同时可以下拉刷新
+ t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer)
+ },
+
+ // 标记调用wxs视图层的方法
+ callProp: {
+ callType: '', // 方法名
+ t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer)
+ },
+
+ // 不用wxs的平台使用此处的wxsBiz对象,抹平wxs的写法 (微信小程序和APP使用的wxsBiz对象是./wxs/wxs.wxs)
+ // #ifndef MP-WEIXIN || APP-PLUS || H5
+ wxsBiz: {
+ //注册列表touchstart事件,用于下拉刷新
+ touchstartEvent: e=> {
+ this.mescroll.touchstartEvent(e);
+ },
+ //注册列表touchmove事件,用于下拉刷新
+ touchmoveEvent: e=> {
+ this.mescroll.touchmoveEvent(e);
+ },
+ //注册列表touchend事件,用于下拉刷新
+ touchendEvent: e=> {
+ this.mescroll.touchendEvent(e);
+ },
+ propObserver(){}, // 抹平wxs的写法
+ callObserver(){} // 抹平wxs的写法
+ },
+ // #endif
+
+ // 不用renderjs的平台使用此处的renderBiz对象,抹平renderjs的写法 (app 和 h5 使用的renderBiz对象是./wxs/renderjs.js)
+ // #ifndef APP-PLUS || H5
+ renderBiz: {
+ propObserver(){} // 抹平renderjs的写法
+ }
+ // #endif
+ }
+ },
+ methods: {
+ // wxs视图层调用逻辑层的回调
+ wxsCall(msg){
+ if(msg.type === 'setWxsProp'){
+ // 更新wxsProp数据 (值改变才触发更新)
+ this.wxsProp = {
+ optDown: this.mescroll.optDown,
+ scrollTop: this.mescroll.getScrollTop(),
+ bodyHeight: this.mescroll.getBodyHeight(),
+ isDownScrolling: this.mescroll.isDownScrolling,
+ isUpScrolling: this.mescroll.isUpScrolling,
+ isUpBoth: this.mescroll.optUp.isBoth,
+ isScrollBody:this.mescroll.isScrollBody,
+ t: Date.now()
+ }
+ }else if(msg.type === 'setLoadType'){
+ // 设置inOffset,outOffset的状态
+ this.downLoadType = msg.downLoadType
+ }else if(msg.type === 'triggerDownScroll'){
+ // 主动触发下拉刷新
+ this.mescroll.triggerDownScroll();
+ }else if(msg.type === 'endDownScroll'){
+ // 结束下拉刷新
+ this.mescroll.endDownScroll();
+ }else if(msg.type === 'triggerUpScroll'){
+ // 主动触发上拉加载
+ this.mescroll.triggerUpScroll(true);
+ }
+ }
+ },
+ mounted() {
+ // #ifdef MP-WEIXIN || APP-PLUS || H5
+ // 配置主动触发wxs显示加载进度的回调
+ this.mescroll.optDown.afterLoading = ()=>{
+ this.callProp = {callType: "showLoading", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
+ }
+ // 配置主动触发wxs隐藏加载进度的回调
+ this.mescroll.optDown.afterEndDownScroll = ()=>{
+ this.callProp = {callType: "endDownScroll", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
+ setTimeout(()=>{
+ if(this.downLoadType === 4 || this.downLoadType === 0){
+ this.callProp = {callType: "clearTransform", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
+ }
+ },320)
+ }
+ // 初始化wxs的数据
+ this.wxsCall({type: 'setWxsProp'})
+ // #endif
+ }
+}
+
+export default WxsMixin;
diff --git a/components/mescroll-uni/wxs/renderjs.js b/components/mescroll-uni/wxs/renderjs.js
new file mode 100644
index 0000000..207f388
--- /dev/null
+++ b/components/mescroll-uni/wxs/renderjs.js
@@ -0,0 +1,92 @@
+// 使用renderjs直接操作window对象,实现动态控制app和h5的bounce
+// bounce: iOS橡皮筋,Android半月弧,h5浏览器下拉背景等效果 (下拉刷新时禁止)
+// https://uniapp.dcloud.io/frame?id=renderjs
+
+// 与wxs的me实例一致
+var me = {}
+
+// 初始化window对象的touch事件 (仅初始化一次)
+if(window && !window.$mescrollRenderInit){
+ window.$mescrollRenderInit = true
+
+
+ window.addEventListener('touchstart', function(e){
+ if (me.disabled()) return;
+ me.startPoint = me.getPoint(e); // 记录起点
+ }, {passive: true})
+
+
+ window.addEventListener('touchmove', function(e){
+ if (me.disabled()) return;
+ if (me.getScrollTop() > 0) return; // 需在顶部下拉,才禁止bounce
+
+ var curPoint = me.getPoint(e); // 当前点
+ var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+ // 向下拉
+ if (moveY > 0) {
+ // 可下拉的条件
+ if (!me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling && me.isUpBoth))) {
+
+ // 只有touch在mescroll的view上面,才禁止bounce
+ var el = e.target;
+ var isMescrollTouch = false;
+ while (el && el.tagName && el.tagName !== 'UNI-PAGE-BODY' && el.tagName != "BODY") {
+ var cls = el.classList;
+ if (cls && cls.contains('mescroll-render-touch')) {
+ isMescrollTouch = true
+ break;
+ }
+ el = el.parentNode; // 继续检查其父元素
+ }
+ // 禁止bounce (不会对swiper和iOS侧滑返回造成影响)
+ if (isMescrollTouch && e.cancelable && !e.defaultPrevented) e.preventDefault();
+ }
+ }
+ }, {passive: false})
+}
+
+/* 获取滚动条的位置 */
+me.getScrollTop = function() {
+ return me.scrollTop || 0
+}
+
+/* 是否禁用下拉刷新 */
+me.disabled = function(){
+ return !me.optDown || !me.optDown.use || me.optDown.native
+}
+
+/* 根据点击滑动事件获取第一个手指的坐标 */
+me.getPoint = function(e) {
+ if (!e) {
+ return {x: 0,y: 0}
+ }
+ if (e.touches && e.touches[0]) {
+ return {x: e.touches[0].pageX,y: e.touches[0].pageY}
+ } else if (e.changedTouches && e.changedTouches[0]) {
+ return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY}
+ } else {
+ return {x: e.clientX,y: e.clientY}
+ }
+}
+
+/**
+ * 监听逻辑层数据的变化 (实时更新数据)
+ */
+function propObserver(wxsProp) {
+ me.optDown = wxsProp.optDown
+ me.scrollTop = wxsProp.scrollTop
+ me.isDownScrolling = wxsProp.isDownScrolling
+ me.isUpScrolling = wxsProp.isUpScrolling
+ me.isUpBoth = wxsProp.isUpBoth
+}
+
+/* 导出模块 */
+const renderBiz = {
+ data() {
+ return {
+ propObserver: propObserver,
+ }
+ }
+}
+
+export default renderBiz;
\ No newline at end of file
diff --git a/components/mescroll-uni/wxs/wxs.wxs b/components/mescroll-uni/wxs/wxs.wxs
new file mode 100644
index 0000000..3fb4ad9
--- /dev/null
+++ b/components/mescroll-uni/wxs/wxs.wxs
@@ -0,0 +1,268 @@
+// 使用wxs处理交互动画, 提高性能, 同时避免小程序bounce对下拉刷新的影响
+// https://uniapp.dcloud.io/frame?id=wxs
+// https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html
+
+// 模拟mescroll实例, 与mescroll.js的写法尽量保持一致
+var me = {}
+
+// ------ 自定义下拉刷新动画 start ------
+
+/* 下拉过程中的回调,滑动过程一直在执行 (rate<1为inOffset; rate>1为outOffset) */
+me.onMoving = function (ins, rate, downHight){
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'transform', // 可解决下拉过程中, image和swiper脱离文档流的问题
+ 'transform': 'translateY(' + downHight + 'px)',
+ 'transition': ''
+ })
+ // 环形进度条
+ var progress = ins.selectComponent('.mescroll-wxs-progress')
+ progress && progress.setStyle({transform: 'rotate(' + 360 * rate + 'deg)'})
+ })
+}
+
+/* 显示下拉刷新进度 */
+me.showLoading = function (ins){
+ me.downHight = me.optDown.offset
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'auto',
+ 'transform': 'translateY(' + me.downHight + 'px)',
+ 'transition': 'transform 300ms'
+ })
+ })
+}
+
+/* 结束下拉 */
+me.endDownScroll = function (ins){
+ me.downHight = 0;
+ me.isDownScrolling = false;
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': 'auto',
+ 'transform': 'translateY(0)', // 不可以写空串,否则scroll-view渲染不完整 (延时350ms会调clearTransform置空)
+ 'transition': 'transform 300ms'
+ })
+ })
+}
+
+/* 结束下拉动画执行完毕后, 清除transform和transition, 避免对列表内容样式造成影响, 如: h5的list-msg示例下拉进度条漏出来等 */
+me.clearTransform = function (ins){
+ ins.requestAnimationFrame(function () {
+ ins.selectComponent('.mescroll-wxs-content').setStyle({
+ 'will-change': '',
+ 'transform': '',
+ 'transition': ''
+ })
+ })
+}
+
+// ------ 自定义下拉刷新动画 end ------
+
+/**
+ * 监听逻辑层数据的变化 (实时更新数据)
+ */
+function propObserver(wxsProp) {
+ me.optDown = wxsProp.optDown
+ me.scrollTop = wxsProp.scrollTop
+ me.bodyHeight = wxsProp.bodyHeight
+ me.isDownScrolling = wxsProp.isDownScrolling
+ me.isUpScrolling = wxsProp.isUpScrolling
+ me.isUpBoth = wxsProp.isUpBoth
+ me.isScrollBody = wxsProp.isScrollBody
+ me.startTop = wxsProp.scrollTop // 及时更新touchstart触发的startTop, 避免scroll-view快速惯性滚动到顶部取值不准确
+}
+
+/**
+ * 监听逻辑层数据的变化 (调用wxs的方法)
+ */
+function callObserver(callProp, oldValue, ins) {
+ if (me.disabled()) return;
+ if(callProp.callType){
+ // 逻辑层(App Service)的style已失效,需在视图层(Webview)设置style
+ if(callProp.callType === 'showLoading'){
+ me.showLoading(ins)
+ }else if(callProp.callType === 'endDownScroll'){
+ me.endDownScroll(ins)
+ }else if(callProp.callType === 'clearTransform'){
+ me.clearTransform(ins)
+ }
+ }
+}
+
+/**
+ * touch事件
+ */
+function touchstartEvent(e, ins) {
+ me.downHight = 0; // 下拉的距离
+ me.startPoint = me.getPoint(e); // 记录起点
+ me.startTop = me.getScrollTop(); // 记录此时的滚动条位置
+ me.startAngle = 0; // 初始角度
+ me.lastPoint = me.startPoint; // 重置上次move的点
+ me.maxTouchmoveY = me.getBodyHeight() - me.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
+ me.inTouchend = false; // 标记不是touchend
+
+ me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
+}
+
+function touchmoveEvent(e, ins) {
+ var isPrevent = true // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
+
+ if (me.disabled()) return isPrevent;
+
+ var scrollTop = me.getScrollTop(); // 当前滚动条的距离
+ var curPoint = me.getPoint(e); // 当前点
+
+ var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+
+ // 向下拉 && 在顶部
+ // mescroll-body,直接判定在顶部即可
+ // scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
+ // scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
+ if (moveY > 0 && (
+ (me.isScrollBody && scrollTop <= 0)
+ ||
+ (!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
+ )) {
+ // 可下拉的条件
+ if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
+ me.isUpBoth))) {
+
+ // 下拉的角度是否在配置的范围内
+ if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
+ if (me.startAngle < me.optDown.minAngle) return isPrevent; // 如果小于配置的角度,则不往下执行下拉刷新
+
+ // 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
+ if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
+ me.inTouchend = true; // 标记执行touchend
+ touchendEvent(e, ins); // 提前触发touchend
+ return isPrevent;
+ }
+
+ isPrevent = false // 小程序是return false
+
+ var diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
+
+ // 下拉距离 < 指定距离
+ if (me.downHight < me.optDown.offset) {
+ if (me.movetype !== 1) {
+ me.movetype = 1; // 加入标记,保证只执行一次
+ // me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
+ me.callMethod(ins, {type: 'setLoadType', downLoadType: 1})
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
+
+ // 指定距离 <= 下拉距离
+ } else {
+ if (me.movetype !== 2) {
+ me.movetype = 2; // 加入标记,保证只执行一次
+ // me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
+ me.callMethod(ins, {type: 'setLoadType', downLoadType: 2})
+ me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+ }
+ if (diff > 0) { // 向下拉
+ me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
+ } else { // 向上收
+ me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
+ }
+ }
+
+ me.downHight = Math.round(me.downHight) // 取整
+ var rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
+ // me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
+ me.onMoving(ins, rate, me.downHight)
+ }
+ }
+
+ me.lastPoint = curPoint; // 记录本次移动的点
+
+ return isPrevent // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
+}
+
+function touchendEvent(e, ins) {
+ // 如果下拉区域高度已改变,则需重置回来
+ if (me.isMoveDown) {
+ if (me.downHight >= me.optDown.offset) {
+ // 符合触发刷新的条件
+ me.downHight = me.optDown.offset; // 更新下拉区域高度
+ // me.triggerDownScroll();
+ me.callMethod(ins, {type: 'triggerDownScroll'})
+ } else {
+ // 不符合的话 则重置
+ me.downHight = 0;
+ // me.optDown.endDownScroll && me.optDown.endDownScroll(me);
+ me.callMethod(ins, {type: 'endDownScroll'})
+ }
+ me.movetype = 0;
+ me.isMoveDown = false;
+ } else if (!me.isScrollBody && me.getScrollTop() === me.startTop) { // scroll-view到顶/左/右/底的滑动事件
+ var isScrollUp = me.getPoint(e).y - me.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+ // 上滑
+ if (isScrollUp) {
+ // 需检查滑动的角度
+ var angle = me.getAngle(me.getPoint(e), me.startPoint); // 两点之间的角度,区间 [0,90]
+ if (angle > 80) {
+ // 检查并触发上拉
+ // me.triggerUpScroll(true);
+ me.callMethod(ins, {type: 'triggerUpScroll'})
+ }
+ }
+ }
+ me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
+}
+
+/* 是否禁用下拉刷新 */
+me.disabled = function(){
+ return !me.optDown || !me.optDown.use || me.optDown.native
+}
+
+/* 根据点击滑动事件获取第一个手指的坐标 */
+me.getPoint = function(e) {
+ if (!e) {
+ return {x: 0,y: 0}
+ }
+ if (e.touches && e.touches[0]) {
+ return {x: e.touches[0].pageX,y: e.touches[0].pageY}
+ } else if (e.changedTouches && e.changedTouches[0]) {
+ return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY}
+ } else {
+ return {x: e.clientX,y: e.clientY}
+ }
+}
+
+/* 计算两点之间的角度: 区间 [0,90]*/
+me.getAngle = function (p1, p2) {
+ var x = Math.abs(p1.x - p2.x);
+ var y = Math.abs(p1.y - p2.y);
+ var z = Math.sqrt(x * x + y * y);
+ var angle = 0;
+ if (z !== 0) {
+ angle = Math.asin(y / z) / Math.PI * 180;
+ }
+ return angle
+}
+
+/* 获取滚动条的位置 */
+me.getScrollTop = function() {
+ return me.scrollTop || 0
+}
+
+/* 获取body的高度 */
+me.getBodyHeight = function() {
+ return me.bodyHeight || 0;
+}
+
+/* 调用逻辑层的方法 */
+me.callMethod = function(ins, param) {
+ if(ins) ins.callMethod('wxsCall', param)
+}
+
+/* 导出模块 */
+module.exports = {
+ propObserver: propObserver,
+ callObserver: callObserver,
+ touchstartEvent: touchstartEvent,
+ touchmoveEvent: touchmoveEvent,
+ touchendEvent: touchendEvent
+}
\ No newline at end of file
diff --git a/components/my-nocontent/my-nocontent.vue b/components/my-nocontent/my-nocontent.vue
new file mode 100644
index 0000000..e0de312
--- /dev/null
+++ b/components/my-nocontent/my-nocontent.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ 暂无数据
+
+
+
+
+
+
diff --git a/components/my-uni-skeleton/README.md b/components/my-uni-skeleton/README.md
new file mode 100644
index 0000000..77c64e1
--- /dev/null
+++ b/components/my-uni-skeleton/README.md
@@ -0,0 +1,77 @@
+# skeleton
+感谢原作者 https://ext.dcloud.net.cn/plugin?id=852
+
+自己项目非常需要骨架,正好原作者发布了1.0 根据自己项目 自己修改了下。
+
+目前仅支持:
+1.轮播图
+2.分类栏
+3.头像
+4.文章条
+5.动态心情
+
+以上是根据自己项目修改的,后续再拓展,或者自己根据自己项目修改,原作者写的还是很灵活的,修改方便!
+
+
+## 属性说明
+
+|属性名|类型|默认值|说明|
+| -- | -- | --|--|
+| loading | Boolean | true | 是否显示占位图 |
+| flexType | String | flex-start | 排列方式 center 居中 √ space-between 两端对齐 √ space-around 子元素拉手分布 √ flex-start 居左 flex-end 居右 |
+| imgTitle | Boolean | false | 轮播图占位图 |
+| showAvatar | Boolean | true | 是否显示头像占位图 |
+| nameRow | Number | 1 | 显示头像圆1个 |
+| avatarSize | String | 50px | 头像站占位图大小 |
+| avatarShape | String | round | 头像形状,可选值:round, square |
+| showTitle | Boolean | true | 是否显示标题占位图 |
+| titleWidth | String | 40% | 标题占位图宽度 |
+| row | Number| 3 | 标题段落占位图行数 |
+| animate | Boolean | true | 是否开启动画 |
+
+## 使用示例
+
+```html
+
+ 我是段落1
+
+```
+
+```javascript
+import Skeleton from '../components/skeleton/index.vue'
+export default {
+ components: {
+ Skeleton
+ },
+ data() {
+ return {
+ loading: true,
+ skeleton1 : {
+ avatarSize: '52px',
+ row: 3,
+ showTitle: true,
+ }
+ }
+ },
+ created() {
+ this.reloadData()
+ },
+ methods: {
+ reloadData() {
+ this.loading = true
+ setTimeout(() => {
+ this.loading = false
+ }, 3000)
+ },
+ },
+}
+```
+
+## 效果图
+
+
diff --git a/components/my-uni-skeleton/index.vue b/components/my-uni-skeleton/index.vue
new file mode 100644
index 0000000..05c56a9
--- /dev/null
+++ b/components/my-uni-skeleton/index.vue
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages.json b/pages.json
index 99ac9af..ddb8cb3 100644
--- a/pages.json
+++ b/pages.json
@@ -35,19 +35,19 @@
"backgroundColor": "#ffffff",
"list": [{
"pagePath": "pages/index/index",
- "iconPath": "static/index.png",
- "selectedIconPath": "static/index-selected.png",
+ "iconPath": "static/tabbar/index.png",
+ "selectedIconPath": "static/tabbar/index-selected.png",
"text": "首页"
}, {
"pagePath": "pages/order/index",
- "iconPath": "static/center.png",
- "selectedIconPath": "static/center-selected.png",
+ "iconPath": "static/tabbar/center.png",
+ "selectedIconPath": "static/tabbar/center-selected.png",
"text": "订单"
},
{
"pagePath": "pages/center/index",
- "iconPath": "static/center.png",
- "selectedIconPath": "static/center-selected.png",
+ "iconPath": "static/tabbar/center.png",
+ "selectedIconPath": "static/tabbar/center-selected.png",
"text": "我的"
}
]
diff --git a/static/center-selected.png b/static/tabbar/center-selected.png
similarity index 100%
rename from static/center-selected.png
rename to static/tabbar/center-selected.png
diff --git a/static/center.png b/static/tabbar/center.png
similarity index 100%
rename from static/center.png
rename to static/tabbar/center.png
diff --git a/static/index-selected.png b/static/tabbar/index-selected.png
similarity index 100%
rename from static/index-selected.png
rename to static/tabbar/index-selected.png
diff --git a/static/index.png b/static/tabbar/index.png
similarity index 100%
rename from static/index.png
rename to static/tabbar/index.png