Browse Source

首次提交

lgh 3 months ago
commit
93096d5ae0
100 changed files with 13184 additions and 0 deletions
  1. 27 0
      .gitignore
  2. 21 0
      App.vue
  3. 42 0
      apis/index.js
  4. 9 0
      apis/order.js
  5. 10 0
      apis/user.js
  6. 74 0
      components/no-data/no-data.vue
  7. 4 0
      config/default.js
  8. 23 0
      config/index.js
  9. 5 0
      config/modules/dev.js
  10. 5 0
      config/modules/pre.js
  11. 5 0
      config/modules/prod.js
  12. 5 0
      config/modules/test.js
  13. 20 0
      index.html
  14. 31 0
      main.js
  15. 88 0
      manifest.json
  16. 120 0
      package-lock.json
  17. 55 0
      package.json
  18. 100 0
      pages.json
  19. 213 0
      pages/index/detail.vue
  20. 573 0
      pages/index/index.vue
  21. 29 0
      pages/login/index.vue
  22. 253 0
      pages/order/index.vue
  23. 738 0
      pages/order/order_confirm.vue
  24. 250 0
      pages/order/success.vue
  25. 398 0
      pages/user/index.vue
  26. 6 0
      plugins/mescroll/changelog.md
  27. 19 0
      plugins/mescroll/components/mescroll-body/mescroll-body.css
  28. 400 0
      plugins/mescroll/components/mescroll-body/mescroll-body.vue
  29. 47 0
      plugins/mescroll/components/mescroll-diy/beibei/components/mescroll-down.css
  30. 39 0
      plugins/mescroll/components/mescroll-diy/beibei/components/mescroll-down.vue
  31. 360 0
      plugins/mescroll/components/mescroll-diy/beibei/mescroll-body.vue
  32. 49 0
      plugins/mescroll/components/mescroll-diy/beibei/mescroll-uni-option.js
  33. 437 0
      plugins/mescroll/components/mescroll-diy/beibei/mescroll-uni.vue
  34. 44 0
      plugins/mescroll/components/mescroll-diy/xinlang/components/mescroll-down.css
  35. 53 0
      plugins/mescroll/components/mescroll-diy/xinlang/components/mescroll-down.vue
  36. 32 0
      plugins/mescroll/components/mescroll-diy/xinlang/components/mescroll-up.css
  37. 40 0
      plugins/mescroll/components/mescroll-diy/xinlang/components/mescroll-up.vue
  38. 380 0
      plugins/mescroll/components/mescroll-diy/xinlang/mescroll-body.vue
  39. 64 0
      plugins/mescroll/components/mescroll-diy/xinlang/mescroll-uni-option.js
  40. 462 0
      plugins/mescroll/components/mescroll-diy/xinlang/mescroll-uni.vue
  41. 116 0
      plugins/mescroll/components/mescroll-empty/mescroll-empty.vue
  42. 55 0
      plugins/mescroll/components/mescroll-uni/components/mescroll-down.css
  43. 47 0
      plugins/mescroll/components/mescroll-uni/components/mescroll-down.vue
  44. 83 0
      plugins/mescroll/components/mescroll-uni/components/mescroll-top.vue
  45. 47 0
      plugins/mescroll/components/mescroll-uni/components/mescroll-up.css
  46. 39 0
      plugins/mescroll/components/mescroll-uni/components/mescroll-up.vue
  47. 15 0
      plugins/mescroll/components/mescroll-uni/mescroll-i18n.js
  48. 57 0
      plugins/mescroll/components/mescroll-uni/mescroll-mixins.js
  49. 64 0
      plugins/mescroll/components/mescroll-uni/mescroll-uni-option.js
  50. 36 0
      plugins/mescroll/components/mescroll-uni/mescroll-uni.css
  51. 799 0
      plugins/mescroll/components/mescroll-uni/mescroll-uni.js
  52. 477 0
      plugins/mescroll/components/mescroll-uni/mescroll-uni.vue
  53. 47 0
      plugins/mescroll/components/mescroll-uni/mixins/mescroll-comp.js
  54. 66 0
      plugins/mescroll/components/mescroll-uni/mixins/mescroll-more-item.js
  55. 74 0
      plugins/mescroll/components/mescroll-uni/mixins/mescroll-more.js
  56. 109 0
      plugins/mescroll/components/mescroll-uni/wxs/mixins.js
  57. 92 0
      plugins/mescroll/components/mescroll-uni/wxs/renderjs.js
  58. 268 0
      plugins/mescroll/components/mescroll-uni/wxs/wxs.wxs
  59. 80 0
      plugins/mescroll/package.json
  60. 45 0
      plugins/mescroll/readme.md
  61. 21 0
      plugins/uview-ui/LICENSE
  62. 66 0
      plugins/uview-ui/README.md
  63. 357 0
      plugins/uview-ui/changelog.md
  64. 78 0
      plugins/uview-ui/components/u--form/u--form.vue
  65. 73 0
      plugins/uview-ui/components/u--input/u--input.vue
  66. 44 0
      plugins/uview-ui/components/u--text/u--text.vue
  67. 48 0
      plugins/uview-ui/components/u--textarea/u--textarea.vue
  68. 72 0
      plugins/uview-ui/components/u-badge/props.js
  69. 171 0
      plugins/uview-ui/components/u-badge/u-badge.vue
  70. 46 0
      plugins/uview-ui/components/u-button/nvue.scss
  71. 161 0
      plugins/uview-ui/components/u-button/props.js
  72. 493 0
      plugins/uview-ui/components/u-button/u-button.vue
  73. 80 0
      plugins/uview-ui/components/u-button/vue.scss
  74. 14 0
      plugins/uview-ui/components/u-cell-group/props.js
  75. 61 0
      plugins/uview-ui/components/u-cell-group/u-cell-group.vue
  76. 110 0
      plugins/uview-ui/components/u-cell/props.js
  77. 229 0
      plugins/uview-ui/components/u-cell/u-cell.vue
  78. 82 0
      plugins/uview-ui/components/u-checkbox-group/props.js
  79. 103 0
      plugins/uview-ui/components/u-checkbox-group/u-checkbox-group.vue
  80. 69 0
      plugins/uview-ui/components/u-checkbox/props.js
  81. 344 0
      plugins/uview-ui/components/u-checkbox/u-checkbox.vue
  82. 79 0
      plugins/uview-ui/components/u-code-input/props.js
  83. 252 0
      plugins/uview-ui/components/u-code-input/u-code-input.vue
  84. 34 0
      plugins/uview-ui/components/u-code/props.js
  85. 129 0
      plugins/uview-ui/components/u-code/u-code.vue
  86. 48 0
      plugins/uview-ui/components/u-form-item/props.js
  87. 238 0
      plugins/uview-ui/components/u-form-item/u-form-item.vue
  88. 45 0
      plugins/uview-ui/components/u-form/props.js
  89. 217 0
      plugins/uview-ui/components/u-form/u-form.vue
  90. 24 0
      plugins/uview-ui/components/u-gap/props.js
  91. 38 0
      plugins/uview-ui/components/u-gap/u-gap.vue
  92. 214 0
      plugins/uview-ui/components/u-icon/icons.js
  93. 89 0
      plugins/uview-ui/components/u-icon/props.js
  94. 234 0
      plugins/uview-ui/components/u-icon/u-icon.vue
  95. 187 0
      plugins/uview-ui/components/u-input/props.js
  96. 354 0
      plugins/uview-ui/components/u-input/u-input.vue
  97. 33 0
      plugins/uview-ui/components/u-line/props.js
  98. 62 0
      plugins/uview-ui/components/u-line/u-line.vue
  99. 39 0
      plugins/uview-ui/components/u-link/props.js
  100. 0 0
      plugins/uview-ui/components/u-link/u-link.vue

+ 27 - 0
.gitignore

@@ -0,0 +1,27 @@
+.DS_Store
+node_modules/
+unpackage/
+dist/
+.history
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.project
+.idea
+.hbuilderx
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw*
+
+apis/user.js.rej

+ 21 - 0
App.vue

@@ -0,0 +1,21 @@
+<script>
+	export default {
+		onLaunch: function() {
+			console.log('App Launch')
+		},
+		onShow: function() {
+			console.log('App Show')
+		},
+		onHide: function() {
+			console.log('App Hide')
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import '@/static/style/common.scss';
+	/*每个页面公共css */
+	*{
+		box-sizing:border-box;
+	}
+</style>

+ 42 - 0
apis/index.js

@@ -0,0 +1,42 @@
+import request from "@/request"
+
+// 首页数据
+export const home = (data) => {
+	return request.get({
+		url: '/api/index',
+		data: data,
+	})
+}
+
+// 根据ID获取套餐数据
+export const getPackageById = (data) => {
+	return request.get({
+		url: '/api/getPackageById',
+		data: data,
+	})
+}
+
+// 根据ID获取配置参数
+export const getConfigById = (data) => {
+	return request.get({
+		url: '/api/getConfigById',
+		data: data,
+	})
+}
+
+// 上传文件
+export const upload = (data) => {
+	return request.post({
+		url: '/sys/oss/upload',
+		data: data,
+	})
+}
+
+
+// 下单
+export const save = (data) => {
+	return request.post({
+		url: '/api/order/save',
+		data: data,
+	})
+}

+ 9 - 0
apis/order.js

@@ -0,0 +1,9 @@
+import request from "@/request"
+
+// 订单分页
+export const orderApi = (data) => {
+	return request.get({
+		url: '/api/order/page',
+		data: data,
+	})
+}

+ 10 - 0
apis/user.js

@@ -0,0 +1,10 @@
+import request from "@/request"
+
+
+// 订单分页
+export const loginWx = (data) => {
+	return request.post({
+		url: '/api/loginWx',
+		data: data,
+	})
+}

+ 74 - 0
components/no-data/no-data.vue

@@ -0,0 +1,74 @@
+<template>
+  <!-- 无数据展示 -->
+    <view class="no-data-box">
+      <slot v-if="custom"></slot>
+      <view v-else class="text-center">
+        <view class="flex jcc">
+          <image class="status-img" mode="widthFix" v-if="statusImgUrl" :src="statusImgUrl"></image>
+        </view>
+        <view class="no-data-tips">{{tipsTextCOM}}</view>
+      </view>
+  </view>
+</template>
+
+<script>
+  import defaultConfig from '@/config/default.js'
+  export default {
+    props: {
+      statusType: { // 空状态类型
+        type: Number,
+        default: 1
+      },
+      custom: Boolean, // 是否自定义插槽
+      tipsText: String, // 文案
+    },
+    data() {
+      return {
+        statusImgUrl: '',
+      }
+    },
+    computed: {
+      // statusImgUrl() {
+      //   console.log(this.$store.state.emptyStatus.status);
+      //   let url = ''
+      //   const maps = this.$store.state.emptyStatus.status
+      //   const index = maps.findIndex(item => item.type === this.statusType)
+      //   url = index !== -1 ? defaultConfig.ossImgUrl + maps[index].url : ''
+      //   console.log(url);
+      //   return url
+      // },
+      tipsTextCOM() {
+        let text = ''
+        text = this.tipsText
+        if (this.statusType === 1) {
+          text = '暂无数据'
+        }
+        return text
+      }
+    },
+    methods: {}
+  }
+</script>
+
+<style lang="scss" scoped>
+  .no-data-box {
+      width: 100%;
+      height: 100%;
+      z-index: 996;
+      margin-top: 160rpx;
+    }
+    .status-img {
+      width: 50%;
+      margin-bottom: 26rpx;
+    }
+
+    .no-data-tips {
+      color: #707476;
+      font-size: 28rpx;
+      line-height: 40rpx;
+      // margin-bottom: 34rpx;
+      text-align: center;
+      padding: 0 50rpx;
+    }
+  
+</style>

+ 4 - 0
config/default.js

@@ -0,0 +1,4 @@
+module.exports = {
+	ossImgUrl: 'https://seafood-1313828448.cos.ap-nanjing.myqcloud.com/seafood/giftcard/image', // oss图片域名地址
+	agreeUrl: 'http://debugseafood.mashangyl.com' // 用户协议、隐私政策url
+}

+ 23 - 0
config/index.js

@@ -0,0 +1,23 @@
+import platform from "@/utils/platform" // 平台判断方法
+import getEnv from "@/utils/env" // 获取环境变量
+const defaults = require("./default.js")
+const dev = require("./modules/dev.js")
+const test = require("./modules/test.js")
+const prod = require("./modules/prod.js")
+const pre = require("./modules/pre.js")
+const ENV = platform() === 'APP-PLUS' ? 'app' : getEnv()
+console.log('当前运行平台 :>>', platform())
+console.log("当前运行环境 :>> ", ENV)
+
+const BASE = {
+	// 本地
+	dev,
+	// 生产
+	prod,
+	// 测试
+	test,
+	// 预发布
+	pre,
+}
+
+module.exports = Object.assign(defaults, BASE[ENV])

+ 5 - 0
config/modules/dev.js

@@ -0,0 +1,5 @@
+module.exports = {
+	env: 'dev',
+	baseUrl: 'https://videotest.9jodia.net/weapi',
+	appid: 'wx32a0607fd20c6c16'
+}

+ 5 - 0
config/modules/pre.js

@@ -0,0 +1,5 @@
+module.exports = {
+	env: 'pre',
+	baseUrl: 'http://preseafood.mashangyl.com',
+	appid: 'wx32a0607fd20c6c16'
+}

+ 5 - 0
config/modules/prod.js

@@ -0,0 +1,5 @@
+module.exports = {
+	env: 'prod',
+	baseUrl: 'http://seafood.boxingyu.com',
+	appid: 'wx32a0607fd20c6c16'
+}

+ 5 - 0
config/modules/test.js

@@ -0,0 +1,5 @@
+module.exports = {
+	env: 'test',
+	baseUrl: 'http://testseafood.mashangyl.com',
+	appid: 'wx32a0607fd20c6c16'
+}

+ 20 - 0
index.html

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <script>
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
+        CSS.supports('top: constant(a)'))
+      document.write(
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+        (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/main.js"></script>
+  </body>
+</html>

+ 31 - 0
main.js

@@ -0,0 +1,31 @@
+import App from './App'
+
+// #ifndef VUE3
+import Vue from 'vue'
+import uView from '@/plugins/uview-ui'
+import '@/utils/preCheck.js'
+import '@/utils/prototype.js'
+import platform from "@/utils/platform" // 平台判断方法
+import getEnv from "@/utils/env" // 获取环境变量
+
+import './uni.promisify.adaptor'
+
+Vue.use(uView)
+
+Vue.config.productionTip = false
+App.mpType = 'app'
+const app = new Vue({
+  ...App
+})
+app.$mount()
+// #endif
+
+// #ifdef VUE3
+import { createSSRApp } from 'vue'
+export function createApp() {
+  const app = createSSRApp(App)
+  return {
+    app
+  }
+}
+// #endif

+ 88 - 0
manifest.json

@@ -0,0 +1,88 @@
+{
+	"name": "高投短视频分销系统",
+	"appid": "__UNI__1F0A327",
+	"description": "",
+	"versionName": "1.0.0",
+	"versionCode": "100",
+	"transformPx": false,
+	/* 5+App特有相关 */
+	"app-plus": {
+		"usingComponents": true,
+		"nvueStyleCompiler": "uni-app",
+		"compilerVersion": 3,
+		"splashscreen": {
+			"alwaysShowBeforeRender": true,
+			"waiting": true,
+			"autoclose": true,
+			"delay": 0
+		},
+		/* 模块配置 */
+		"modules": {},
+		/* 应用发布信息 */
+		"distribute": {
+			/* android打包配置 */
+			"android": {
+				"permissions": [
+					"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+					"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+					"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+					"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+					"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+					"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+					"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+					"<uses-permission android:name=\"android.permission.CAMERA\"/>",
+					"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+					"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+					"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+					"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+					"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+					"<uses-feature android:name=\"android.hardware.camera\"/>",
+					"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+				]
+			},
+			/* ios打包配置 */
+			"ios": {},
+			/* SDK配置 */
+			"sdkConfigs": {}
+		}
+	},
+	/* 快应用特有相关 */
+	"quickapp": {},
+	/* 小程序特有相关 */
+	"mp-weixin": {
+		"appid": "",
+		"setting": {
+			"urlCheck": false
+		},
+		"usingComponents": true
+	},
+	"mp-alipay": {
+		"usingComponents": true
+	},
+	"mp-baidu": {
+		"usingComponents": true
+	},
+	"mp-toutiao": {
+		"usingComponents": true
+	},
+	"uniStatistics": {
+		"enable": false
+	},
+	"vueVersion": "2",
+	"h5": {
+		"router": {
+			"mode": "hash",
+			"base": "./"
+		},
+		"sdkConfigs": {
+			"weixin": {
+				"appid": "wx32a0607fd20c6c16",
+				"scope": "snsapi_userinfo",
+				"callback": "https://videotest.9jodia.net" // 必须与微信后台配置域名一致
+			}
+		},
+		"devServer": {
+			"port": "https://videotest.9jodia.net"
+		}
+	}
+}

+ 120 - 0
package-lock.json

@@ -0,0 +1,120 @@
+{
+	"name": "name",
+	"version": "1.0.0",
+	"lockfileVersion": 2,
+	"requires": true,
+	"packages": {
+		"": {
+			"name": "name",
+			"version": "1.0.0",
+			"dependencies": {
+				"jweixin-module": "^1.6.0",
+				"vconsole": "^3.15.0"
+			}
+		},
+		"node_modules/@babel/runtime": {
+			"version": "7.27.0",
+			"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.0.tgz",
+			"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
+			"dependencies": {
+				"regenerator-runtime": "^0.14.0"
+			},
+			"engines": {
+				"node": ">=6.9.0"
+			}
+		},
+		"node_modules/copy-text-to-clipboard": {
+			"version": "3.2.0",
+			"resolved": "https://registry.npmmirror.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz",
+			"integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==",
+			"engines": {
+				"node": ">=12"
+			},
+			"funding": {
+				"url": "https://github.com/sponsors/sindresorhus"
+			}
+		},
+		"node_modules/core-js": {
+			"version": "3.41.0",
+			"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.41.0.tgz",
+			"integrity": "sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==",
+			"hasInstallScript": true,
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/core-js"
+			}
+		},
+		"node_modules/jweixin-module": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmmirror.com/jweixin-module/-/jweixin-module-1.6.0.tgz",
+			"integrity": "sha512-dGk9cf+ipipHmtzYmKZs5B2toX+p4hLyllGLF6xuC8t+B05oYxd8fYoaRz0T30U2n3RUv8a4iwvjhA+OcYz52w=="
+		},
+		"node_modules/mutation-observer": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmmirror.com/mutation-observer/-/mutation-observer-1.0.3.tgz",
+			"integrity": "sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA=="
+		},
+		"node_modules/regenerator-runtime": {
+			"version": "0.14.1",
+			"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+			"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+		},
+		"node_modules/vconsole": {
+			"version": "3.15.1",
+			"resolved": "https://registry.npmmirror.com/vconsole/-/vconsole-3.15.1.tgz",
+			"integrity": "sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==",
+			"dependencies": {
+				"@babel/runtime": "^7.17.2",
+				"copy-text-to-clipboard": "^3.0.1",
+				"core-js": "^3.11.0",
+				"mutation-observer": "^1.0.3"
+			}
+		}
+	},
+	"dependencies": {
+		"@babel/runtime": {
+			"version": "7.27.0",
+			"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.0.tgz",
+			"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
+			"requires": {
+				"regenerator-runtime": "^0.14.0"
+			}
+		},
+		"copy-text-to-clipboard": {
+			"version": "3.2.0",
+			"resolved": "https://registry.npmmirror.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz",
+			"integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q=="
+		},
+		"core-js": {
+			"version": "3.41.0",
+			"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.41.0.tgz",
+			"integrity": "sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA=="
+		},
+		"jweixin-module": {
+			"version": "1.6.0",
+			"resolved": "https://registry.npmmirror.com/jweixin-module/-/jweixin-module-1.6.0.tgz",
+			"integrity": "sha512-dGk9cf+ipipHmtzYmKZs5B2toX+p4hLyllGLF6xuC8t+B05oYxd8fYoaRz0T30U2n3RUv8a4iwvjhA+OcYz52w=="
+		},
+		"mutation-observer": {
+			"version": "1.0.3",
+			"resolved": "https://registry.npmmirror.com/mutation-observer/-/mutation-observer-1.0.3.tgz",
+			"integrity": "sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA=="
+		},
+		"regenerator-runtime": {
+			"version": "0.14.1",
+			"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+			"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+		},
+		"vconsole": {
+			"version": "3.15.1",
+			"resolved": "https://registry.npmmirror.com/vconsole/-/vconsole-3.15.1.tgz",
+			"integrity": "sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==",
+			"requires": {
+				"@babel/runtime": "^7.17.2",
+				"copy-text-to-clipboard": "^3.0.1",
+				"core-js": "^3.11.0",
+				"mutation-observer": "^1.0.3"
+			}
+		}
+	}
+}

+ 55 - 0
package.json

@@ -0,0 +1,55 @@
+{
+	"uni-app": {
+		"scripts": {
+			"mp-h5-dev": {
+				"title": "h5-dev",
+				"browser": "chrome",
+				"env": {
+					"UNI_PLATFORM": "h5"
+				},
+				"define": {
+					"DEV": true
+				}
+			},
+			"mp-h5-test": {
+				"title": "h5-test",
+				"browser": "chrome",
+				"env": {
+					"UNI_PLATFORM": "h5"
+				},
+				"define": {
+					"TEST": true
+				}
+			},
+			"mp-h5-pre": {
+				"title": "h5-pre",
+				"browser": "chrome",
+				"env": {
+					"UNI_PLATFORM": "h5"
+				},
+				"define": {
+					"PRE": true
+				}
+			},
+			"mp-h5-prod": {
+				"title": "h5-prod",
+				"browser": "chrome",
+				"env": {
+					"UNI_PLATFORM": "h5"
+				},
+				"define": {
+					"PROD": true
+				}
+			}
+		}
+	},
+	"name": "name",
+	"description": "description",
+	"authors": "author",
+	"version": "1.0.0",
+	"main": "pathToMain",
+	"dependencies": {
+		"jweixin-module": "^1.6.0",
+		"vconsole": "^3.15.0"
+	}
+}

+ 100 - 0
pages.json

@@ -0,0 +1,100 @@
+{
+	"easycom": {
+		"^u-(.*)": "@/plugins/uview-ui/components/u-$1/u-$1.vue"
+	},
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/index/index",
+			"style": {
+				"navigationBarTitleText": "首页",
+				"navigationStyle": "custom"
+
+			}
+		},
+		{
+			"path": "pages/user/index",
+			"style": {
+				"navigationBarTitleText": "我的",
+				"navigationStyle": "custom"
+
+			}
+		}
+	],
+	"subPackages": [
+		{
+			"root": "pages/login",
+			"pages": [
+				{
+					"path": "index",
+					"style": {
+						"navigationBarTitleText": "uni-app"
+					}
+				}
+			]
+		},{
+			"root": "pages/index",
+			"pages": [
+				{
+					"path": "detail",
+					"style": {
+						"navigationBarTitleText": "",
+						"navigationStyle": "custom"
+					}
+				}
+			]
+		},{
+			"root": "pages/order",
+			"pages": [
+				{
+					"path": "index",
+					"style": {
+						"navigationBarTitleText": "我的订单",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "order_confirm",
+					"style": {
+						"navigationBarTitleText": "确认购买",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "success",
+					"style": {
+						"navigationBarTitleText": "购买成功",
+						"navigationStyle": "custom"
+					}
+				}
+			]
+		}
+	],
+	"tabBar": {
+		"color": "#9EA3B1",
+		"selectedColor": "#0D121A",
+		"borderStyle": "black",
+		"backgroundColor": "#ffffff",
+		"list":[
+			{
+				"pagePath": "pages/index/index",
+				"iconPath": "static/image/ic_home_unselected.png",
+				"selectedIconPath": "static/image/ic_home_selected.png",
+				"text": "首页"
+			},
+			{
+				"pagePath": "pages/user/index",
+				"iconPath": "static/image/ic_me_unselected.png",
+				"selectedIconPath": "static/image/ic_me_selected.png",
+				"text": "我的"
+			}
+		]
+		
+	},
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "uni-app",
+		"navigationBarBackgroundColor": "#F8F8F8",
+		"backgroundColor": "#F8F8F8"
+	},
+	"uniIdRouter": {}
+}

+ 213 - 0
pages/index/detail.vue

@@ -0,0 +1,213 @@
+<!-- 详情页 -->
+<template>
+    <view class="detail">
+        <!-- 三大套餐对比 -->
+        <view class="menu_tmps">
+            <view class="tips">
+                <view class="tips-title">
+                    <text class="title-text">三大套餐所有内容对比</text>
+                    <image :src="lineBg" class="line" mode="widthFix"></image>
+                </view>
+            </view>
+            <view class="menu_table">
+                <view class="table-prop flex jcsb aic">
+                    <view class="table-prop-item">内容板块</view>
+                    <view v-for="item in tabLists" :key="item" class="table-prop-item">{{ item }}</view>
+                </view>
+                <view v-for="(item, index) in configs" :key="index" class="table-item">
+                    <view class="table-title flex aic jcsb">
+                        <text class="ttitle">{{ item.name }}</text>
+                        <view class="cutout flex aic" @click="item.flag = !item.flag">
+                            <text>{{ item.flag ? '展开' : '收起' }}</text>
+                            <image :src="item.flag ? more_mindown : more_minup"></image>
+                        </view>
+                    </view>
+                    <view v-if="!item.flag" v-for="(items, indexs) in item.data" :key="indexs" class="table-content">
+                        <view class="row flex aic jcsb">
+                            <view class="row_name">
+                                <text>{{ items.name }}</text>
+                            </view>
+                            <view v-for="(itm, inx) in items.data" :key="inx" class="row_value">
+                                <image v-if="!itm || itm == ''" :src="emptyImg" class="row_empty"></image>
+                                <text v-else>{{ itm }}</text>
+                            </view>
+                        </view>
+                    </view>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+import { home } from '@/apis/index'
+
+export default {
+    data() {
+        return {
+            lineBg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_line.png',
+            emptyImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_empty.png',
+            tabLists: [],
+            packagesEntity: [],
+            configs: [],
+            type: "",
+        };
+    },
+    onLoad(options) {
+        if (options.type) this.type = options.type
+        this.getData()
+    },
+    methods: {
+        // 获取列表
+        getData() {
+            home({ type: this.type }).then(res => {
+                this.tabLists = res.packages
+                this.configs = res.configs
+
+            })
+        },
+    },
+}
+</script>
+<style lang='scss' scoped>
+.detail {
+    width: 100%;
+    // min-height: 100vh;
+    background-color: #0D121A;
+    // position: absolute;
+    // top: 0;
+    // bottom: 0;
+    display: flex;
+    flex-direction: column;
+    padding: 96rpx 24rpx 24rpx;
+}
+
+.tips {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+
+    .tips-title {
+        display: flex;
+        flex-direction: column;
+        align-items: flex-end;
+
+        .title-text {
+            font-weight: bold;
+            font-size: 40rpx;
+            color: transparent;
+            background: linear-gradient(54.35820730218651deg, #00C1FF 0%, #2FFFFC 100%);
+            -webkit-background-clip: text;
+        }
+
+        .line {
+            width: 112rpx;
+        }
+    }
+
+    .tips-content {
+        font-size: 32rpx;
+        color: #F8FAFF;
+        margin-top: 28rpx;
+
+        .cantant {
+            font-weight: 400;
+            font-size: 32rpx;
+            color: #00ECFF;
+            background: linear-gradient(54.35820730218651deg, #00C1FF 0%, #2FFFFC 100%);
+            -webkit-background-clip: text;
+            text-decoration: underline;
+            margin-left: 8rpx;
+        }
+    }
+}
+
+.menu_tmps {
+    width: 100%;
+
+    .menu_table {
+        background: linear-gradient(180deg, rgba(9, 155, 255, 0.12) 0%, rgba(66, 121, 192, 0.08) 100%);
+        border-radius: 24rpx;
+        border: 2rpx solid #003849;
+        margin-top: 48rpx;
+
+        .table-prop {
+            padding: 36rpx 40rpx;
+            color: #F8FAFF;
+            font-size: 24rpx;
+            border-bottom: #1D3A4A 2rpx solid;
+        }
+
+        .table-item {
+            padding: 40rpx;
+            font-size: 24rpx;
+            border-bottom: #1D3A4A 2rpx solid;
+
+            .table-title {
+                .ttitle {
+                    background: linear-gradient(311.670791165708deg, #00C1FF 0%, #2FFFFC 100%);
+                    -webkit-background-clip: text;
+                    color: transparent;
+                }
+
+                .cutout {
+                    text {
+                        background: linear-gradient(54.35820730218651deg, #00C1FF 0%, #2FFFFC 100%);
+                        -webkit-background-clip: text;
+                        color: transparent;
+                    }
+
+                    image {
+                        width: 32rpx;
+                        height: 32rpx;
+                        margin-left: 8rpx;
+                    }
+                }
+            }
+
+            .table-content {
+                margin-top: 40rpx;
+                color: #FFFFFF;
+
+                .row {
+                    margin-top: 40rpx;
+                    width: 100%;
+
+                    .row_name {
+                        white-space: nowrap;
+                        width: 15%;
+                        display: flex;
+                        flex-direction: column;
+
+                        text:nth-child(2) {
+                            font-size: 20rpx;
+                            text-align: center;
+                        }
+                    }
+
+                    .row_value {
+                        width: 15%;
+                        text-align: center;
+                        display: flex;
+                        justify-content: center;
+                    }
+
+                    .row_empty {
+                        width: 32rpx;
+                        height: 32rpx;
+                    }
+                }
+
+                .row:nth-child(1) {
+                    margin-top: 0;
+                }
+            }
+        }
+
+        .table-item:last-child {
+            border: none;
+        }
+    }
+}
+</style>

+ 573 - 0
pages/index/index.vue

@@ -0,0 +1,573 @@
+<template>
+	<view class="content">
+		<view class="all">
+			<view class="title">
+				<!-- <image class="logo" :src="logoImg" mode="heightFix"></image> -->
+				高投短视频分销系统
+			</view>
+
+			<!-- 顶部提示语 -->
+			<!-- <view class="tips">
+				<view class="tips-title">
+					<text class="title-text">请选择适合您团队的套餐</text>
+					<image :src="lineBg" class="line" mode="widthFix"></image>
+				</view>
+				<view class="tips-content">
+					<view class="">不同套餐所享受权益不同,您可按需采购</view>
+					<view class="flex aic">
+						<text>有任何疑问请</text>
+						<text class="cantant">联系客服</text>
+					</view>
+				</view>
+			</view> -->
+
+			<!-- 内容 -->
+			<view class="tabs-wrapper">
+
+				<!-- 成片/素材 -->
+				<view class="tabs">
+					<view class="tab-item" :class="tabStatus == 0 ? 'select' : ''" @click="tabStatusChange(0)">拍成片</view>
+					<view class="tab-item" :class="tabStatus == 1 ? 'select' : ''" @click="tabStatusChange(1)">拍素材</view>
+				</view>
+
+				<!-- 套餐tabs -->
+				<view class="tab_menus flex jcsb aic">
+					<view class="tab-menu-item flex aic" v-for="(item, index) in tabLists" :key="index" @click="changeTabs(item,index)">
+						<text :class="tabIndex == index ? 'now_menu' : ''">{{ item }}</text>
+						<image v-if="index == tabIndex" :src="biaoqianImg"></image>
+					</view>
+				</view>
+
+				<!-- 套餐内容 -->
+				<view class="menu_con" :class="tabIndex == 0 ? 'bg1' : tabIndex == 1 ? 'bg2' : 'bg3'">
+					<view class="cons">
+						<view class="menu_title">
+							<text class="metit">{{ setData.packageName }}</text>
+							<view class="exmple flex aic">
+								<image class="play" :src="playImg"></image>
+								<text>案例</text>
+							</view>
+						</view>
+						<view class="amounts">
+							<view class="now flex aife">
+								<view class="money flex aife">
+									<text class="dw fs40">¥</text>
+									<text class="num">{{setData.currentPrice}}</text>
+									<text class="unit">/条</text>
+								</view>
+								<view class="limit">限时</view>
+							</view>
+							<view class="past flex aic">
+								<text class="past_text fs28">原价</text>
+								<text class="past_num fs32">¥{{setData.originalPrice}}/条</text>
+							</view>
+						</view>
+						<view class="choose">
+							<view v-for="(item, index) in setData.descriptions" :key="index" class="choose_item-box flex aic jcsb">
+								<view class="choose_item flex aic">
+									<image :src="guoxuanImg"></image>
+									<text>{{item}}</text>
+								</view>
+								<view v-if="index == 0" class="choose_item-more flex aic" @click="isList = !isList">
+									<text>查看更多</text>
+									<image :src="!isList?moreImg:more_maxdown"></image>
+								</view>
+							</view>
+							<!-- <view class="choose_item flex aic">
+								<image :src="guoxuanImg"></image>
+								<text>交付周期 3 天</text>
+							</view>
+							<view class="choose_item flex aic">
+								<image :src="guoxuanImg"></image>
+								<text>交付周期 3 天</text>
+							</view>
+							<view class="choose_item flex aic">
+								<image :src="guoxuanImg"></image>
+								<text>交付周期 3 天</text>
+							</view> -->
+						</view>
+					</view>
+				</view>
+
+				<!-- 三大套餐对比 -->
+				<view v-if="isList" class="menu_tmps">
+					<view class="tips">
+						<view class="tips-title">
+							<text class="title-text">三大套餐所有内容对比</text>
+							<image :src="lineBg" class="line" mode="widthFix"></image>
+						</view>
+						<!-- <view class="tips-content">适用于TikTok、亚马逊、YouTub、INS</view> -->
+					</view>
+					<view class="menu_table">
+						<view class="table-prop flex jcsb aic">
+							<view class="table-prop-item">内容板块</view>
+							<view v-for="item in tabLists" :key="item" class="table-prop-item">{{ item }}</view>
+							<!-- <view class="table-prop-item">B类套餐</view>
+							<view class="table-prop-item">C类套餐</view> -->
+						</view>
+						<view v-for="(item, index) in configs" :key="index" class="table-item">
+							<view class="table-title flex aic jcsb">
+								<text class="ttitle">{{ item.name }}</text>
+								<view class="cutout flex aic" @click="item.flag=!item.flag">
+									<text>{{!item.flag?'展开':'收起'}}</text>
+									<image :src="!item.flag?more_mindown:more_minup"></image>
+								</view>
+							</view>
+							<view v-if="item.flag" v-for="(items, indexs) in item.data" :key="indexs" class="table-content">
+								<view class="row flex aic jcsb">
+									<view class="row_name">
+										<text>{{ items.name }}</text>
+									</view>
+									<view v-for="(itm,inx) in items.data" :key="inx" class="row_value">
+										<image v-if="!itm || itm == ''" :src="emptyImg" class="row_empty"></image>
+										<text v-else>{{ itm }}</text>
+									</view>
+								</view>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view class="btom bg1">
+			<view class="btn" :style="{ background: 'url(' + buybtnBg + ')' }" @click="toBuy()">立即购买</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { home } from '@/apis/index'
+export default {
+	data() {
+		return {
+			logoImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/img_logo.png',
+			lineBg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_line.png',
+			biaoqianImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_biaoqian.png',
+			playImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_play.png',
+			guoxuanImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_guoxuan.png',
+			moreImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_more.png',
+			more_minup: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_more_minup.png',
+			more_mindown: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_more_mindown.png',
+			more_maxdown: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_more_maxdown.png',
+			emptyImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_empty.png',
+			buybtnBg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_buy.png',
+			tabStatus: 0,// 成片/素材
+			tabIndex: 0,// 套餐tabs角标
+			tabLists: [],// 套餐tabs列表
+			packagesEntity:[],
+			setData:{},
+			isList:false,
+			configs: []
+		}
+	},
+	onLoad(){
+		this.getData()
+	},
+	onShow() {
+		this.changeTabbar()
+	},
+	methods: {
+		// 跳转到购买详情页
+		toBuy(){
+			uni.navigateTo({
+				url: '/pages/order/order_confirm?id='+this.setData.packageId+'&strip='+ this.setData.count + '&type='+this.tabStatus
+			})
+		},
+
+		// 成片/素材
+		tabStatusChange(num){
+			this.tabStatus = num
+			this.getData()
+		},
+		// 套餐tabs角标
+		changeTabs(data,num){
+			this.tabIndex = num
+			this.setData = this.packagesEntity.find(item => item.packageName === data)
+		},
+		// 获取列表
+		getData(){
+			home({type:this.tabStatus}).then(res => {
+				this.tabLists = res.packages
+				this.packagesEntity = res.packagesEntity
+				this.setData = this.packagesEntity[0]
+				this.configs = res.configs
+				console.log(res,'res');
+
+			})
+		},
+
+		// tabbar样式
+		changeTabbar() {
+			uni.setTabBarStyle({
+				color: '#9EA3B1',
+				selectedColor: '#FFFFFF',
+				backgroundColor: '#0D121A'
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.content {
+	width: 100%;
+	// min-height: 100vh;
+	background-color: #0D121A;
+	position: absolute;
+	top: 0;
+	bottom: 0;
+	display: flex;
+	flex-direction: column;
+	.all{
+		flex: 1;
+		overflow: auto;
+		padding: 0 24rpx 24rpx;
+
+	}
+	.title {
+		padding: 16rpx 8rpx;
+		font-weight: normal;
+		font-size: 32rpx;
+		color: #FFFFFF;
+		.logo {
+			height: 56rpx;
+		}
+	}
+
+	.tips {
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		margin-top: 96rpx;
+
+		.tips-title {
+			display: flex;
+			flex-direction: column;
+			align-items: flex-end;
+
+			.title-text {
+				font-weight: bold;
+				font-size: 40rpx;
+				color: transparent;
+				background: linear-gradient(54.35820730218651deg, #00C1FF 0%, #2FFFFC 100%);
+				-webkit-background-clip: text;
+			}
+
+			.line {
+				width: 112rpx;
+			}
+		}
+
+		.tips-content {
+			font-size: 32rpx;
+			color: #F8FAFF;
+			margin-top: 28rpx;
+
+			.cantant {
+				font-weight: 400;
+				font-size: 32rpx;
+				color: #00ECFF;
+				background: linear-gradient(54.35820730218651deg, #00C1FF 0%, #2FFFFC 100%);
+				-webkit-background-clip: text;
+				text-decoration: underline;
+				margin-left: 8rpx;
+			}
+		}
+	}
+
+	.tabs-wrapper {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		margin-top: 52rpx;
+
+		.tabs {
+			display: flex;
+			justify-content: center;
+			width: 368rpx;
+			height: 72rpx;
+			background: rgba($color: #FFF, $alpha: 0.1);
+			border-radius: 36rpx;
+
+			.tab-item {
+				display: flex;
+				justify-content: center;
+				align-items: center;
+				font-size: 28rpx;
+				width: 50%;
+				height: 64rpx;
+				color: #F6F8FF;
+			}
+		}
+
+		.select {
+			background: linear-gradient(144deg, #00C1FF 0%, #2FFFFC 100%);
+			border-radius: 32rpx;
+			color: #0D121A !important;
+		}
+
+		.tab_menus {
+			padding: 64rpx 40rpx 56rpx;
+			width: 100%;
+
+			.tab-menu-item {
+				color: #9EA3B1;
+				font-size: 28rpx;
+
+				.now_menu {
+					font-size: 36rpx;
+					color: #F8FAFF !important;
+					margin-right: 10rpx;
+				}
+
+				image {
+					width: 36rpx;
+					height: 36rpx;
+				}
+			}
+
+		}
+
+		
+
+		.menu_con {
+			width: 100%;
+			border-radius: 24rpx;
+			border: 2rpx solid rgba(255, 255, 255, 0.12);
+			margin: 0 24rpx;
+			padding: 56rpx 32rpx 48rpx;
+
+			.cons {
+				padding: 0 16rpx;
+			}
+
+			.menu_title {
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+
+				.metit {
+					font-size: 44rpx;
+					color: #F8FAFF;
+				}
+
+				.exmple {
+					width: 156rpx;
+					height: 64rpx;
+					background: #0D121A;
+					border-radius: 12rpx;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					font-size: 28rpx;
+					color: #FFFFFF;
+
+					.play {
+						width: 32rpx;
+						height: 32rpx;
+						margin-right: 12rpx;
+					}
+				}
+			}
+
+			.amounts {
+				margin-top: 72rpx;
+
+				.now {
+					.money {
+						font-size: 32rpx;
+						color: #FFFFFF;
+
+						.dw {
+							line-height: 40rpx;
+						}
+
+						.num {
+							font-size: 72rpx;
+							line-height: 56rpx;
+						}
+
+						.unit {
+							margin-left: 24rpx;
+						}
+					}
+
+					.limit {
+						margin-left: 24rpx;
+						width: 72rpx;
+						height: 40rpx;
+						background: linear-gradient(144deg, #00C1FF 0%, #2FFFFC 100%);
+						border-radius: 8rpx;
+						font-weight: 500;
+						font-size: 24rpx;
+						color: #0D121A;
+						text-align: center;
+						line-height: 40rpx;
+					}
+				}
+
+				.past {
+					color: #BCC0C5;
+					margin-top: 34rpx;
+
+					.past_text {
+						margin-right: 8rpx;
+					}
+				}
+			}
+
+			.choose {
+				margin-top: 64rpx;
+
+				.choose_item-box {
+					margin-top: 16rpx;
+
+					image {
+						width: 48rpx;
+						height: 48rpx;
+						margin-right: 24rpx;
+					}
+
+					text {
+						color: #F8FAFF;
+						font-size: 28rpx;
+					}
+				}
+
+				.choose_item-more {
+					text {
+						font-size: 28rpx;
+						background: linear-gradient(54.35820730218651deg, #00C1FF 0%, #2FFFFC 100%);
+						color: transparent;
+						-webkit-background-clip: text;
+					}
+
+					image {
+						width: 32rpx;
+						height: 32rpx;
+						margin-left: 12rpx;
+					}
+				}
+
+				.choose_item-box:nth-child(1) {
+					margin-top: 0;
+				}
+			}
+		}
+
+		.menu_tmps {
+			width: 100%;
+
+			.menu_table {
+				background: linear-gradient(180deg, rgba(9, 155, 255, 0.12) 0%, rgba(66, 121, 192, 0.08) 100%);
+				border-radius: 24rpx;
+				border: 2rpx solid #003849;
+				margin-top: 48rpx;
+
+				.table-prop {
+					padding: 36rpx 40rpx;
+					color: #F8FAFF;
+					font-size: 24rpx;
+					border-bottom: #1D3A4A 2rpx solid;
+				}
+
+				.table-item {
+					padding: 40rpx;
+					font-size: 24rpx;
+					border-bottom: #1D3A4A 2rpx solid;
+
+					.table-title {
+						.ttitle {
+							background: linear-gradient(311.670791165708deg, #00C1FF 0%, #2FFFFC 100%);
+							-webkit-background-clip: text;
+							color: transparent;
+						}
+
+						.cutout {
+							text {
+								background: linear-gradient(54.35820730218651deg, #00C1FF 0%, #2FFFFC 100%);
+								-webkit-background-clip: text;
+								color: transparent;
+							}
+
+							image {
+								width: 32rpx;
+								height: 32rpx;
+								margin-left: 8rpx;
+							}
+						}
+					}
+
+					.table-content {
+						margin-top: 40rpx;
+						color: #FFFFFF;
+
+						.row {
+							margin-top: 40rpx;
+							width: 100%;
+
+							.row_name {
+								white-space: nowrap;
+								width: 15%;
+								display: flex;
+								flex-direction: column;
+
+								text:nth-child(2) {
+									font-size: 20rpx;
+									text-align: center;
+								}
+							}
+
+							.row_value {
+								width: 15%;
+								text-align: center;
+								display: flex;
+								justify-content: center;
+							}
+
+							.row_empty {
+								width: 32rpx;
+								height: 32rpx;
+							}
+						}
+
+						.row:nth-child(1) {
+							margin-top: 0;
+						}
+					}
+				}
+
+				.table-item:last-child {
+					border: none;
+				}
+			}
+		}
+	}
+	.bg1 {
+		background: linear-gradient(180deg, rgba(99, 113, 235, 0.2) 0%, rgba(94, 108, 228, 0.08) 100%);
+	}
+
+	.bg2 {
+		background: linear-gradient(180deg, rgba(31, 113, 227, 0.2) 0%, rgba(31, 113, 227, 0.08) 100%);
+	}
+
+	.bg3 {
+		background: linear-gradient(180deg, rgba(0, 161, 205, 0.2) 0%, rgba(0, 161, 205, 0.08) 100%);
+	}
+	.btom{
+		flex-shrink: 1;
+		padding: 24rpx 32rpx;
+		.btn{
+			background-size: 100% 100% !important;
+			width: 100%;
+			height: 84rpx;
+			text-align: center;
+			line-height: 84rpx;
+			font-weight: bold;
+			font-size: 32rpx;
+			color: #0D121A;
+		}
+	}
+}
+</style>

+ 29 - 0
pages/login/index.vue

@@ -0,0 +1,29 @@
+<!-- 登录 -->
+<template>
+  <view class=''></view>
+</template>
+
+<script>
+export default {
+  components: {},
+    data() {
+      return {
+
+      };
+    },
+  computed: {},
+  watch: {},
+  methods: {
+
+  },
+  created() {
+
+  },
+  mounted() {
+
+  },
+}
+</script>
+<style lang='scss' scoped>
+
+</style>

+ 253 - 0
pages/order/index.vue

@@ -0,0 +1,253 @@
+<!-- 我的订单 -->
+<template>
+    <view class="order">
+        <view class="tabs">
+            <u-tabs :list="tabLists" lineWidth="40" lineHeight="8" :lineColor="`url(${lineBg}) 100% 100%`" :activeStyle="{
+                color: '#0D121A',
+                fontSize: '32rpx',
+                fontWeight: 'bold',
+                transform: 'scale(1.05)'
+            }" :inactiveStyle="{
+                color: '#9EA3B1',
+                fontSize: '28rpx',
+                transform: 'scale(1)'
+            }" itemStyle="padding-left: 18px; padding-right: 18px; height: 56px;" @click="tabsChange">
+            </u-tabs>
+        </view>
+        <view class="content">
+            <mescroll-body height="100%" class="custom-height" ref="mescrollRef" @init="mescrollInit" @down="downCallback"
+                :up="upOption" @up="upCallback">
+                <view class="order-list" v-if="arrList && arrList.length">
+                    <view class="tab-content" v-for="(item, index) in arrList" :key="index">
+                        <view class="order-top flex jcsb aic">
+                            <text class="name">{{ item.name }}</text>
+                            <text class="status">{{ item.statusName }}</text>
+                        </view>
+                        <view class="order-middle flex jcsb aifs">
+                            <view class="order-middle-left">
+                                <view>拍摄场景:6个</view>
+                                <view>交付周期:20天</view>
+                            </view>
+                            <view class="order-middle-right flex aic">
+                                <text class="right-label fs24">应付款</text>
+                                <text class="right-money fs28"><text class="fs24">¥</text>2500</text>
+                            </view>
+                        </view>
+                        <view class="order-bottom flex jcsb aic">
+                            <view class="order-bottom-left flex aic">
+                                <image :src="timeImg" class="time-img"></image>
+                                <text class="time fw400"><text class="fw500">{{ item.time }}</text>后自动取消</text>
+                            </view>
+                            <view class="order-bottom-right flex aic">
+                                <view class="btn btn1 p2">取消</view>
+                                <view class="btn btn2 p1">支付</view>
+                                <!-- <view class="btn3">寄样</view> -->
+                            </view>
+                        </view>
+                    </view>
+                </view>
+                <no-data :statusType="2" tipsText="暂无记录" v-if="!isInit && !arrList.length"></no-data>
+            </mescroll-body>
+        </view>
+
+    </view>
+</template>
+
+<script>
+import MescrollMixin from "@/plugins/mescroll/components/mescroll-uni/mescroll-mixins.js";
+import MescrollBody from '@/plugins/mescroll/components/mescroll-uni/mescroll-uni.vue';
+import { orderApi } from '@/apis/order.js';
+export default {
+    mixins: [MescrollMixin],
+    components: {
+        MescrollBody
+    },
+    data() {
+        return {
+            page: 1,
+            limit: 10,
+            lineBg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_line.png',
+            timeImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_time.png',
+            arrList: [
+                {
+                    status: 1,
+                    name: 'A类套餐(50条视频)',
+                    orderNo: '20200709123456789',
+                    time: '34:56',
+                    price: '100.00',
+                    status: 1,
+                    statusName: '待付款',
+                    statusColor: '#FF5E00',
+                    statusIcon: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_daifukuan%403x.png'
+
+                }
+            ],
+            isInit: false,
+            paymentStatus: 1,
+            tabLists: [
+                {
+                    name: '全部',
+                    value: 0
+                },{
+                    name: '待付款',
+                    value: 1
+                },{
+                    name: '拍摄中',
+                    value: 2
+                }, {
+                    name: '已完成',
+                    value: 3
+                }
+            ]
+        };
+    },
+    computed: {},
+    watch: {},
+    methods: {
+        tabsChange(item){
+            console.log(item,'item');
+        },
+        getList() {
+            let params = {
+                paymentStatus:this.paymentStatus,
+                page: 1,
+                limit: 10,
+            }
+            orderApi(params).then(res => {
+                console.log(res);
+            })
+        }
+    },
+    created() {
+
+    },
+    mounted() {
+
+    },
+}
+</script>
+<style lang='scss' scoped>
+.order {
+    height: 100vh;
+    background-color: #EDEFF5;
+    display: flex;
+    flex-direction: column;
+
+    .tabs {
+        background-color: #FFF;
+        padding: 0 28rpx 12rpx;
+    }
+
+    .content{
+        flex: 1;
+        overflow: hidden;
+    }
+    .order-list {
+        padding: 24rpx;
+
+        .tab-content {
+            padding: 40rpx 36rpx;
+            width: 100%;
+            // height: 296rpx;
+            background: #FFFFFF;
+            border-radius: 24rpx;
+
+            .order-top {
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+
+                .name {
+                    font-weight: bold;
+                    font-size: 32rpx;
+                    color: #0D121A;
+                }
+
+                .status {
+                    font-size: 28rpx;
+                    color: #FF7441;
+                }
+
+                .status2 {
+                    font-size: 28rpx;
+                    color: #0D121A;
+                }
+
+                .status3 {
+                    font-size: 28rpx;
+                    color: #9EA3B1;
+                }
+            }
+
+            .order-middle {
+                margin-top: 32rpx;
+                color: #0D121A;
+
+                .order-middle-left {
+                    font-weight: 400;
+                    font-size: 28rpx;
+                    color: #9EA3B1;
+                }
+
+                .order-middle-right {
+                    .right-label {
+                        font-weight: 400;
+                        margin-right: 8rpx;
+                    }
+
+                    .right-money {
+                        font-weight: 500;
+                    }
+                }
+            }
+
+            .order-bottom {
+                margin-top: 16rpx;
+
+                .order-bottom-left {
+                    .time-img {
+                        margin-right: 8rpx;
+                        width: 48rpx;
+                        height: 48rpx;
+                    }
+
+                    .time {
+                        color: #FF7441;
+                        font-size: 24rpx;
+                    }
+                }
+
+                .order-bottom-right {
+                    .p1 {
+                        padding: 18rpx 44rpx;
+                    }
+
+                    .p2 {
+                        padding: 16rpx 42rpx;
+                    }
+
+                    .btn {
+                        font-weight: 500;
+                        font-size: 28rpx;
+                        display: flex;
+                        justify-content: center;
+                        align-items: center;
+                        border-radius: 36rpx;
+                    }
+
+                    .btn1 {
+                        color: #0D121A;
+                        border: 3rpx solid #E2E4EB;
+                    }
+
+                    .btn2 {
+                        margin-left: 16rpx;
+                        background: #FF7441;
+                        color: #FFF;
+                    }
+                }
+            }
+        }
+    }
+}
+</style>

+ 738 - 0
pages/order/order_confirm.vue

@@ -0,0 +1,738 @@
+<!-- 订单确认页 -->
+<template>
+    <view class="order_confirm" :style="{ background: 'url(' + bgImg + ') #F4F5F9' }">
+        <view class="all">
+            <view class="title">您选择的{{ setData.packageName }}</view>
+            <view class="menu">
+                <view class="amounts flex jcsb">
+                    <view class="amontl">
+                        <view class="now flex aife">
+                            <text class="dw fs40">¥</text>
+                            <text class="num">{{ setData.currentPrice }}</text>
+                            <text class="unit">/条</text>
+                        </view>
+                        <view class="past flex aic">
+                            <text class="past_text fs28">原价</text>
+                            <text class="past_num fs32">¥{{ setData.originalPrice }}/条</text>
+                        </view>
+                    </view>
+                    <view class="amontr">
+                        <text class="num">{{ strip }}</text>
+                        <text class="unit">条</text>
+                    </view>
+                </view>
+                <view class="choose">
+                    <view v-for="(item, index) in setData.selectContents" :key="index"
+                        class="choose_item-box flex aic jcsb">
+                        <view class="choose_item flex aic">
+                            <image :src="guoxuanImg"></image>
+                            <text>{{ item.configKey }}可选 {{ item.count || 0 }} 个</text>
+                        </view>
+                        <view v-if="index == 0" class="choose_item-more flex aic" @click="toDetail">
+                            <text>查看更多</text>
+                            <image :src="moreBImg"></image>
+                        </view>
+                    </view>
+                </view>
+            </view>
+            <view class="choosion">
+                <view class="name">选择内容</view>
+                <view class="content">
+                    <view v-for="(item, index) in orderContents" :key="index" class="content_item flex aic jcsb"
+                        @click="chooseScene(item, index)">
+                        <view class="aic flex">
+                            <view class="item_label">{{ item.name }}</view>
+                            <view v-if="item.scenes.length > 0" class="item_num">{{ item.scenes.join('; ') }}</view>
+                            <view v-else class="item_text">{{ setData.packageName }}可选 {{ item.count || 0 }} 个{{
+                                item.name
+                            }}</view>
+                        </view>
+                        <image class="more_img" :src="moreImg"></image>
+                    </view>
+                </view>
+            </view>
+            <view class="choosion">
+                <view class="name">支付方式</view>
+                <view class="content">
+                    <view class="content_item flex aic jcsb">
+                        <view class="aic flex">
+                            <image class="icon_img" :src="weixinImg"></image>
+                            <view class="item_name">微信支付</view>
+                        </view>
+                        <image class="icon_img" :src="guoyuanImg"></image>
+                    </view>
+                </view>
+            </view>
+            <view class="choosion">
+                <view class="name">收件方式</view>
+                <view class="content">
+                    <view class="content_item flex aic jcsb">
+                        <view class="aic flex">
+                            <view class="item_label">收件人</view>
+                            <u--input v-model="formData.recipientName" placeholder="输入收件人姓名" fontSize="14"
+                                :customStyle="{ 'margin-left': '48rpx', 'padding': 0 }"></u--input>
+                        </view>
+                    </view>
+                    <view class="content_item flex aic jcsb">
+                        <view class="aic flex">
+                            <view class="item_label">联系电话</view>
+                            <u--input v-model="formData.contactPhone" placeholder="输入手机号码" fontSize="14"
+                                :customStyle="{ 'margin-left': '48rpx', 'padding': 0 }"></u--input>
+                        </view>
+                    </view>
+                    <view class="content_item flex aic jcsb">
+                        <view class="aic flex">
+                            <view class="item_label">收件地址</view>
+                            <u--input v-model="formData.email" placeholder="输入电子邮箱或网盘" fontSize="14"
+                                :customStyle="{ 'margin-left': '48rpx', 'padding': 0 }"></u--input>
+                        </view>
+                    </view>
+                </view>
+            </view>
+            <view class="choosion">
+                <view class="name">需求相关</view>
+                <view class="content">
+                    <view class="content_item flex aic jcsb">
+                        <view class="flex">
+                            <view class="item_label">以往案例</view>
+                            <u-upload :fileList="fileList1" @afterRead="afterRead" @delete="deletePic" name="1" multiple
+                                :maxCount="5">
+                                <view class="item_updata">
+                                    <image :src="upImg"></image>
+                                    <text>上传案例</text>
+                                </view>
+                            </u-upload>
+                        </view>
+                    </view>
+                    <view class="content_item flex aic jcsb">
+                        <view class="flex w100">
+                            <view class="item_label pt18">拍摄需求</view>
+                            <u--textarea v-model="formData.shootingRequirements" placeholder="简单描述您的拍摄需求" count
+                                maxlength="200"></u--textarea>
+                        </view>
+                    </view>
+                </view>
+            </view>
+            <view class="agree">
+                <text>点击“确认支付”,即代表您已阅读并同意</text>
+                <text class="agree_text">《用户购买协议》</text>
+            </view>
+        </view>
+        <view class="btom">
+            <view class="sum">
+                <text>应付款:</text>
+                <text class="money"><text class="unit">¥</text>{{ numMultiR }}</text>
+            </view>
+            <view class="btn" :style="{ background: 'url(' + buybtnBg + ')' }" @click="submitOrder">立即购买</view>
+        </view>
+        <u-popup ref="uPicker" mode="bottom" :loading="loading" :show="isSelect" round="12" @close="close" @open="open"
+            :customStyle="{ height: '100px' }">
+            <view class="popup_content safe-area-inset-bottom">
+                <view class="checkbox">
+                    <u-checkbox-group v-model="checked" placement="column" iconPlacement="right"
+                        @change="checkboxChange">
+                        <u-checkbox v-for="(item, index) in configData" :key="index" :label="item" :name="item"
+                            :customStyle="{ marginBottom: '24px' }" :disabled="isDisabled(item)"></u-checkbox>
+                    </u-checkbox-group>
+                </view>
+                <view class="btns flex aic jcsb">
+                    <view class="cancel" @click="close">取消</view>
+                    <view class="confirm" @click="confirm">确认</view>
+                </view>
+            </view>
+        </u-popup>
+
+    </view>
+</template>
+
+<script>
+import { getPackageById, getConfigById, upload, save } from '@/apis/index';
+import { baseUrl } from "@/config"
+import { numMulti } from '@/utils/calculate'
+export default {
+    components: {},
+    data() {
+        return {
+            type: '',
+            fileList1: [],
+            count: '', //可选数量
+            strip: '',//条数
+            packageId: '', //套餐id
+            setData: {}, // 套餐数据
+            configData: [], //选择内容数据
+            loading: false,
+            isSelect: false,
+            name: '',
+            phone: '',
+            checked: [], //多选
+            orderContents: [], //选中后的内容
+            configKey: '',//内容标题
+            bgImg: "https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/bg_top%403x.png",
+            guoxuanImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_guoxuan.png',
+            guoyuanImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_guoxuanyuan.png',
+            moreBImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_more_black.png',
+            moreImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_more%403x.png',
+            weixinImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_weixinzhifu.png',
+            upImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_up.png',
+            buybtnBg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_buy.png',
+            formData: {
+                recipientName: '',
+                contactPhone: '',
+                email: '',
+                shootingRequirements: '',
+            }
+        };
+    },
+    computed: {
+        numMultiR() {
+            return numMulti(Number(this.setData.currentPrice), Number(this.strip))
+        }
+    },
+    watch: {},
+    onLoad(options) {
+        if (options.id) this.packageId = options.id
+        if (options.strip) this.strip = options.strip
+        if (options.type) this.type = options.type
+        this.getPackage()
+    },
+    methods: {
+
+        toDetail(){
+            uni.navigateTo({
+                url: '/pages/index/detail?type='+this.type
+            })
+        },
+        // 判断当前选项是否应该禁用
+        isDisabled(value) {
+            // 已选中6个且当前选项未被选中时禁用
+            return this.checked.length >= this.count &&
+                !this.checked.includes(value)
+        },
+
+        // 选择变化时的处理
+        checkboxChange(values) {
+            if (values.length > this.count) {
+                // 如果超过6个,自动截断并提示
+                this.checked = values.slice(0, thsi.count);
+                uni.showToast({
+                    title: '最多只能选择6个选项',
+                    icon: 'none'
+                });
+            }
+        },
+
+        // 获取套餐内容
+        getPackage() {
+            getPackageById({ packageId: this.packageId }).then(res => {
+                this.setData = res
+                this.orderContents = res.selectContents.map(item => {
+                    return {
+                        name: item.configKey,
+                        configId: item.configId,
+                        count: item.count,
+                        scenes: []
+                    }
+                })
+                console.log('orderContents', this.orderContents);
+            })
+        },
+        open() {
+            console.log('打开');
+        },
+        // 选择内容
+        chooseScene(item, index) {
+            if (!item.count) {
+                uni.showToast({
+                    title: '无可选内容',
+                    icon: 'none'
+                });
+                return
+            }
+            this.loading = true
+            getConfigById({
+                packageId: this.packageId,
+                configId: item.configId
+            }).then(res => {
+                this.loading = false
+                this.count = item.count
+                console.log(this.checked, this.count, '数量');
+                this.configData = res
+                this.configKey = item.name
+                this.orderContents.map(itm => {
+                    if (itm.name === this.configKey && itm.scenes.length !== 0) {
+                        this.checked = itm.scenes
+                    }
+                })
+                this.isSelect = true
+
+            })
+        },
+        confirm() {
+            if (this.checked.length < this.count) {
+                uni.showToast({
+                    title: '至少选择' + this.count + '个选项',
+                    icon: 'none',
+                });
+                return
+            }
+            this.orderContents.map(item => {
+                if (item.name === this.configKey) {
+                    item.scenes = this.checked
+                }
+            })
+            this.checked = []
+            this.isSelect = false
+
+        },
+        close() {
+            this.isSelect = false
+            this.checked = []
+        },
+
+        // 删除图片
+        deletePic(event) {
+            this[`fileList${event.name}`].splice(event.index, 1);
+        },
+        // 新增图片
+        async afterRead(event) {
+            // 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
+
+            let lists = [].concat(event.file);
+            let fileListLen = this[`fileList${event.name}`].length;
+            lists.map((item) => {
+                this[`fileList${event.name}`].push({
+                    ...item,
+                    status: "uploading",
+                    message: "上传中",
+                });
+            });
+            for (let i = 0; i < lists.length; i++) {
+                const result = await this.uploadFilePromise(lists[i].url);
+                let item = this[`fileList${event.name}`][fileListLen];
+                this[`fileList${event.name}`].splice(
+                    fileListLen,
+                    1,
+                    Object.assign({
+                        // status: "success",
+                        // message: "",
+                        url: result,
+                    })
+                );
+                fileListLen++;
+            }
+            console.log(this.fileList1, 'fileList1');
+        },
+        uploadFilePromise(url) {
+            console.log(url, upload, 'url');
+            return new Promise((resolve, reject) => {
+                let a = uni.uploadFile({
+                    url: baseUrl + '/sys/oss/upload', // 仅为示例,非真实的接口地址
+                    filePath: url,
+                    name: "file",
+                    formData: {
+                        user: "test",
+                    },
+                    success: (res) => {
+                        let data = JSON.parse(res.data);
+                        setTimeout(() => {
+                            resolve(data.data.src);
+                        }, 1000);
+                    },
+                    fail: (err) => {
+                        console.log(err, 'err');
+                        reject(err);
+                    },
+                });
+            });
+        },
+
+        // 校验选择内容
+        checkContents() {
+            // for (const item of this.orderContents){
+            //     if (item.scenes.length === 0) {
+            //         uni.showToast({
+            //             title: '请选择' + item.name,
+            //             icon: 'none',
+            //         });
+            //         return false
+            //     }
+            // }
+            // // this.orderContents.map(item => {
+            // //     if (item.scenes.length === 0) {
+            // //         uni.showToast({
+            // //             title: '请选择' + item.name,
+            // //             icon: 'none',
+            // //         });
+            // //         return false
+            // //     }
+            // // })
+            // return true
+            for (const item of this.orderContents) {
+                if (!item.scenes || item.scenes.length === 0) {
+                    uni.showToast({
+                        title: `请选择${item.name}`,
+                        icon: 'none',
+                        duration: 2000
+                    });
+                    return false; // 校验失败
+                }
+            }
+            return true; // 校验成功
+        },
+
+        // 校验表单
+        checkForm() {
+            if (this.formData.recipientName === '' || this.formData.contactPhone === '' || this.formData.email === '') {
+                uni.showToast({
+                    title: '请填写完整收件方式',
+                    icon: 'none',
+                });
+                return false
+            }
+            return true; // 校验成功
+
+        },
+
+        // 提交订单
+        submitOrder() {
+            // this.checkContents()
+            if (!this.checkContents()) {
+                return; // 如果校验失败,停止提交
+            }
+            // this.checkForm()
+            if (!this.checkForm()) {
+                return; // 如果校验失败,停止提交
+            }
+            let params = {
+                packageId: this.packageId,
+                quantity: this.strip,
+                unitPrice: this.setData.currentPrice,
+                totalAmount: this.numMultiR,
+                packageName: this.setData.packageName,
+                ...this.formData,
+                caseFileUrls: this.fileList1.map(item => {
+                    return item.url
+                }),
+                orderContents: JSON.stringify(this.orderContents),
+            }
+            save(params).then(res => {
+                console.log(res, 'res');
+            })
+        }
+
+
+    },
+    created() {
+
+    },
+    mounted() {
+
+    },
+}
+</script>
+<style lang="scss">
+uni-toast {
+    z-index: 99999 !important;
+}
+</style>
+<style lang='scss' scoped>
+.order_confirm {
+    width: 100%;
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    display: flex;
+    flex-direction: column;
+    background-size: contain !important;
+    background-repeat: no-repeat !important;
+    background-position: top;
+
+    .all {
+        padding: 0 24rpx;
+        flex: 1;
+        overflow: auto;
+
+        .title {
+            font-weight: 500;
+            font-size: 36rpx;
+            color: #0D121A;
+            margin: 48rpx 0 40rpx;
+            text-align: center;
+        }
+
+        .menu {
+            width: 100%;
+            background: linear-gradient(180deg, #E4F8FF 0%, #FFFFFF 100%);
+            border-radius: 24rpx;
+            border: 2rpx solid #FFFFFF;
+            padding: 64rpx 48rpx;
+
+            .amounts {
+                .now {
+                    font-size: 32rpx;
+                    color: #0D121A;
+
+                    .dw {
+                        line-height: 40rpx;
+                    }
+
+                    .limit {
+                        margin-left: 24rpx;
+                        width: 72rpx;
+                        height: 40rpx;
+                        background: linear-gradient(144deg, #00C1FF 0%, #2FFFFC 100%);
+                        border-radius: 8rpx;
+                        font-weight: 500;
+                        font-size: 24rpx;
+                        color: #0D121A;
+                        text-align: center;
+                        line-height: 40rpx;
+                    }
+                }
+
+                .num {
+                    font-size: 72rpx;
+                    line-height: 56rpx;
+                    font-weight: bold;
+                }
+
+                .unit {
+                    margin-left: 8px;
+                }
+
+                .past {
+                    color: #0D121A;
+                    margin-top: 34rpx;
+
+                    .past_text {
+                        margin-right: 8rpx;
+                    }
+                }
+            }
+
+            .choose {
+                margin-top: 64rpx;
+
+                .choose_item-box {
+                    margin-top: 16rpx;
+
+                    image {
+                        width: 48rpx;
+                        height: 48rpx;
+                        margin-right: 24rpx;
+                    }
+
+                    text {
+                        color: #0D121A;
+                        font-size: 28rpx;
+                    }
+                }
+
+                .choose_item-more {
+                    text {
+                        font-size: 28rpx;
+                        color: #0D121A;
+                    }
+
+                    image {
+                        width: 32rpx;
+                        height: 32rpx;
+                        margin-left: 12rpx;
+                    }
+                }
+
+                .choose_item-box:nth-child(1) {
+                    margin-top: 0;
+                }
+            }
+        }
+
+        .choosion {
+            .name {
+                font-weight: 400;
+                font-size: 28rpx;
+                color: #9EA3B1;
+                margin: 32rpx 0 20rpx 32rpx;
+            }
+
+            .content {
+                background-color: #FFFFFF;
+                border-radius: 24rpx;
+
+                .content_item {
+                    padding: 36rpx 32rpx;
+
+                    .item_updata {
+                        width: 168rpx;
+                        height: 168rpx;
+                        background: #F4F5F9;
+                        border-radius: 16rpx;
+                        display: flex;
+                        flex-direction: column;
+                        align-items: center;
+                        justify-content: center;
+                        // margin-left: 48rpx;
+
+                        image {
+                            width: 56rpx;
+                            height: 56rpx;
+                        }
+
+                        text {
+                            color: #9EA3B1;
+                            font-weight: 400;
+                            font-size: 24rpx;
+                        }
+                    }
+
+                    .item_label {
+                        font-weight: 500;
+                        font-size: 28rpx;
+                        color: #0D121A;
+                        min-width: 150rpx;
+                        white-space: nowrap;
+                    }
+
+                    .item_num {
+                        font-size: 28rpx;
+                        margin-left: 48rpx;
+
+                    }
+
+                    .item_text {
+                        font-size: 28rpx;
+                        color: #9EA3B1;
+                        margin-left: 48rpx;
+                    }
+
+                    .item_name {
+                        font-size: 28rpx;
+                        color: #0D121A;
+                        margin-left: 16rpx;
+                    }
+
+                    .more_img {
+                        width: 32rpx;
+                        height: 32rpx;
+                        flex-shrink: 0;
+                    }
+
+                    .icon_img {
+                        width: 48rpx;
+                        height: 48rpx;
+                    }
+
+                    input {
+                        height: 0;
+                    }
+                }
+
+                .content_item_input {}
+            }
+        }
+
+        .agree {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            font-size: 24rpx;
+            color: #9EA3B1;
+            padding: 48rpx 0 32rpx;
+
+            .agree_text {
+                color: #0D121A;
+            }
+        }
+    }
+
+    .btom {
+        background: #FFFFFF;
+        flex-shrink: 1;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: 16rpx 24rpx;
+        padding-bottom: calc(16rpx + constant(safe-area-inset-bottom)) !important;
+        padding-bottom: calc(16rpx + env(safe-area-inset-bottom)) !important;
+
+        .sum {
+            font-weight: 400;
+            font-size: 30rpx;
+            color: #0D121A;
+
+            .money {
+                font-size: 56rpx;
+                color: #0D121A;
+            }
+
+            .unit {
+                font-size: 40rpx;
+            }
+        }
+
+        .btn {
+            width: 312rpx;
+            height: 84rpx;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            font-weight: bold;
+            font-size: 32rpx;
+            color: #0D121A;
+            background-size: 100% 100% !important;
+
+        }
+    }
+
+    .pt18 {
+        padding-top: 18rpx;
+    }
+
+    .popup_content {
+        padding: 32rpx;
+        max-height: 1000rpx;
+        display: flex;
+        flex-direction: column;
+
+        .checkbox {
+            flex: 1;
+            overflow: auto;
+        }
+
+        .btns {
+            flex-shrink: 1;
+        }
+    }
+
+    .btns {
+        padding: 0 0 16rpx;
+        color: #0D121A;
+
+        .cancel {
+            width: 45%;
+            height: 84rpx;
+            background: #FFFFFF;
+            border-radius: 16rpx;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            border: #9EA3B1 1px solid;
+        }
+
+        .confirm {
+            width: 45%;
+            height: 84rpx;
+            border-radius: 16rpx;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            background: linear-gradient(144deg, #00C1FF 0%, #2FFFFC 100%);
+        }
+    }
+
+    .u-popup {
+        flex: 0;
+        z-index: 999;
+    }
+}
+</style>

+ 250 - 0
pages/order/success.vue

@@ -0,0 +1,250 @@
+<!-- 支付成功 -->
+<template>
+  <view class="success" :style="{ background: 'url(' + bgImg + ') #F4F5F9' }">
+    <view class="success_status">
+      <view class="status flex aic">
+        <image class="pay_img" :src="payImg"></image>
+        <view class="status_text">支付成功</view>
+      </view>
+      <view class="status_tip">套餐购买成功 请查看账单明细</view>
+    </view>
+    <view class="success_content">
+        <view class="success_info">
+            <view class="">
+                <view class="success_item flex jcsb aic">
+                    <view class="flex aic">
+                        <view class="label">收件地址</view>
+                        <text class="bold">123</text>
+                    </view>
+                    <view class="success_btn" @click="copyText()">复制</view>
+                  </view>
+
+                    <view class="success_item flex aic">
+                        <view class="label">收件人</view>
+                        <text>234</text>
+                    </view>
+            </view>
+        </view>
+        <view class="line" :style="{ background: `url(${successImg}) no-repeat` }">
+            <view class="line-circle"></view>
+        </view>
+        <view class="detail">
+            <view class="detail_item flex aic jcsb">
+                <text>订单金额</text>
+                <view class="datas flex">
+                    <text>2500元</text>
+                </view>
+            </view>
+            <view class="detail_item flex aic jcsb">
+                <text>套餐名称</text>
+                <view class="datas flex">
+                    <text>A类套餐</text>
+                    <view class="copy">详情</view>
+                </view>
+            </view>
+            <view class="detail_item flex aic jcsb">
+                <text>套餐内容</text>
+                <view class="datas flex">
+                    <text>50条 (每条15-20s)</text>
+                </view>
+            </view>
+            <view class="detail_item flex aic jcsb">
+                <text>支付方式</text>
+                <view class="datas flex">
+                    <text>微信支付</text>
+                </view>
+            </view>
+            <view class="detail_item flex aic jcsb">
+                <text>订单编号</text>
+                <view class="datas flex">
+                    <text>12346789</text><view class="copy" @click="copyText()">复制</view>
+                </view>
+            </view>
+            <view class="detail_item flex aic jcsb">
+                <text>订单时间</text>
+                <view class="datas flex">
+                    <text>123</text>
+                </view>
+            </view>
+        </view>
+    </view>
+    <view class="btns">
+      <view class="bind" @click="bindCard">查看详情</view>
+      <view class="back" @click="toHome">返回首页</view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { copy } from "@/utils/utils";
+export default {
+  components: {},
+    data() {
+      return {
+        bgImg: "https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/bg_top%403x.png",
+        payImg: "https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_paysuccess.png",
+        successImg: 'https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/img_quekou.png'
+      };
+    },
+  computed: {},
+  watch: {},
+  methods: {
+    // 复制
+    copyText(num){
+      copy(num)
+    }
+  },
+  created() {
+
+  },
+  mounted() {
+
+  },
+}
+</script>
+<style lang='scss' scoped>
+.success{
+    width: 100%;
+    position: absolute;
+    top: 0;
+    bottom: 0;
+    display: flex;
+    flex-direction: column;
+    background-size: contain !important;
+    background-repeat: no-repeat !important;
+    background-position: top;
+    padding: 0 24rpx;
+  .success_status{
+    margin-top: 64rpx;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    .pay_img{
+      width: 48rpx;
+      height: 48rpx;
+      margin-left: 16rpx;
+    }
+    .status_text{
+      font-weight: 500;
+      font-size: 40rpx;
+      color: #0D121A;
+    }
+    .status_tip{
+      font-weight: 400;
+      font-size: 28rpx;
+      color: #9EA3B1;
+      margin-top: 20rpx;
+    }
+  }
+  .success_content{
+        border-radius: 32rpx;
+        overflow: hidden;
+        margin-top: 64rpx;
+        .success_info{
+            background-color: #fff;
+            padding: 36rpx 32rpx 8rpx;
+            .success_item{
+                margin-bottom: 20rpx;
+                .label{
+                    min-width: 120rpx;
+                    font-weight: 400;
+                    font-size: 28rpx;
+                    color: #9EA3B1;
+                    margin-right: 16rpx;
+                }
+                text{
+                    font-size: 32rpx;
+                    color: #0D121A;
+                }
+
+            }
+            
+        }
+        .success_btn{
+          width: 80rpx;
+          height: 40rpx;
+          font-weight: 500;
+          background: #F4F5F9;
+          border-radius: 48rpx;
+          font-size: 24rpx;
+          color: #0D121A;
+          text-align: center;
+          line-height: 40rpx;
+        }
+        .line{
+            width: 100%;
+            height: 40rpx;
+            background-size: cover !important;
+            padding: 0 32rpx;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            .line-circle{
+                width: 100%;
+                border: 2rpx dashed #EDEDED;
+                height: 2rpx;
+            }
+        }
+        .detail{
+            background-color: #fff;
+            padding: 32rpx 32rpx 40rpx;
+            .detail_item {
+                margin-bottom: 36rpx;
+                text{
+                    white-space: nowrap;
+                    font-weight: 400;
+                    font-size: 28rpx;
+                    color: #0D121A;
+                }
+                .datas{
+                    .copy{
+                        width: 80rpx;
+                        height: 40rpx;
+                        background: #F4F5F9;
+                        border-radius: 48rpx;
+                        font-weight: 500;
+                        font-size: 24rpx;
+                        color: #061714;
+                        text-align: center;
+                        line-height: 40rpx;
+                        margin-left: 16rpx;
+                    }
+                }
+            }
+            .detail_item:nth-last-child(1){
+                margin-bottom: 0;
+            }
+        }
+        .success_title{
+            color: #9DA3A2;
+        }
+    }
+    .btns{
+        padding: 96rpx 32rpx;
+        view{
+            width: 100%;
+            height: 84rpx;
+            // border-radius: 48rpx;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            font-size: 32rpx;
+        }
+        .bind{
+            color: #061714;
+            margin-bottom: 32rpx;
+            font-weight: 500rpx;
+            background: linear-gradient( 140deg, #246AF1 0%, #2F98EF 100%);
+        }
+        .back{
+            font-weight: bold;
+            background: #FFFFFF;
+            // border: 4rpx solid #E7EAEA;
+            color: #061714;
+        }
+    }
+    .ml16{
+      margin-left: 16rpx;
+    }
+}
+</style>

+ 398 - 0
pages/user/index.vue

@@ -0,0 +1,398 @@
+<!-- 我的 -->
+<template>
+  <view class="my" :style="{ background: 'url(' + bgImg + ') #F4F5F9' }">
+    <view class="title">
+      <image :src="logoImg" mode="heightFix"></image>
+    </view>
+    <view class="news">
+      <image :src="avatar"></image>
+      <view v-if="name" class="phone">{{name}}</view>
+      <view v-else class="phone" @click="toLogin()">点击登录</view>
+    </view>
+    <view class="content">
+      <view class="order-wrap">
+        <!-- <view class="unpay">
+          <view class="left">
+            <image :src="tongzhi"></image>
+            <view class="tip">您有1笔订单待付款<text>09:34</text>后取消</view>
+          </view>
+          <view class="right">去付款</view>
+        </view> -->
+        <view class="order">
+          <view class="order_top">
+            <text class="mytt">我的订单</text>
+            <view class="myall" @click="toOrder">
+              <text class="all">全部</text>
+              <image class="more" :src="moreImg"></image>
+            </view>
+          </view>
+          <view class="order-box">
+            <view class="order-box-item" v-for="(item, index) in orderList" :key="index">
+              <image :src="item.icon"></image>
+              <text class="name">{{ item.label }}</text>
+            </view>
+          </view>
+        </view>
+      </view>
+      <view class="cantact">
+        <view class="canl">
+          <image :src="cantact"></image>
+          <text>联系客服</text>
+        </view>
+        <image class="more" :src="moreImg"></image>
+      </view>
+      <view class="outline">退出登录</view>
+    </view>
+  </view>
+</template>
+
+<script>
+import config from '@/config'
+import { loginWx } from '@/apis/user.js'
+export default {
+  components: {},
+  data() {
+    return {
+      name: '',
+      code: '',
+      bgImg: "https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/bg_top%403x.png",
+      logoImg: "https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/img_logo%403x.png",
+      tongzhi: "https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_tongzhi%403x.png",
+      moreImg: "https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_more%403x.png",
+      cantact: "https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_kefurexian%403x.png",
+      avatar: '',
+      orderList: [
+        {
+          value: 1,
+          label: '全部',
+          icon: "https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_daifukuan%403x.png"
+        },
+        {
+          value: 2,
+          label: '待付款',
+          icon: "https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_daifukuan%403x.png"
+        },
+        {
+          value: 3,
+          label: '拍摄中',
+          icon: "https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_daifukuan%403x.png"
+        },
+        {
+          value: 4,
+          label: '已完成',
+          icon: "https://ovp-shop.oss-cn-hangzhou.aliyuncs.com/static/9dshop/ic_diankaguanli%403x.png"
+        }
+      ]
+    };
+  },
+  computed: {},
+  watch: {},
+  onLoad() {
+
+
+    // if(code !== null || code !== "") {
+    //     this.getOpenidAndUserinfo(code)
+    // }
+  },
+  onShow() {
+    this.changeTabbar()
+  },
+  methods: {
+
+    toLogin() {
+      let isWX = this.isWechat()
+      if (isWX) {
+        let code = this.getUrlCode('code')
+        uni.showToast({
+          title: code,
+        })
+        if (code !== null && code !== undefined) {
+          this.code = code
+          this.getOpenidAndUserinfo(code)
+        } else {
+          this.getCode()
+        }
+      }
+    },
+    // 判断是否为微信环境
+    isWechat() {
+      var ua = navigator.userAgent.toLowerCase()
+
+      var isWXWork = ua.match(/wxwork/i) == 'wxwork'
+
+      var isWeixin = !isWXWork && ua.match(/MicroMessenger/i) == 'micromessenger'
+      return isWeixin
+
+    },
+    getCode() {
+      const appid = config.appid
+      let loc_href = encodeURIComponent(window.location.href);
+      let mainstate = Math.random()
+      let wxUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${loc_href}&response_type=code&scope=snsapi_base&state=${mainstate}#wechat_redirect`;
+      window.location.href = wxUrl;
+    },
+    getUrlCode(name) {
+      return new URL(window.location.href).searchParams.get(name + '')
+    },
+
+    getOpenidAndUserinfo(code){
+      loginWx({
+				code: code
+			}).then(res => {
+				const {
+					name,
+					customerVo,
+					token,
+				} = res
+				uni.setStorageSync('openId', openId)
+				const userInfo = uni.getStorageSync('userInfo')
+				if (token) { // 登录成功 后更新用户信息
+
+					uni.setStorageSync('accessToken', token)
+					uni.setStorageSync('name', name)
+          this.name = name
+					// uni.setStorageSync('userInfo', customerVo)
+					// 设置默认地址
+					// addressFind().then(addrList => {
+					// 	const defaultAddrIndex = addrList.findIndex(item => item.defaultFlag === 1);
+					// 	defaultAddrIndex !== -1 && uni.setStorageSync('currentBuyerAddr', addrList[
+					// 		defaultAddrIndex]);
+					// })
+				}
+				if (!token) { // 未登录过
+					// const channel = getUrlParamsNormal('channel')
+					// if (channel === 'qrcode') { // 扫码进入的页面
+					// 	uni.redirectTo({
+					// 		url: '/pages/card/cardPhysical/index'
+					// 	})
+					// } else { // 进入登录
+					// 	uni.redirectTo({
+					// 		url: '/pages/login/index'
+					// 	})
+					// }
+					uni.switchTab({
+						url: '/pages/user/index'
+					})
+				}
+			})
+    },
+    changeTabbar() {
+      uni.setTabBarStyle({
+        color: '#9EA3B1',
+        selectedColor: '#0D121A',
+        backgroundColor: '#FFF'
+      })
+    },
+    toOrder() {
+      this.$router.push({
+        path: '/pages/order/index'
+      })
+    }
+  },
+  created() {
+
+  },
+  mounted() {
+
+  },
+}
+</script>
+<style lang='scss' scoped>
+.my {
+  width: 100%;
+  // height: 100%;
+  display: flex;
+  flex-direction: column;
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  background-size: contain !important;
+  background-repeat: no-repeat !important;
+  background-position: top;
+
+  .title {
+    padding: 16rpx 32rpx;
+
+    image {
+      height: 56rpx;
+    }
+  }
+
+  .news {
+    padding: 64rpx 48rpx 0;
+    display: flex;
+    align-items: center;
+
+    image {
+      width: 104rpx;
+      height: 104rpx;
+      background-color: aqua;
+      border-radius: 50%;
+    }
+
+    .phone {
+      font-weight: bold;
+      font-size: 44rpx;
+      color: #0D121A;
+      margin-left: 32rpx;
+    }
+  }
+
+  .content {
+    padding: 0 24rpx;
+    margin-top: 40rpx;
+    box-sizing: content-box;
+  }
+
+  .more {
+    width: 32rpx;
+    height: 32rpx;
+  }
+
+  .order-wrap {
+    width: 100%;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+
+    .unpay {
+      // box-sizing: border-box;
+      width: 638rpx;
+      background: #FFEEE2;
+      border-radius: 24rpx 24rpx 0rpx 0rpx;
+      border: 2rpx solid #FFFEFD;
+      padding: 16rpx 18rpx 16rpx 16rpx;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+
+      .left {
+        display: flex;
+        align-items: center;
+
+        image {
+          width: 48rpx;
+          height: 48rpx;
+        }
+
+        .tip {
+          font-size: 24rpx;
+          color: #FF7441;
+          margin-left: 14rpx;
+
+          text {
+            font-weight: bold;
+          }
+        }
+      }
+
+      .right {
+        width: 120rpx;
+        height: 44rpx;
+        font-weight: 500;
+        background: #FF7441;
+        border-radius: 24rpx;
+        font-size: 24rpx;
+        color: #FFFFFF;
+        text-align: center;
+        line-height: 44rpx;
+      }
+    }
+
+    .order {
+      padding: 32rpx 32rpx 40rpx;
+      width: 100%;
+      box-sizing: border-box;
+      height: 260rpx;
+      background: linear-gradient(180deg, #DEF5FD 0%, #FFFFFF 100%);
+      border-radius: 32rpx;
+      border: 2rpx solid #FFFFFF;
+
+      .order_top {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+
+        .mytt {
+          font-weight: 500;
+          font-size: 32rpx;
+          color: #0D121A;
+        }
+
+        .myall {
+          font-weight: 400;
+          font-size: 26rpx;
+          color: #0D121A;
+          display: flex;
+          align-items: center;
+
+          .all {
+            margin-right: 8rpx;
+
+          }
+        }
+      }
+
+      .order-box {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-top: 48rpx;
+
+        .order-box-item {
+          display: flex;
+          flex-direction: column;
+          align-items: center;
+          width: 128rpx;
+
+          image {
+            width: 64rpx;
+            height: 64rpx;
+          }
+
+          .name {
+            font-size: 24rpx;
+            color: #0D121A;
+            margin-top: 16rpx;
+          }
+        }
+      }
+    }
+  }
+
+  .cantact {
+    width: 100%;
+    height: 112rpx;
+    background: #FFFFFF;
+    border-radius: 24rpx;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 24rpx 32rpx;
+    margin-top: 24rpx;
+
+    .canl {
+      display: flex;
+      align-items: center;
+
+      image {
+        margin-right: 24rpx;
+        width: 64rpx;
+        height: 64rpx;
+      }
+    }
+  }
+
+  .outline {
+    width: 622rpx;
+    height: 96rpx;
+    border-radius: 36rpx;
+    border: 4rpx solid #E2E4EB;
+    font-size: 28rpx;
+    color: #0D121A;
+    text-align: center;
+    line-height: 96rpx;
+    margin: 48rpx auto;
+  }
+}
+</style>

+ 6 - 0
plugins/mescroll/changelog.md

@@ -0,0 +1,6 @@
+## 1.3.7(2021-04-13)
+1. 新增`mescroll-swiper-sticky.vue`的示例, 轮播吸顶菜单导航  
+2. 新增`mescroll-empty.vue`的示例, 单独使用空布局组件  
+3. 简化tabs在具体项目中的使用,并简化对应的示例  
+4. mescroll-uni 支持动态禁止滚动的属性 disableScroll (注: mescroll-body不支持)  
+-by 小瑾同学

+ 19 - 0
plugins/mescroll/components/mescroll-body/mescroll-body.css

@@ -0,0 +1,19 @@
+.mescroll-body {
+	position: relative; /* 下拉刷新区域相对自身定位 */
+	height: auto; /* 不可固定高度,否则overflow:hidden导致无法滑动; 同时使设置的最小高生效,实现列表不满屏仍可下拉*/
+	overflow: hidden; /* 当有元素写在mescroll-body标签前面时,可遮住下拉刷新区域 */
+	box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
+}
+
+/* 使sticky生效: 父元素不能overflow:hidden或者overflow:auto属性 */
+.mescroll-body.mescorll-sticky{
+	overflow: unset !important
+}
+
+/* 适配 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);
+	}
+}

+ 400 - 0
plugins/mescroll/components/mescroll-body/mescroll-body.vue

@@ -0,0 +1,400 @@
+<template>
+	<view 
+	class="mescroll-body mescroll-render-touch" 
+	:class="{'mescorll-sticky': sticky}"
+	:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" 
+	@touchstart="wxsBiz.touchstartEvent" 
+	@touchmove="wxsBiz.touchmoveEvent" 
+	@touchend="wxsBiz.touchendEvent" 
+	@touchcancel="wxsBiz.touchendEvent"
+	:change:prop="wxsBiz.propObserver"
+	:prop="wxsProp"
+	>
+		<!-- 状态栏 -->
+		<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+		
+		<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
+			<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+			<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
+			<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+				<view class="downwarp-content">
+					<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
+					<view class="downwarp-tip">{{downText}}</view>
+				</view>
+			</view>
+	
+			<!-- 列表内容 -->
+			<slot></slot>
+
+			<!-- 空布局 -->
+			<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+			<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+			<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+			<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+				<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+				<view v-show="upLoadType===1">
+					<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+					<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+				</view>
+				<!-- 无数据 -->
+				<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+			</view>
+		</view>
+		
+		<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
+		<!-- #ifdef H5 -->
+		<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+		<!-- #endif -->
+		
+		<!-- 适配iPhoneX -->
+		<view v-if="safearea" class="mescroll-safearea"></view>
+		
+		<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+		
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from "../mescroll-uni/wxs/renderjs.js";
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	// 引入mescroll-uni.js,处理核心逻辑
+	import MeScroll from "../mescroll-uni/mescroll-uni.js";
+	// 引入全局配置
+	import GlobalOption from "../mescroll-uni/mescroll-uni-option.js";
+	// 引入国际化工具类
+	import mescrollI18n from '../mescroll-uni/mescroll-i18n.js';
+	// 引入回到顶部组件
+	import MescrollTop from "../mescroll-uni/components/mescroll-top.vue";
+	// 引入兼容wxs(含renderjs)写法的mixins
+	import WxsMixin from "../mescroll-uni/wxs/mixins.js";
+	
+	/**
+	 * mescroll-body 基于page滚动的下拉刷新和上拉加载组件, 支持嵌套原生组件, 性能好
+	 * @property {Object} down 下拉刷新的参数配置
+	 * @property {Object} up 上拉加载的参数配置
+	 * @property {Object} i18n 国际化的参数配置
+	 * @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+	 * @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+	 * @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true
+	 * @property {String, Number} height 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+	 * @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效)
+	 * @property {Boolean} sticky 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法隐藏
+	 * @event {Function} init 初始化完成的回调 
+	 * @event {Function} down 下拉刷新的回调
+	 * @event {Function} up 上拉加载的回调 
+	 * @event {Function} emptyclick 点击empty配置的btnText按钮回调
+	 * @event {Function} topclick 点击回到顶部的按钮回调
+	 * @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效)
+	 * @example <mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-body>
+	 */
+	export default {
+		name: 'mescroll-body',
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		props: {
+			down: Object,
+			up: Object,
+			i18n: Object,
+			top: [String, Number],
+			topbar: [Boolean, String],
+			bottom: [String, Number],
+			safearea: Boolean,
+			height: [String, Number],
+			bottombar:{
+				type: Boolean,
+				default: true
+			},
+			sticky: Boolean
+		},
+		data() {
+			return {
+				mescroll: {optDown:{},optUp:{}}, // mescroll实例
+				downHight: 0, //下拉刷新: 容器高度
+				downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				windowHeight: 0, // 可使用窗口的高度
+				windowBottom: 0, // 可使用窗口的底部位置
+				statusBarHeight: 0 // 状态栏高度
+			};
+		},
+		computed: {
+			// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			minHeight(){
+				return this.toPx(this.height || '100%') + 'px'
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			padTop() {
+				return this.numTop + 'px';
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom);
+			},
+			padBottom() {
+				return this.numBottom + 'px';
+			},
+			// 是否为重置下拉的状态
+			isDownReset() {
+				return this.downLoadType === 3 || this.downLoadType === 4;
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : '';
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 是否在加载中
+			isDownLoading(){
+				return this.downLoadType === 3
+			},
+			// 旋转的角度
+			downRotate(){
+				return 'rotate(' + 360 * this.downRate + 'deg)'
+			},
+			// 文本提示
+			downText(){
+				if(!this.mescroll) return ""; // 避免头条小程序初始化时报错
+				switch (this.downLoadType){
+					case 1: return this.mescroll.optDown.textInOffset;
+					case 2: return this.mescroll.optDown.textOutOffset;
+					case 3: return this.mescroll.optDown.textLoading;
+					case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
+					default: return this.mescroll.optDown.textInOffset;
+				}
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num) {
+				if (typeof num === 'string') {
+					if (num.indexOf('px') !== -1) {
+						if (num.indexOf('rpx') !== -1) {
+							// "10rpx"
+							num = num.replace('rpx', '');
+						} else if (num.indexOf('upx') !== -1) {
+							// "10upx"
+							num = num.replace('upx', '');
+						} else {
+							// "10px"
+							return Number(num.replace('px', ''));
+						}
+					} else if (num.indexOf('%') !== -1) {
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace('%', '')) / 100;
+						return this.windowHeight * rate;
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0;
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll);
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					beforeEndDownScroll(mescroll){
+						vm.downLoadType = 4; 
+						return mescroll.optDown.beforeEndDelay // 延时结束的时长
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset
+							if(vm.downLoadType === 4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll);
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+					}
+				}
+			};
+			
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是page的scroll,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				if(typeof y === 'string'){
+					// 滚动到指定view (y为css选择器)
+					setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick
+						let selector;
+						if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
+							selector = '#'+y // 不带#和. 则默认为id选择器
+						}else{
+							selector = y
+							// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
+							if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
+								selector = y.split('>>>')[1].trim()
+							}
+							// #endif
+						}
+						uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
+							if (rect) {
+								let top = rect.top
+								top += vm.mescroll.getScrollTop()
+								uni.pageScrollTo({
+									scrollTop: top,
+									duration: t
+								})
+							} else{
+								console.error(selector + ' does not exist');
+							}
+						}).exec()
+					},30)
+				} else{
+					// 滚动到指定位置 (y必须为数字)
+					uni.pageScrollTo({
+						scrollTop: y,
+						duration: t
+					})
+				}
+			});
+
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	};
+</script>
+
+<style>
+	@import "../mescroll-body/mescroll-body.css";
+	@import "../mescroll-uni/components/mescroll-down.css";
+	@import "../mescroll-uni/components/mescroll-up.css";
+</style>

+ 47 - 0
plugins/mescroll/components/mescroll-diy/beibei/components/mescroll-down.css

@@ -0,0 +1,47 @@
+/*下拉刷新--标语*/
+.mescroll-downwarp .downwarp-slogan{
+	display: block;
+	width: 420rpx;
+	height: 168rpx;
+	margin: auto;
+}
+/*下拉刷新--向下进度动画*/
+.mescroll-downwarp .downwarp-progress{
+	display: inline-block;
+	width: 40rpx;
+	height: 40rpx;
+	border: none;
+	margin: auto;
+	background-size: contain;
+	background-repeat: no-repeat;
+	background-position: center;
+	background-image: url(https://www.mescroll.com/img/beibei/mescroll-progress.png);
+	transition: all 300ms;
+}
+/*下拉刷新--进度条*/
+.mescroll-downwarp .downwarp-loading{
+	display: inline-block;
+	width: 32rpx;
+	height: 32rpx;
+	border-radius: 50%;
+	border: 2rpx solid #FF8095;
+	border-bottom-color: transparent;
+}
+/*下拉刷新--吉祥物*/
+.mescroll-downwarp .downwarp-mascot{
+	position: absolute;
+	right: 16rpx;
+	bottom: 0;
+	width: 100rpx;
+	height: 100rpx;
+	background-size: contain;
+	background-repeat: no-repeat;
+	animation: animMascot .6s steps(1,end) infinite;
+}
+@keyframes animMascot {
+	0% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb1.png)}
+	25% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb2.png)}
+	50% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb3.png)}
+	75% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb4.png)}
+	100% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb1.png)}
+}

+ 39 - 0
plugins/mescroll/components/mescroll-diy/beibei/components/mescroll-down.vue

@@ -0,0 +1,39 @@
+<!-- 下拉刷新区域 -->
+<template>
+	<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}">
+		<view class="downwarp-content">
+			<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
+			<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
+			<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
+			<view class="downwarp-mascot"></view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		option: Object , // down的配置项
+		type: Number // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption(){
+			return this.option || {}
+		},
+		// 是否在加载中
+		isDownLoading(){
+			return this.type === 3
+		},
+		// 旋转的角度
+		downRotate(){
+			return this.type === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
+		}
+	}
+};
+</script>
+
+<style>
+@import "../../../mescroll-uni/components/mescroll-down.css";
+@import "./mescroll-down.css";
+</style>

+ 360 - 0
plugins/mescroll/components/mescroll-diy/beibei/mescroll-body.vue

@@ -0,0 +1,360 @@
+<template>
+	<view 
+		class="mescroll-body mescroll-render-touch" 
+		:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" 
+		:class="{'mescorll-sticky': sticky}"
+		@touchstart="wxsBiz.touchstartEvent" 
+		@touchmove="wxsBiz.touchmoveEvent" 
+		@touchend="wxsBiz.touchendEvent" 
+		@touchcancel="wxsBiz.touchendEvent"
+		:change:prop="wxsBiz.propObserver"
+		:prop="wxsProp"
+		>
+		
+		<!-- 状态栏 -->
+		<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+
+		<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
+			<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+			<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
+			<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+				<view class="downwarp-content">
+					<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
+					<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
+					<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
+					<view class="downwarp-mascot"></view>
+				</view>
+			</view>
+						
+			<!-- 列表内容 -->
+			<slot></slot>
+
+			<!-- 空布局 -->
+			<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+			<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+			<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+			<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+				<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+				<view v-show="upLoadType===1">
+					<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+					<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+				</view>
+				<!-- 无数据 -->
+				<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+			</view>
+		</view>
+		
+		<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
+		<!-- #ifdef H5 -->
+		<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+		<!-- #endif -->
+		
+		<!-- 适配iPhoneX -->
+		<view v-if="safearea" class="mescroll-safearea"></view>
+		
+		<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	import MeScroll from '../../mescroll-uni/mescroll-uni.js';
+	import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
+	import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
+	import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
+	import GlobalOption from './mescroll-uni-option.js';
+	
+	export default {
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: null, // mescroll实例
+				downHight: 0, //下拉刷新: 容器高度
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				windowHeight: 0, // 可使用窗口的高度
+				windowBottom: 0, // 可使用窗口的底部位置
+				statusBarHeight: 0 // 状态栏高度
+			};
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			i18n: Object, // 国际化的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
+				type: Boolean,
+				default: true
+			},
+			sticky: Boolean // 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法会隐藏
+		},
+		computed: {
+			// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			minHeight(){
+				return this.toPx(this.height || '100%') + 'px'
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			padTop() {
+				return this.numTop + 'px';
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom);
+			},
+			padBottom() {
+				return this.numBottom + 'px';
+			},
+			// 是否为重置下拉的状态
+			isDownReset() {
+				return this.downLoadType === 3 || this.downLoadType === 4;
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : '';
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 是否在加载中
+			isDownLoading(){
+				return this.downLoadType === 3
+			},
+			// 旋转的角度
+			downRotate(){
+				return this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num) {
+				if (typeof num === 'string') {
+					if (num.indexOf('px') !== -1) {
+						if (num.indexOf('rpx') !== -1) {
+							// "10rpx"
+							num = num.replace('rpx', '');
+						} else if (num.indexOf('upx') !== -1) {
+							// "10upx"
+							num = num.replace('upx', '');
+						} else {
+							// "10px"
+							return Number(num.replace('px', ''));
+						}
+					} else if (num.indexOf('%') !== -1) {
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace('%', '')) / 100;
+						return this.windowHeight * rate;
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0;
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll);
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset
+							if(vm.downLoadType === 4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll);
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+					}
+				}
+			};
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是page的scroll,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				if(typeof y === 'string'){
+					// 滚动到指定view (y为css选择器)
+					setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick
+						let selector;
+						if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
+							selector = '#'+y // 不带#和. 则默认为id选择器
+						}else{
+							selector = y
+							// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
+							if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
+								selector = y.split('>>>')[1].trim()
+							}
+							// #endif
+						}
+						uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
+							if (rect) {
+								let top = rect.top
+								top += vm.mescroll.getScrollTop()
+								uni.pageScrollTo({
+									scrollTop: top,
+									duration: t
+								})
+							} else{
+								console.error(selector + ' does not exist');
+							}
+						}).exec()
+					},30)
+				} else{
+					// 滚动到指定位置 (y必须为数字)
+					uni.pageScrollTo({
+						scrollTop: y,
+						duration: t
+					})
+				}
+			});
+
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	};
+</script>
+
+<style>
+	@import "../../mescroll-body/mescroll-body.css";
+	@import "../../mescroll-uni/components/mescroll-down.css";
+	@import "../../mescroll-uni/components/mescroll-up.css";
+	@import "./components/mescroll-down.css";
+</style>

+ 49 - 0
plugins/mescroll/components/mescroll-diy/beibei/mescroll-uni-option.js

@@ -0,0 +1,49 @@
+// mescroll-uni和mescroll-body 的全局配置
+const GlobalOption = {
+	down: {
+		// 其他down的配置参数也可以写,这里只展示了常用的配置:
+		offset: uni.upx2px(140), // 在列表顶部,下拉大于140upx,松手即可触发下拉刷新的回调
+		native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+	},
+	up: {
+		// 其他up的配置参数也可以写,这里只展示了常用的配置:
+		offset: 150, // 距底部多远时,触发upCallback
+		toTop: {
+			// 回到顶部按钮,需配置src才显示
+			src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入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: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
+		}
+	},
+	// 国际化配置
+	i18n: {
+		// 中文
+		zh: {
+			up: {
+				textLoading: '加载中 ...', // 加载中的提示文本
+				textNoMore: '-- END --', // 没有更多数据的提示文本
+				empty: {
+					tip: '~ 暂无相关数据 ~' // 空提示
+				}
+			}
+		},
+		// 英文
+		en: {
+			up: {
+				textLoading: 'loading ...',
+				textNoMore: '-- END --',
+				empty: {
+					tip: '~ absolutely empty ~'
+				}
+			}
+		}
+	}
+}
+
+export default GlobalOption

+ 437 - 0
plugins/mescroll/components/mescroll-diy/beibei/mescroll-uni.vue

@@ -0,0 +1,437 @@
+<template>
+	<view class="mescroll-uni-warp">
+		<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
+			<view class="mescroll-uni-content mescroll-render-touch"
+			@touchstart="wxsBiz.touchstartEvent" 
+			@touchmove="wxsBiz.touchmoveEvent" 
+			@touchend="wxsBiz.touchendEvent" 
+			@touchcancel="wxsBiz.touchendEvent"
+			:change:prop="wxsBiz.propObserver"
+			:prop="wxsProp">
+						
+				<!-- 状态栏 -->
+				<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+							
+				<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
+					<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+					<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
+					<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+						<view class="downwarp-content">
+							<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
+							<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
+							<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
+							<view class="downwarp-mascot"></view>
+						</view>
+					</view>
+
+					<!-- 列表内容 -->
+					<slot></slot>
+
+					<!-- 空布局 -->
+					<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+					<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+					<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+					<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+						<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+						<view v-show="upLoadType===1">
+							<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+							<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+						</view>
+						<!-- 无数据 -->
+						<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+					</view>
+				</view>
+				
+				<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
+				<!-- #ifdef H5 -->
+				<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+				<!-- #endif -->
+				
+				<!-- 适配iPhoneX -->
+				<view v-if="safearea" class="mescroll-safearea"></view>
+			</view>
+		</scroll-view>
+
+		<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	import MeScroll from '../../mescroll-uni/mescroll-uni.js';
+	import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
+	import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
+	import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
+	import GlobalOption from './mescroll-uni-option.js';
+	
+	export default {
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: null, // mescroll实例
+				viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
+				downHight: 0, //下拉刷新: 容器高度
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				scrollTop: 0, // 滚动条的位置
+				scrollAnim: false, // 是否开启滚动动画
+				windowTop: 0, // 可使用窗口的顶部位置
+				windowBottom: 0, // 可使用窗口的底部位置
+				windowHeight: 0, // 可使用窗口的高度
+				statusBarHeight: 0 // 状态栏高度
+			}
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			i18n: Object, // 国际化的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			fixed: { // 是否通过fixed固定mescroll的高度, 默认true
+				type: Boolean,
+				default: true
+			},
+			height: [String, Number], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
+				type: Boolean,
+				default: true
+			},
+			disableScroll: Boolean // 是否禁止滚动
+		},
+		computed: {
+			// 是否使用fixed定位 (当height有值,则不使用)
+			isFixed(){
+				return !this.height && this.fixed
+			},
+			// mescroll的高度
+			scrollHeight(){
+				if (this.isFixed) {
+					return "auto"
+				} else if(this.height){
+					return this.toPx(this.height) + 'px'
+				}else{
+					return "100%"
+				}
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			fixedTop() {
+				return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
+			},
+			padTop() {
+				return !this.isFixed ? this.numTop + 'px' : 0
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom)
+			},
+			fixedBottom() {
+				return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
+			},
+			padBottom() {
+				return !this.isFixed ? this.numBottom + 'px' : 0
+			},
+			// 是否为重置下拉的状态
+			isDownReset(){
+				return this.downLoadType===3 || this.downLoadType===4
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : ''
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : '' // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 列表是否可滑动
+			scrollable(){
+				if(this.disableScroll) return false
+				return this.downLoadType===0 || this.isDownReset
+			},
+			// 是否在加载中
+			isDownLoading(){
+				return this.downLoadType === 3
+			},
+			// 旋转的角度
+			downRotate(){
+				return this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num){
+				if(typeof num === "string"){
+					if (num.indexOf('px') !== -1) {
+						if(num.indexOf('rpx') !== -1) { // "10rpx"
+							num = num.replace('rpx', '');
+						} else if(num.indexOf('upx') !== -1) { // "10upx"
+							num = num.replace('upx', '');
+						} else { // "10px"
+							return Number(num.replace('px', ''))
+						}
+					}else if (num.indexOf('%') !== -1){
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace("%","")) / 100
+						return this.windowHeight * rate
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0
+			},
+			//注册列表滚动事件,用于下拉刷新和上拉加载
+			scroll(e) {
+				this.mescroll.scroll(e.detail, () => {
+					this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动
+				})
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll)
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			},
+			// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页)
+			setClientHeight() {
+				if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
+					this.isExec = true; // 避免多次获取
+					this.$nextTick(() => { // 确保dom已渲染
+						this.getClientInfo(data=>{
+							this.isExec = false;
+							if (data) {
+								this.mescroll.setClientHeight(data.height);
+							} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次
+								this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
+								setTimeout(() => {
+									this.setClientHeight()
+								}, this.clientNum * 100)
+							}
+						})
+					})
+				}
+			},
+			// 获取滚动区域的信息
+			getClientInfo(success){
+				let query = uni.createSelectorQuery();
+				// #ifndef MP-ALIPAY || MP-DINGTALK
+				query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
+				// #endif
+				let view = query.select('#' + this.viewId);
+				view.boundingClientRect(data => {
+					success(data)
+				}).exec();
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downResetTimer && clearTimeout(vm.downResetTimer)
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
+							if(vm.downLoadType===4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll)
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+						// 更新容器的高度 (多mescroll的情况)
+						vm.setClientHeight()
+					}
+				}
+			}
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption);
+			vm.mescroll.viewId = vm.viewId; // 附带id
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+			
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if(sys.windowTop) vm.windowTop = sys.windowTop;
+			if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是scrollview,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
+				if(typeof y === 'string'){
+					// 小程序不支持slot里面的scroll-into-view, 统一使用计算的方式实现
+					vm.getClientInfo(function(rect){
+						let mescrollTop = rect.top // mescroll到顶部的距离
+						let selector;
+						if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
+							selector = '#'+y // 不带#和. 则默认为id选择器
+						}else{
+							selector = y
+							// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
+							if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
+								selector = y.split('>>>')[1].trim()
+							}
+							// #endif
+						}
+						uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
+							if (rect) {
+								let curY = vm.mescroll.getScrollTop()
+								let top = rect.top - mescrollTop
+								top += curY
+								if(!vm.isFixed) top -= vm.numTop
+								vm.scrollTop = curY;
+								vm.$nextTick(function() {
+									vm.scrollTop = top
+								})
+							} else{
+								console.error(selector + ' does not exist');
+							}
+						}).exec()
+					})
+					return;
+				}
+				let curY = vm.mescroll.getScrollTop()
+				if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡
+					vm.scrollTop = curY;
+					vm.$nextTick(function() {
+						vm.scrollTop = y
+					})
+				} else {
+					vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t
+						vm.scrollTop = step
+					}, t)
+				}
+			})
+			
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		mounted() {
+			// 设置容器的高度
+			this.setClientHeight()
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	}
+</script>
+
+<style>
+	@import "../../mescroll-uni/mescroll-uni.css";
+	@import "../../mescroll-uni/components/mescroll-down.css";
+	@import "../../mescroll-uni/components/mescroll-up.css";
+	@import "./components/mescroll-down.css";
+</style>

+ 44 - 0
plugins/mescroll/components/mescroll-diy/xinlang/components/mescroll-down.css

@@ -0,0 +1,44 @@
+/*下拉刷新--上下箭头*/
+.mescroll-downwarp .downwarp-arrow {
+	display: inline-block;
+	width: 20px;
+	height: 20px;
+	margin: 10px;
+	background-image: url(https://www.mescroll.com/img/xinlang/mescroll-arrow.png);
+	background-size: contain;
+	vertical-align: middle;
+	transition: all 300ms;
+}
+
+/*下拉刷新--旋转进度条*/
+.mescroll-downwarp .downwarp-progress{
+	width: 36px;
+	height: 36px;
+	border: none;
+	margin: auto;
+	background-size: contain;
+	animation: progressRotate 0.6s steps(6, start) infinite;
+}
+@keyframes progressRotate {
+	0% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
+	}
+	16% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png);
+	}
+	32% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png);
+	}
+	48% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png);
+	}
+	64% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png);
+	}
+	80% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png);
+	}
+	100% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
+	}
+}

+ 53 - 0
plugins/mescroll/components/mescroll-diy/xinlang/components/mescroll-down.vue

@@ -0,0 +1,53 @@
+<!-- 下拉刷新区域 -->
+<template>
+	<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}">
+		<view class="downwarp-content">
+			<view v-if="isDownLoading" class="downwarp-progress"></view>
+			<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view>
+			<view class="downwarp-tip">{{ downText }}</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		option: Object, // down的配置项
+		type: Number // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption() {
+			return this.option || {};
+		},
+		// 是否在加载中
+		isDownLoading() {
+			return this.type === 3;
+		},
+		// 旋转的角度
+		downRotate() {
+			return this.type === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
+		},
+		// 文本提示
+		downText() {
+			switch (this.type) {
+				case 1:
+					return this.mOption.textInOffset;
+				case 2:
+					return this.mOption.textOutOffset;
+				case 3:
+					return this.mOption.textLoading;
+				case 4:
+					return this.mOption.textLoading;
+				default:
+					return this.mOption.textInOffset;
+			}
+		}
+	}
+};
+</script>
+
+<style>
+@import '../../../mescroll-uni/components/mescroll-down.css';
+@import './mescroll-down.css';
+</style>

+ 32 - 0
plugins/mescroll/components/mescroll-diy/xinlang/components/mescroll-up.css

@@ -0,0 +1,32 @@
+/*上拉加载--旋转进度条*/
+.mescroll-upwarp .upwarp-progress {
+	width: 36px;
+	height: 36px;
+	border: none;
+	margin: auto;
+	background-size: contain;
+	animation: progressRotate 0.6s steps(6, start) infinite;
+}
+@keyframes progressRotate {
+	0% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
+	}
+	16% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png);
+	}
+	32% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png);
+	}
+	48% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png);
+	}
+	64% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png);
+	}
+	80% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png);
+	}
+	100% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
+	}
+}

+ 40 - 0
plugins/mescroll/components/mescroll-diy/xinlang/components/mescroll-up.vue

@@ -0,0 +1,40 @@
+<!-- 上拉加载区域 -->
+<template>
+	<view class="mescroll-upwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}">
+		<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+		<view v-show="isUpLoading">
+			<view class="upwarp-progress mescroll-rotate"></view>
+			<view class="upwarp-tip">{{ mOption.textLoading }}</view>
+		</view>
+		<!-- 无数据 -->
+		<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		option: Object, // up的配置项
+		type: Number // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption() {
+			return this.option || {};
+		},
+		// 加载中
+		isUpLoading() {
+			return this.type === 1;
+		},
+		// 没有更多了
+		isUpNoMore() {
+			return this.type === 2;
+		}
+	}
+};
+</script>
+
+<style>
+@import '../../../mescroll-uni/components/mescroll-up.css';
+@import './mescroll-up.css';
+</style>

+ 380 - 0
plugins/mescroll/components/mescroll-diy/xinlang/mescroll-body.vue

@@ -0,0 +1,380 @@
+<template>
+	<view 
+		class="mescroll-body mescroll-render-touch" 
+		:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" 
+		:class="{'mescorll-sticky': sticky}"
+		@touchstart="wxsBiz.touchstartEvent" 
+		@touchmove="wxsBiz.touchmoveEvent" 
+		@touchend="wxsBiz.touchendEvent" 
+		@touchcancel="wxsBiz.touchendEvent"
+		:change:prop="wxsBiz.propObserver"
+		:prop="wxsProp"
+		>
+		
+		<!-- 状态栏 -->
+		<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+		
+		<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
+			<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+			<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
+			<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+				<view class="downwarp-content">
+					<view v-if="isDownLoading" class="downwarp-progress"></view>
+					<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view>
+					<view class="downwarp-tip">{{ downText }}</view>
+				</view>
+			</view>
+			
+			<!-- 列表内容 -->
+			<slot></slot>
+
+			<!-- 空布局 -->
+			<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+			<!-- 上拉加载区域 (下拉刷新时不显示,支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+			<!-- <mescroll-up v-if="mescroll.optUp.use && downLoadType !== 3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+			<view class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+				<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+				<view v-show="upLoadType===1">
+					<view class="upwarp-progress mescroll-rotate"></view>
+					<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+				</view>
+				<!-- 无数据 -->
+				<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+			</view>
+		</view>
+		
+		<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
+		<!-- #ifdef H5 -->
+		<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+		<!-- #endif -->
+		
+		<!-- 适配iPhoneX -->
+		<view v-if="safearea" class="mescroll-safearea"></view>
+		
+		<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+		
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	import MeScroll from '../../mescroll-uni/mescroll-uni.js';
+	import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
+	import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
+	import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
+	import GlobalOption from './mescroll-uni-option.js';
+
+	export default {
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: null, // mescroll实例
+				downHight: 0, //下拉刷新: 容器高度
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				windowHeight: 0, // 可使用窗口的高度
+				windowBottom: 0, // 可使用窗口的底部位置
+				statusBarHeight: 0 // 状态栏高度
+			};
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			i18n: Object, // 国际化的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
+				type: Boolean,
+				default: true
+			},
+			sticky: Boolean // 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法会隐藏
+		},
+		computed: {
+			// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			minHeight(){
+				return this.toPx(this.height || '100%') + 'px'
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			padTop() {
+				return this.numTop + 'px';
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom);
+			},
+			padBottom() {
+				return this.numBottom + 'px';
+			},
+			// 是否为重置下拉的状态
+			isDownReset() {
+				return this.downLoadType === 3 || this.downLoadType === 4;
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : '';
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 是否在加载中
+			isDownLoading() {
+				return this.downLoadType === 3;
+			},
+			// 旋转的角度
+			downRotate() {
+				return this.downLoadType === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
+			},
+			// 文本提示
+			downText() {
+				if(!this.mescroll) return "";
+				switch (this.downLoadType) {
+					case 1:
+						return this.mescroll.optDown.textInOffset;
+					case 2:
+						return this.mescroll.optDown.textOutOffset;
+					case 3:
+						return this.mescroll.optDown.textLoading;
+					case 4:
+						return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
+					default:
+						return this.mescroll.optDown.textInOffset;
+				}
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num) {
+				if (typeof num === 'string') {
+					if (num.indexOf('px') !== -1) {
+						if (num.indexOf('rpx') !== -1) {
+							// "10rpx"
+							num = num.replace('rpx', '');
+						} else if (num.indexOf('upx') !== -1) {
+							// "10upx"
+							num = num.replace('upx', '');
+						} else {
+							// "10px"
+							return Number(num.replace('px', ''));
+						}
+					} else if (num.indexOf('%') !== -1) {
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace('%', '')) / 100;
+						return this.windowHeight * rate;
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0;
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll);
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					beforeEndDownScroll(mescroll){
+						vm.downLoadType = 4; 
+						return mescroll.optDown.beforeEndDelay // 延时结束的时长
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset
+							if(vm.downLoadType === 4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll);
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+					}
+				}
+			};
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+			
+			// 因为使用的是page的scroll,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				if(typeof y === 'string'){
+					// 滚动到指定view (y为css选择器)
+					setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick
+						let selector;
+						if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
+							selector = '#'+y // 不带#和. 则默认为id选择器
+						}else{
+							selector = y
+							// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
+							if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
+								selector = y.split('>>>')[1].trim()
+							}
+							// #endif
+						}
+						uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
+							if (rect) {
+								let top = rect.top
+								top += vm.mescroll.getScrollTop()
+								uni.pageScrollTo({
+									scrollTop: top,
+									duration: t
+								})
+							} else{
+								console.error(selector + ' does not exist');
+							}
+						}).exec()
+					},30)
+				} else{
+					// 滚动到指定位置 (y必须为数字)
+					uni.pageScrollTo({
+						scrollTop: y,
+						duration: t
+					})
+				}
+			});
+			
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	};
+</script>
+
+<style>
+	@import "../../mescroll-uni/mescroll-uni.css";
+	@import "../../mescroll-uni/components/mescroll-down.css";
+	@import "../../mescroll-uni/components/mescroll-up.css";
+	@import "./components/mescroll-down.css";
+	@import "./components/mescroll-up.css";
+</style>

+ 64 - 0
plugins/mescroll/components/mescroll-diy/xinlang/mescroll-uni-option.js

@@ -0,0 +1,64 @@
+// 全局配置
+// mescroll-body 和 mescroll-uni 通用
+const GlobalOption = {
+	down: {
+		// 其他down的配置参数也可以写,这里只展示了常用的配置:
+		offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
+		native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+	},
+	up: {
+		// 其他up的配置参数也可以写,这里只展示了常用的配置:
+		offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
+		toTop: {
+			// 回到顶部按钮,需配置src才显示
+			src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入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: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
+		}
+	},
+	// 国际化配置
+	i18n: {
+		// 中文
+		zh: {
+			down: {
+				textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
+				textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
+				textLoading: '加载中 ...', // 加载中的提示文本
+				textSuccess: '加载成功', // 加载成功的文本
+				textErr: '加载失败', // 加载失败的文本
+			},
+			up: {
+				textLoading: '加载中 ...', // 加载中的提示文本
+				textNoMore: '-- END --', // 没有更多数据的提示文本
+				empty: {
+					tip: '~ 空空如也 ~' // 空提示
+				}
+			}
+		},
+		// 英文
+		en: {
+			down: {
+				textInOffset: 'drop down refresh',
+				textOutOffset: 'release updates',
+				textLoading: 'loading ...',
+				textSuccess: 'loaded successfully',
+				textErr: 'loading failed'
+			},
+			up: {
+				textLoading: 'loading ...',
+				textNoMore: '-- END --',
+				empty: {
+					tip: '~ absolutely empty ~'
+				}
+			}
+		}
+	}
+}
+
+export default GlobalOption

+ 462 - 0
plugins/mescroll/components/mescroll-diy/xinlang/mescroll-uni.vue

@@ -0,0 +1,462 @@
+<template>
+	<view class="mescroll-uni-warp">
+		<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll"  :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
+			<view class="mescroll-uni-content mescroll-render-touch"
+			@touchstart="wxsBiz.touchstartEvent" 
+			@touchmove="wxsBiz.touchmoveEvent" 
+			@touchend="wxsBiz.touchendEvent" 
+			@touchcancel="wxsBiz.touchendEvent"
+			:change:prop="wxsBiz.propObserver"
+			:prop="wxsProp">
+			
+				<!-- 状态栏 -->
+				<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+				
+				<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
+					<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+					<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
+					<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+						<view class="downwarp-content">
+							<view v-if="isDownLoading" class="downwarp-progress"></view>
+							<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view>
+							<view class="downwarp-tip">{{ downText }}</view>
+						</view>
+					</view>
+
+					<!-- 列表内容 -->
+					<slot></slot>
+
+					<!-- 空布局 -->
+					<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+					<!-- 上拉加载区域 (下拉刷新时不显示,支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+					<!-- <mescroll-up v-if="mescroll.optUp.use && downLoadType !== 3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+					<view class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+						<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+						<view v-show="upLoadType===1">
+							<view class="upwarp-progress mescroll-rotate"></view>
+							<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+						</view>
+						<!-- 无数据 -->
+						<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+					</view>
+				</view>
+				
+				<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
+				<!-- #ifdef H5 -->
+				<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+				<!-- #endif -->
+				
+				<!-- 适配iPhoneX -->
+				<view v-if="safearea" class="mescroll-safearea"></view>
+			
+			</view>
+		</scroll-view>
+
+		<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+		
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	import MeScroll from '../../mescroll-uni/mescroll-uni.js';
+	import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
+	import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
+	import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
+	import GlobalOption from './mescroll-uni-option.js';
+	
+	export default {
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: null, // mescroll实例
+				viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
+				downHight: 0, //下拉刷新: 容器高度
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				scrollTop: 0, // 滚动条的位置
+				scrollAnim: false, // 是否开启滚动动画
+				windowTop: 0, // 可使用窗口的顶部位置
+				windowBottom: 0, // 可使用窗口的底部位置
+				windowHeight: 0, // 可使用窗口的高度
+				statusBarHeight: 0 // 状态栏高度
+			}
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			i18n: Object, // 国际化的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			fixed: { // 是否通过fixed固定mescroll的高度, 默认true
+				type: Boolean,
+				default: true
+			},
+			height: [String, Number], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
+				type: Boolean,
+				default: true
+			},
+			disableScroll: Boolean // 是否禁止滚动
+		},
+		computed: {
+			// 是否使用fixed定位 (当height有值,则不使用)
+			isFixed(){
+				return !this.height && this.fixed
+			},
+			// mescroll的高度
+			scrollHeight(){
+				if (this.isFixed) {
+					return "auto"
+				} else if(this.height){
+					return this.toPx(this.height) + 'px'
+				}else{
+					return "100%"
+				}
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			fixedTop() {
+				return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
+			},
+			padTop() {
+				return !this.isFixed ? this.numTop + 'px' : 0
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom)
+			},
+			fixedBottom() {
+				return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
+			},
+			padBottom() {
+				return !this.isFixed ? this.numBottom + 'px' : 0
+			},
+			// 是否为重置下拉的状态
+			isDownReset(){
+				return this.downLoadType===3 || this.downLoadType===4
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : ''
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : '' // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 列表是否可滑动
+			scrollable(){
+				if(this.disableScroll) return false
+				return this.downLoadType===0 || this.isDownReset
+			},
+			// 是否在加载中
+			isDownLoading() {
+				return this.downLoadType === 3;
+			},
+			// 旋转的角度
+			downRotate() {
+				return this.downLoadType === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
+			},
+			// 文本提示
+			downText() {
+				if(!this.mescroll) return "";
+				switch (this.downLoadType) {
+					case 1:
+						return this.mescroll.optDown.textInOffset;
+					case 2:
+						return this.mescroll.optDown.textOutOffset;
+					case 3:
+						return this.mescroll.optDown.textLoading;
+					case 4:
+						return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
+					default:
+						return this.mescroll.optDown.textInOffset;
+				}
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num){
+				if(typeof num === "string"){
+					if (num.indexOf('px') !== -1) {
+						if(num.indexOf('rpx') !== -1) { // "10rpx"
+							num = num.replace('rpx', '');
+						} else if(num.indexOf('upx') !== -1) { // "10upx"
+							num = num.replace('upx', '');
+						} else { // "10px"
+							return Number(num.replace('px', ''))
+						}
+					}else if (num.indexOf('%') !== -1){
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace("%","")) / 100
+						return this.windowHeight * rate
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0
+			},
+			//注册列表滚动事件,用于下拉刷新和上拉加载
+			scroll(e) {
+				this.mescroll.scroll(e.detail, () => {
+					this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动
+				})
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll)
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			},
+			// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页)
+			setClientHeight() {
+				if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
+					this.isExec = true; // 避免多次获取
+					this.$nextTick(() => { // 确保dom已渲染
+						this.getClientInfo(data=>{
+							this.isExec = false;
+							if (data) {
+								this.mescroll.setClientHeight(data.height);
+							} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次
+								this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
+								setTimeout(() => {
+									this.setClientHeight()
+								}, this.clientNum * 100)
+							}
+						})
+					})
+				}
+			},
+			// 获取滚动区域的信息
+			getClientInfo(success){
+				let query = uni.createSelectorQuery();
+				// #ifndef MP-ALIPAY || MP-DINGTALK
+				query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
+				// #endif
+				let view = query.select('#' + this.viewId);
+				view.boundingClientRect(data => {
+					success(data)
+				}).exec();
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					beforeEndDownScroll(mescroll){
+						vm.downLoadType = 4; 
+						return mescroll.optDown.beforeEndDelay // 延时结束的时长
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downResetTimer && clearTimeout(vm.downResetTimer)
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
+							if(vm.downLoadType===4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll)
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+						// 更新容器的高度 (多mescroll的情况)
+						vm.setClientHeight()
+					}
+				}
+			}
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({
+				'down': vm.down,
+				'up': vm.up
+			})) // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption);
+			vm.mescroll.viewId = vm.viewId; // 附带id
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+			
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if(sys.windowTop) vm.windowTop = sys.windowTop;
+			if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是scrollview,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
+				if(typeof y === 'string'){
+					// 小程序不支持slot里面的scroll-into-view, 统一使用计算的方式实现
+					vm.getClientInfo(function(rect){
+						let mescrollTop = rect.top // mescroll到顶部的距离
+						let selector;
+						if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
+							selector = '#'+y // 不带#和. 则默认为id选择器
+						}else{
+							selector = y
+							// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
+							if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
+								selector = y.split('>>>')[1].trim()
+							}
+							// #endif
+						}
+						uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
+							if (rect) {
+								let curY = vm.mescroll.getScrollTop()
+								let top = rect.top - mescrollTop
+								top += curY
+								if(!vm.isFixed) top -= vm.numTop
+								vm.scrollTop = curY;
+								vm.$nextTick(function() {
+									vm.scrollTop = top
+								})
+							} else{
+								console.error(selector + ' does not exist');
+							}
+						}).exec()
+					})
+					return;
+				}
+				let curY = vm.mescroll.getScrollTop()
+				if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡
+					vm.scrollTop = curY;
+					vm.$nextTick(function() {
+						vm.scrollTop = y
+					})
+				} else {
+					vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t
+						vm.scrollTop = step
+					}, t)
+				}
+			})
+			
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		mounted() {
+			// 设置容器的高度
+			this.setClientHeight()
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	}
+</script>
+
+<style>
+	@import "../../mescroll-uni/mescroll-uni.css";
+	@import "../../mescroll-uni/components/mescroll-down.css";
+	@import "../../mescroll-uni/components/mescroll-up.css";
+	@import "./components/mescroll-down.css";
+	@import "./components/mescroll-up.css";
+</style>

+ 116 - 0
plugins/mescroll/components/mescroll-empty/mescroll-empty.vue

@@ -0,0 +1,116 @@
+<!--空布局:
+遵循easycom规范, 可作为独立的组件, 不使用mescroll的页面也能使用:
+<mescroll-empty v-if="isShowEmpty" :option="optEmpty" @emptyclick="emptyClick"></mescroll-empty>
+-->
+<template>
+	<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" :style="{ 'z-index': option.zIndex, top: option.top }">
+		<view> <image v-if="icon" class="empty-icon" :src="icon" mode="widthFix" /> </view>
+		<view v-if="tip" class="empty-tip">{{ tip }}</view>
+		<view v-if="btnText" class="empty-btn" @click="emptyClick">{{ btnText }}</view>
+	</view>
+</template>
+
+<script>
+// 引入全局配置
+import GlobalOption from '../mescroll-uni/mescroll-uni-option.js';
+// 引入国际化工具类
+import mescrollI18n from '../mescroll-uni/mescroll-i18n.js';
+export default {
+	props: {
+		// empty的配置项: 默认为GlobalOption.up.empty
+		option: {
+			type: Object,
+			default() {
+				return {};
+			}
+		}
+	},
+	// 使用computed获取配置,用于支持option的动态配置
+	computed: {
+		// 图标
+		icon() {
+			if (this.option.icon != null) { // 此处不使用短路求值, 用于支持传空串不显示图标
+				return this.option.icon
+			} else{
+				let i18nType = mescrollI18n.getType() // 国际化配置
+				if (this.option.i18n) {
+					return this.option.i18n[i18nType].icon
+				} else{
+					return GlobalOption.i18n[i18nType].up.empty.icon || GlobalOption.up.empty.icon
+				}
+			}
+		},
+		// 文本提示
+		tip() {
+			if (this.option.tip != null) { // 支持传空串不显示文本提示
+				return this.option.tip
+			} else{
+				let i18nType = mescrollI18n.getType() // 国际化配置
+				if (this.option.i18n) {
+					return this.option.i18n[i18nType].tip
+				} else{
+					return GlobalOption.i18n[i18nType].up.empty.tip || GlobalOption.up.empty.tip
+				}
+			}
+		},
+		// 按钮文本
+		btnText() {
+			if (this.option.i18n) {
+				let i18nType = mescrollI18n.getType() // 国际化配置
+				return this.option.i18n[i18nType].btnText
+			} else{
+				return this.option.btnText
+			}
+		}
+	},
+	methods: {
+		// 点击按钮
+		emptyClick() {
+			this.$emit('emptyclick');
+		}
+	}
+};
+</script>
+
+<style>
+/* 无任何数据的空布局 */
+.mescroll-empty {
+	box-sizing: border-box;
+	width: 100%;
+	padding: 100rpx 50rpx;
+	text-align: center;
+}
+
+.mescroll-empty.empty-fixed {
+	z-index: 99;
+	position: absolute; /*transform会使fixed失效,最终会降级为absolute */
+	top: 100rpx;
+	left: 0;
+}
+
+.mescroll-empty .empty-icon {
+	width: 280rpx;
+	height: 280rpx;
+}
+
+.mescroll-empty .empty-tip {
+	margin-top: 20rpx;
+	font-size: 24rpx;
+	color: gray;
+}
+
+.mescroll-empty .empty-btn {
+	display: inline-block;
+	margin-top: 40rpx;
+	min-width: 200rpx;
+	padding: 18rpx;
+	font-size: 28rpx;
+	border: 1rpx solid #e04b28;
+	border-radius: 60rpx;
+	color: #e04b28;
+}
+
+.mescroll-empty .empty-btn:active {
+	opacity: 0.75;
+}
+</style>

+ 55 - 0
plugins/mescroll/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);
+	}
+}

+ 47 - 0
plugins/mescroll/components/mescroll-uni/components/mescroll-down.vue

@@ -0,0 +1,47 @@
+<!-- 下拉刷新区域 -->
+<template>
+	<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}">
+		<view class="downwarp-content">
+			<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mOption.textColor, 'transform':downRotate}"></view>
+			<view class="downwarp-tip">{{downText}}</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		option: Object , // down的配置项
+		type: Number, // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
+		rate: Number // 下拉比率 (inOffset: rate<1; outOffset: rate>=1)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption(){
+			return this.option || {}
+		},
+		// 是否在加载中
+		isDownLoading(){
+			return this.type === 3
+		},
+		// 旋转的角度
+		downRotate(){
+			return 'rotate(' + 360 * this.rate + 'deg)'
+		},
+		// 文本提示
+		downText(){
+			switch (this.type){
+				case 1: return this.mOption.textInOffset;
+				case 2: return this.mOption.textOutOffset;
+				case 3: return this.mOption.textLoading;
+				case 4: return this.mOption.textLoading;
+				default: return this.mOption.textInOffset;
+			}
+		}
+	}
+};
+</script>
+
+<style>
+@import "./mescroll-down.css";
+</style>

+ 83 - 0
plugins/mescroll/components/mescroll-uni/components/mescroll-top.vue

@@ -0,0 +1,83 @@
+<!-- 回到顶部的按钮 -->
+<template>
+	<image
+		v-if="mOption.src"
+		class="mescroll-totop"
+		:class="[value ? 'mescroll-totop-in' : 'mescroll-totop-out', {'mescroll-totop-safearea': mOption.safearea}]"
+		:style="{'z-index':mOption.zIndex, 'left': left, 'right': right, 'bottom':addUnit(mOption.bottom), 'width':addUnit(mOption.width), 'border-radius':addUnit(mOption.radius)}"
+		:src="mOption.src"
+		mode="widthFix"
+		@click="toTopClick"
+	/>
+</template>
+
+<script>
+export default {
+	props: {
+		// up.toTop的配置项
+		option: Object,
+		// 是否显示
+		value: false
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption(){
+			return this.option || {}
+		},
+		// 优先显示左边
+		left(){
+			return this.mOption.left ? this.addUnit(this.mOption.left) : 'auto';
+		},
+		// 右边距离 (优先显示左边)
+		right() {
+			return this.mOption.left ? 'auto' : this.addUnit(this.mOption.right);
+		}
+	},
+	methods: {
+		addUnit(num){
+			if(!num) return 0;
+			if(typeof num === 'number') return num + 'rpx';
+			return num
+		},
+		toTopClick() {
+			this.$emit('input', false); // 使v-model生效
+			this.$emit('click'); // 派发点击事件
+		}
+	}
+};
+</script>
+
+<style>
+/* 回到顶部的按钮 */
+.mescroll-totop {
+	z-index: 9990;
+	position: fixed !important; /* 加上important避免编译到H5,在多mescroll中定位失效 */
+	right: 20rpx;
+	bottom: 120rpx;
+	width: 72rpx;
+	height: auto;
+	border-radius: 50%;
+	opacity: 0;
+	transition: opacity 0.5s; /* 过渡 */
+	margin-bottom: var(--window-bottom); /* css变量 */
+}
+
+/* 适配 iPhoneX */
+@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
+	.mescroll-totop-safearea {
+		margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); /* window-bottom + 适配 iPhoneX */
+		margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom));
+	}
+}
+
+/* 显示 -- 淡入 */
+.mescroll-totop-in {
+	opacity: 1;
+}
+
+/* 隐藏 -- 淡出且不接收事件*/
+.mescroll-totop-out {
+	opacity: 0;
+	pointer-events: none;
+}
+</style>

+ 47 - 0
plugins/mescroll/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);
+	}
+}

+ 39 - 0
plugins/mescroll/components/mescroll-uni/components/mescroll-up.vue

@@ -0,0 +1,39 @@
+<!-- 上拉加载区域 -->
+<template>
+	<view class="mescroll-upwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}">
+		<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+		<view v-show="isUpLoading">
+			<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mOption.textColor}"></view>
+			<view class="upwarp-tip">{{ mOption.textLoading }}</view>
+		</view>
+		<!-- 无数据 -->
+		<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		option: Object, // up的配置项
+		type: Number // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption() {
+			return this.option || {};
+		},
+		// 加载中
+		isUpLoading() {
+			return this.type === 1;
+		},
+		// 没有更多了
+		isUpNoMore() {
+			return this.type === 2;
+		}
+	}
+};
+</script>
+
+<style>
+@import './mescroll-up.css';
+</style>

+ 15 - 0
plugins/mescroll/components/mescroll-uni/mescroll-i18n.js

@@ -0,0 +1,15 @@
+// 国际化工具类
+const mescrollI18n = {
+	// 默认语言
+	def: "zh",
+	// 获取当前语言类型
+	getType(){
+		return uni.getStorageSync("mescroll-i18n") || this.def
+	},
+	// 设置当前语言类型
+	setType(type){
+		uni.setStorageSync("mescroll-i18n", type)
+	}
+}
+
+export default mescrollI18n

+ 57 - 0
plugins/mescroll/components/mescroll-uni/mescroll-mixins.js

@@ -0,0 +1,57 @@
+// mescroll-body 和 mescroll-uni 通用
+const MescrollMixin = {
+	data() {
+		return {
+			mescroll: null //mescroll实例对象
+		}
+	},
+	// 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+	onPullDownRefresh(){
+		this.mescroll && this.mescroll.onPullDownRefresh();
+	},
+	// 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
+	onPageScroll(e) {
+		this.mescroll && this.mescroll.onPageScroll(e);
+	},
+	// 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
+	onReachBottom() {
+		this.mescroll && this.mescroll.onReachBottom();
+	},
+	methods: {
+		// mescroll组件初始化的回调,可获取到mescroll对象
+		mescrollInit(mescroll) {
+			this.mescroll = mescroll;
+			this.mescrollInitByRef(); // 兼容字节跳动小程序
+		},
+		// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序)
+		mescrollInitByRef() {
+			if(!this.mescroll || !this.mescroll.resetUpScroll){
+				let mescrollRef = this.$refs.mescrollRef;
+				if(mescrollRef) this.mescroll = mescrollRef.mescroll
+			}
+		},
+		// 下拉刷新的回调 (mixin默认resetUpScroll)
+		downCallback() {
+			if(this.mescroll.optUp.use){
+				this.mescroll.resetUpScroll()
+			}else{
+				setTimeout(()=>{
+					this.mescroll.endSuccess();
+				}, 500)
+			}
+		},
+		// 上拉加载的回调
+		upCallback() {
+			// mixin默认延时500自动结束加载
+			setTimeout(()=>{
+				this.mescroll.endErr();
+			}, 500)
+		}
+	},
+	mounted() {
+		this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况
+	}
+	
+}
+
+export default MescrollMixin;

+ 64 - 0
plugins/mescroll/components/mescroll-uni/mescroll-uni-option.js

@@ -0,0 +1,64 @@
+// 全局配置
+// mescroll-body 和 mescroll-uni 通用
+const GlobalOption = {
+	down: {
+		// 其他down的配置参数也可以写,这里只展示了常用的配置:
+		offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
+		native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+	},
+	up: {
+		// 其他up的配置参数也可以写,这里只展示了常用的配置:
+		offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
+		toTop: {
+			// 回到顶部按钮,需配置src才显示
+			src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入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: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
+		}
+	},
+	// 国际化配置
+	i18n: {
+		// 中文
+		zh: {
+			down: {
+				textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
+				textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
+				textLoading: '加载中 ...', // 加载中的提示文本
+				textSuccess: '加载成功', // 加载成功的文本
+				textErr: '加载失败', // 加载失败的文本
+			},
+			up: {
+				textLoading: '加载中 ...', // 加载中的提示文本
+				textNoMore: '-- END --', // 没有更多数据的提示文本
+				empty: {
+					tip: '~ 空空如也 ~' // 空提示
+				}
+			}
+		},
+		// 英文
+		en: {
+			down: {
+				textInOffset: 'drop down refresh',
+				textOutOffset: 'release updates',
+				textLoading: 'loading ...',
+				textSuccess: 'loaded successfully',
+				textErr: 'loading failed'
+			},
+			up: {
+				textLoading: 'loading ...',
+				textNoMore: '-- END --',
+				empty: {
+					tip: '~ absolutely empty ~'
+				}
+			}
+		}
+	}
+}
+
+export default GlobalOption

+ 36 - 0
plugins/mescroll/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);
+	}
+}

+ 799 - 0
plugins/mescroll/components/mescroll-uni/mescroll-uni.js

@@ -0,0 +1,799 @@
+/* mescroll
+ * version 1.3.7
+ * 2021-04-12 wenju
+ * https://www.mescroll.com
+ */
+
+export default function MeScroll(options, isScrollBody) {
+	let me = this;
+	me.version = '1.3.7'; // 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: '加载中 ...', // 加载中的提示文本
+		textSuccess: '加载成功', // 加载成功的文本
+		textErr: '加载失败', // 加载失败的文本
+		beforeEndDelay: 0, // 延时结束的时长 (显示加载成功/失败的时长, android小程序设置此项结束下拉会卡顿, 配置后请注意测试)
+		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: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
+		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.isDownEndSuccess = null; // 重置是否加载成功的状态 (wxs执行的是wxs.wxs)
+					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(me.isDownEndSuccess == null) delay = 0; // 没有执行加载中,则不延时
+	}
+	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.isDownEndSuccess = true
+		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) {
+		this.isDownEndSuccess = false
+		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()
+}

+ 477 - 0
plugins/mescroll/components/mescroll-uni/mescroll-uni.vue

@@ -0,0 +1,477 @@
+<template>
+	<view class="mescroll-uni-warp">
+		<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
+			<view class="mescroll-uni-content mescroll-render-touch"
+			@touchstart="wxsBiz.touchstartEvent" 
+			@touchmove="wxsBiz.touchmoveEvent" 
+			@touchend="wxsBiz.touchendEvent" 
+			@touchcancel="wxsBiz.touchendEvent"
+			:change:prop="wxsBiz.propObserver"
+			:prop="wxsProp">
+				<!-- 状态栏 -->
+				<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+		
+				<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
+					<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+					<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
+					<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+						<view class="downwarp-content">
+							<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
+							<view class="downwarp-tip">{{downText}}</view>
+						</view>
+					</view>
+
+					<!-- 列表内容 -->
+					<slot></slot>
+
+					<!-- 空布局 -->
+					<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+					<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+					<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+					<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+						<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+						<view v-show="upLoadType===1">
+							<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+							<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+						</view>
+						<!-- 无数据 -->
+						<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+					</view>
+				</view>
+			
+				<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
+				<!-- #ifdef H5 -->
+				<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+				<!-- #endif -->
+				
+				<!-- 适配iPhoneX -->
+				<view v-if="safearea" class="mescroll-safearea"></view>
+			</view>
+		</scroll-view>
+
+		<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+		
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="./wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from './wxs/renderjs.js';
+	export default {
+		mixins:[renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	// 引入mescroll-uni.js,处理核心逻辑
+	import MeScroll from './mescroll-uni.js';
+	// 引入全局配置
+	import GlobalOption from './mescroll-uni-option.js';
+	// 引入国际化工具类
+	import mescrollI18n from './mescroll-i18n.js';
+	// 引入回到顶部组件
+	import MescrollTop from './components/mescroll-top.vue';
+	// 引入兼容wxs(含renderjs)写法的mixins
+	import WxsMixin from './wxs/mixins.js';
+	
+	/**
+	 * mescroll-uni 嵌在页面某个区域的下拉刷新和上拉加载组件, 如嵌在弹窗,浮层,swiper中...
+	 * @property {Object} down 下拉刷新的参数配置
+	 * @property {Object} up 上拉加载的参数配置
+	 * @property {Object} i18n 国际化的参数配置
+	 * @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+	 * @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+	 * @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true
+	 * @property {String, Number} height 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效)
+	 * @property {Boolean} disableScroll 是否禁止滚动, 默认false
+	 * @event {Function} init 初始化完成的回调 
+	 * @event {Function} down 下拉刷新的回调
+	 * @event {Function} up 上拉加载的回调 
+	 * @event {Function} emptyclick 点击empty配置的btnText按钮回调
+	 * @event {Function} topclick 点击回到顶部的按钮回调
+	 * @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效)
+	 * @example <mescroll-uni ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-uni>
+	 */
+	export default {
+		name: 'mescroll-uni',
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		props: {
+			down: Object,
+			up: Object,
+			i18n: Object,
+			top: [String, Number],
+			topbar: [Boolean, String],
+			bottom: [String, Number],
+			safearea: Boolean,
+			fixed: {
+				type: Boolean,
+				default: true
+			},
+			height: [String, Number],
+			bottombar:{
+				type: Boolean,
+				default: true
+			},
+			disableScroll: Boolean
+		},
+		data() {
+			return {
+				mescroll: {optDown:{},optUp:{}}, // mescroll实例
+				viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
+				downHight: 0, //下拉刷新: 容器高度
+				downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				scrollTop: 0, // 滚动条的位置
+				scrollAnim: false, // 是否开启滚动动画
+				windowTop: 0, // 可使用窗口的顶部位置
+				windowBottom: 0, // 可使用窗口的底部位置
+				windowHeight: 0, // 可使用窗口的高度
+				statusBarHeight: 0 // 状态栏高度
+			}
+		},
+		computed: {
+			// 是否使用fixed定位 (当height有值,则不使用)
+			isFixed(){
+				return !this.height && this.fixed
+			},
+			// mescroll的高度
+			scrollHeight(){
+				if (this.isFixed) {
+					return "auto"
+				} else if(this.height){
+					return this.toPx(this.height) + 'px'
+				}else{
+					return "100%"
+				}
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			fixedTop() {
+				return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
+			},
+			padTop() {
+				return !this.isFixed ? this.numTop + 'px' : 0
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom)
+			},
+			fixedBottom() {
+				return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
+			},
+			padBottom() {
+				return !this.isFixed ? this.numBottom + 'px' : 0
+			},
+			// 是否为重置下拉的状态
+			isDownReset(){
+				return this.downLoadType===3 || this.downLoadType===4
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : '';
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 列表是否可滑动
+			scrollable(){
+				if(this.disableScroll) return false
+				return this.downLoadType===0 || this.isDownReset
+			},
+			// 是否在加载中
+			isDownLoading(){
+				return this.downLoadType === 3
+			},
+			// 旋转的角度
+			downRotate(){
+				return 'rotate(' + 360 * this.downRate + 'deg)'
+			},
+			// 文本提示
+			downText(){
+				if(!this.mescroll) return ""; // 避免头条小程序初始化时报错
+				switch (this.downLoadType){
+					case 1: return this.mescroll.optDown.textInOffset;
+					case 2: return this.mescroll.optDown.textOutOffset;
+					case 3: return this.mescroll.optDown.textLoading;
+					case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
+					default: return this.mescroll.optDown.textInOffset;
+				}
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num){
+				if(typeof num === "string"){
+					if (num.indexOf('px') !== -1) {
+						if(num.indexOf('rpx') !== -1) { // "10rpx"
+							num = num.replace('rpx', '');
+						} else if(num.indexOf('upx') !== -1) { // "10upx"
+							num = num.replace('upx', '');
+						} else { // "10px"
+							return Number(num.replace('px', ''))
+						}
+					}else if (num.indexOf('%') !== -1){
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace("%","")) / 100
+						return this.windowHeight * rate
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0
+			},
+			//注册列表滚动事件,用于下拉刷新和上拉加载
+			scroll(e) {
+				this.mescroll.scroll(e.detail, () => {
+					this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动
+				})
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll)
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			},
+			// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页)
+			setClientHeight() {
+				if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
+					this.isExec = true; // 避免多次获取
+					this.$nextTick(() => { // 确保dom已渲染
+						this.getClientInfo(data=>{
+							this.isExec = false;
+							if (data) {
+								this.mescroll.setClientHeight(data.height);
+							} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次
+								this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
+								setTimeout(() => {
+									this.setClientHeight()
+								}, this.clientNum * 100)
+							}
+						})
+					})
+				}
+			},
+			// 获取滚动区域的信息
+			getClientInfo(success){
+				let query = uni.createSelectorQuery();
+				// #ifndef MP-ALIPAY || MP-DINGTALK
+				query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
+				// #endif
+				let view = query.select('#' + this.viewId);
+				view.boundingClientRect(data => {
+					success(data)
+				}).exec();
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					beforeEndDownScroll(mescroll){
+						vm.downLoadType = 4; 
+						return mescroll.optDown.beforeEndDelay // 延时结束的时长
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downResetTimer && clearTimeout(vm.downResetTimer)
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
+							if(vm.downLoadType===4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll)
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+						// 更新容器的高度 (多mescroll的情况)
+						vm.setClientHeight()
+					}
+				}
+			}
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption);
+			vm.mescroll.viewId = vm.viewId; // 附带id
+			vm.mescroll.i18n = i18nOption; // 挂载语言包
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+			
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if(sys.windowTop) vm.windowTop = sys.windowTop;
+			if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是scrollview,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
+				if(typeof y === 'string'){
+					// 小程序不支持slot里面的scroll-into-view, 统一使用计算的方式实现
+					vm.getClientInfo(function(rect){
+						let mescrollTop = rect.top // mescroll到顶部的距离
+						let selector;
+						if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
+							selector = '#'+y // 不带#和. 则默认为id选择器
+						}else{
+							selector = y
+							// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
+							if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
+								selector = y.split('>>>')[1].trim()
+							}
+							// #endif
+						}
+						uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
+							if (rect) {
+								let curY = vm.mescroll.getScrollTop()
+								let top = rect.top - mescrollTop
+								top += curY
+								if(!vm.isFixed) top -= vm.numTop
+								vm.scrollTop = curY;
+								vm.$nextTick(function() {
+									vm.scrollTop = top
+								})
+							} else{
+								console.error(selector + ' does not exist');
+							}
+						}).exec()
+					})
+					return;
+				}
+				let curY = vm.mescroll.getScrollTop()
+				if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡
+					vm.scrollTop = curY;
+					vm.$nextTick(function() {
+						vm.scrollTop = y
+					})
+				} else {
+					vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t
+						vm.scrollTop = step
+					}, t)
+				}
+			})
+			
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		mounted() {
+			// 设置容器的高度
+			this.setClientHeight()
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	}
+</script>
+
+<style>
+	@import "./mescroll-uni.css";
+	@import "./components/mescroll-down.css";
+	@import './components/mescroll-up.css';
+</style>

+ 47 - 0
plugins/mescroll/components/mescroll-uni/mixins/mescroll-comp.js

@@ -0,0 +1,47 @@
+/**
+ * mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期
+ */
+const MescrollCompMixin = {
+	// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件 (一级)
+	onPageScroll(e) {
+		this.handlePageScroll(e)
+	},
+	onReachBottom() {
+		this.handleReachBottom()
+	},
+	// 当down的native: true时, 还需传递此方法进到子组件
+	onPullDownRefresh(){
+		this.handlePullDownRefresh()
+	},
+	data() {
+		return {
+			mescroll: { // mescroll-body写在子子子...组件的情况 (多级)
+				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;

+ 66 - 0
plugins/mescroll/components/mescroll-uni/mixins/mescroll-more-item.js

@@ -0,0 +1,66 @@
+/**
+ * mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例)
+ */
+const MescrollMoreItemMixin = {
+	// 支付宝小程序不支持props的mixin,需写在具体的页面中
+	// #ifndef MP-ALIPAY || MP-DINGTALK
+	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.mescrollTrigger()
+		}
+	},
+	methods: {
+		// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序)
+		mescrollInitByRef() {
+			if(!this.mescroll || !this.mescroll.resetUpScroll){
+				// 字节跳动小程序编辑器不支持一个页面存在相同的ref, 多mescroll的ref需动态生成, 格式为'mescrollRef下标'
+				let mescrollRef = this.$refs.mescrollRef || this.$refs['mescrollRef'+this.i];
+				if(mescrollRef) this.mescroll = mescrollRef.mescroll
+			}
+		},
+		// mescroll组件初始化的回调,可获取到mescroll对象 (覆盖mescroll-mixins.js的mescrollInit, 为了标记isInit)
+		mescrollInit(mescroll) {
+			this.mescroll = mescroll;
+			this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序
+			// 自动加载当前tab的数据
+			if(this.i === this.index){
+				this.mescrollTrigger()
+			}
+		},
+		// 主动触发加载
+		mescrollTrigger(){
+			this.isInit = true; // 标记为true
+			if (this.mescroll) {
+				if (this.mescroll.optDown.use) {
+					this.mescroll.triggerDownScroll();
+				} else{
+					this.mescroll.triggerUpScroll();
+				}
+			}
+		}
+	}
+}
+
+export default MescrollMoreItemMixin;

+ 74 - 0
plugins/mescroll/components/mescroll-uni/mixins/mescroll-more.js

@@ -0,0 +1,74 @@
+/**
+ * mescroll-body写在子组件时, 需通过mescroll的mixins补充子组件缺少的生命周期
+ */
+const MescrollMoreMixin = {
+	data() {
+		return {
+			tabIndex: 0, // 当前tab下标
+			mescroll: { // mescroll-body写在子子子...组件的情况 (多级)
+				onPageScroll: e=>{
+					this.handlePageScroll(e)
+				},
+				onReachBottom: ()=>{
+					this.handleReachBottom()
+				},
+				onPullDownRefresh: ()=>{
+					this.handlePullDownRefresh()
+				}
+			}
+		}
+	},
+	// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
+	onPageScroll(e) {
+		this.handlePageScroll(e)
+	},
+	onReachBottom() {
+		this.handleReachBottom()
+	},
+	// 当down的native: true时, 还需传递此方法进到子组件
+	onPullDownRefresh(){
+		this.handlePullDownRefresh()
+	},
+	methods:{
+		handlePageScroll(e){
+			let mescroll = this.getMescroll(this.tabIndex);
+			mescroll && mescroll.onPageScroll(e);
+		},
+		handleReachBottom(){
+			let mescroll = this.getMescroll(this.tabIndex);
+			mescroll && mescroll.onReachBottom();
+		},
+		handlePullDownRefresh(){
+			let mescroll = this.getMescroll(this.tabIndex);
+			mescroll && mescroll.onPullDownRefresh();
+		},
+		// 根据下标获取对应子组件的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;

+ 109 - 0
plugins/mescroll/components/mescroll-uni/wxs/mixins.js

@@ -0,0 +1,109 @@
+// 定义在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 || MP-QQ || 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
+				// 状态挂载到mescroll对象, 以便在其他组件中使用, 比如<me-video>中
+				this.$set(this.mescroll, 'downLoadType', this.downLoadType)
+				// 重置是否加载成功的状态
+				this.$set(this.mescroll, 'isDownEndSuccess', null)
+			}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 || MP-QQ || 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的方法 (值改变才触发更新)
+			let delay = 300 + (this.mescroll.optDown.beforeEndDelay || 0)
+			setTimeout(()=>{
+				if(this.downLoadType === 4 || this.downLoadType === 0){
+					this.callProp = {callType: "clearTransform", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
+				}
+				// 状态挂载到mescroll对象, 以便在其他组件中使用, 比如<me-video>中
+				this.$set(this.mescroll, 'downLoadType', this.downLoadType)
+			}, delay)
+		}
+		// 初始化wxs的数据
+		this.wxsCall({type: 'setWxsProp'})
+		// #endif
+	}
+}
+
+export default WxsMixin;

+ 92 - 0
plugins/mescroll/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;

+ 268 - 0
plugins/mescroll/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
+}

+ 80 - 0
plugins/mescroll/package.json

@@ -0,0 +1,80 @@
+{
+  "id": "mescroll-uni",
+  "displayName": "【wxs+renderjs实现】高性能的下拉刷新上拉加载组件",
+  "version": "1.3.7",
+  "description": "支持uni-app的下拉刷新和上拉加载的组件,支持原生页面和局部区域滚动,支持国际化",
+  "keywords": [
+    "mescroll",
+    "下拉刷新",
+    "上拉加载",
+    "翻页",
+    "分页"
+],
+  "repository": "https://github.com/mescroll/mescroll",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "category": [
+        "前端组件",
+        "通用组件"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/mescroll-uni"
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "n"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "y",
+          "联盟": "y"
+        }
+      }
+    }
+  }
+}

+ 45 - 0
plugins/mescroll/readme.md

@@ -0,0 +1,45 @@
+## mescroll --【wxs+renderjs实现】高性能的下拉刷新上拉加载组件
+1. mescroll的uni版本 是专门用在uni-app的下拉刷新和上拉加载的组件  
+
+2. mescroll的uni版本 继承了mescroll.js的实用功能: 自动处理分页, 自动控制无数据, 空布局提示, 回到顶部按钮 ..
+
+3. mescroll的uni版本 丰富的案例, 自由灵活的api, 超详细的注释, 可让您快速自定义真正属于自己的下拉上拉组件
+
+<br/>
+
+
+## 最新文档(1.3.7版本): <a href="https://www.mescroll.com/uni.html">https://www.mescroll.com/uni.html</a>
+2021-04-13 by 小瑾同学 (文档可能会有缓存,建议打开时刷新一下)
+
+
+## 1.3.5版本已调整为[uni_modules](https://uniapp.dcloud.io/uni_modules)
+uni_modules版本的mescroll-body 和 mescroll-empty 支持 [easycom规范](https://uniapp.dcloud.io/collocation/pages?id=easycom)  
+所以 main.js 无需再为mescroll-body注册全局组件  
+所以个别页面要单独使用 mescroll-empty , 也无需手动注册
+#### 1.3.5以前的用户升级为uni_modules版本:
+```
+1. 删除原来的 @/components/mescroll-uni 组件
+2. 删除 main.js 注册的 mescroll 组件
+3. 从插件市场导入最新mescroll组件 (1.3.5+uni_modules版本)
+4. 全局搜索 '@/components/mescroll-uni/' 替换为 '@/uni_modules/mescroll-uni/components/mescroll-uni/'
+5. mescroll-empty遵循easycom规范, 若某些页面单独使用 'mescroll-empty.vue', 可删除手动导入的代码
+```
+
+## 近期已更新优化的内容:
+1. 微信小程序, app, h5使用高性能wxs和renderjs, 下拉刷新更流畅丝滑, 尤其能明显解决Android小程序下拉卡顿的问题  
+2. 新增`入门极简`示例, 国际化`mescroll-i18n.vue`示例, 轮播吸顶菜单`mescroll-swiper-sticky.vue`示例  
+3. 新增 "局部区域滚动" 的案例: mescroll-body-part.vue 和 mescroll-uni-part.vue  
+4. 新增 me-video 视频组件, 解决APP端视频下拉悬浮错位的问题, 参考 mescroll-options.vue 示例  
+5. 新增 me-tabs 组件,tabs支持水平滑动; 优化mescroll-more和mescroll-swiper的案例, 顶部tab支持水平滑动  
+6. 吸顶悬浮提供了原生sticky和监听滚动条实现的示例: sticky.vue 和 sticky-scroll.vue (推荐使用sticky样式实现)  
+7. mescroll.scrollTo(y)的y支持css选择器, 包括跨自定义组件的后代选择器, 支持滚动到子组件的view (参考 mescroll-options.vue)  
+8. topbar 顶部是否预留状态栏的高度, 默认false; 还可支持设置状态栏背景: 如 '#ffff00', 'url(xxx) 0 0/100% 100%', 'linear-gradient(xx)'  
+9. down.bgColor 和 up.bgColor 加载区域的背景,不仅支持色值, 而且还是支持背景图和渐变: 如 'url(xxx) 0 0/100% 100%', 'linear-gradient(xx)'  
+10. topbar,bgColor支持一行代码定义background: [https://www.runoob.com/cssref/css3-pr-background.html](https://www.runoob.com/cssref/css3-pr-background.html)
+<br/>
+<br/>
+<a href="https://ext.dcloud.net.cn/plugin?id=343&update_log">查看更多 ... </a>
+
+<br/>
+
+#### mescroll不支持nvue,也暂无支持的计划哈,so sorry~

+ 21 - 0
plugins/uview-ui/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 www.uviewui.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 66 - 0
plugins/uview-ui/README.md

@@ -0,0 +1,66 @@
+<p align="center">
+    <img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
+</p>
+<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView 2.0</h3>
+<h3 align="center">多平台快速开发的UI框架</h3>
+
+[![stars](https://img.shields.io/github/stars/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
+[![forks](https://img.shields.io/github/forks/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
+[![issues](https://img.shields.io/github/issues/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0/issues)
+[![Website](https://img.shields.io/badge/uView-up-blue?style=flat-square)](https://uviewui.com)
+[![release](https://img.shields.io/github/v/release/umicro/uView2.0?style=flat-square)](https://gitee.com/umicro/uView2.0/releases)
+[![license](https://img.shields.io/github/license/umicro/uView2.0?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)
+
+## 说明
+
+uView UI,是[uni-app](https://uniapp.dcloud.io/)全面兼容nvue的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
+
+## [官方文档:https://uviewui.com](https://uviewui.com)
+
+
+## 预览
+
+您可以通过**微信**扫码,查看最佳的演示效果。
+<br>
+<br>
+<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
+
+
+## 链接
+
+- [官方文档](https://www.uviewui.com/)
+- [更新日志](https://www.uviewui.com/components/changelog.html)
+- [升级指南](https://www.uviewui.com/components/changeGuide.html)
+- [关于我们](https://www.uviewui.com/cooperation/about.html)
+
+## 交流反馈
+
+欢迎加入我们的QQ群交流反馈:[点此跳转](https://www.uviewui.com/components/addQQGroup.html)
+
+## 关于PR
+
+> 我们非常乐意接受各位的优质PR,但在此之前我希望您了解uView2.0是一个需要兼容多个平台的(小程序、h5、ios app、android app)包括nvue页面、vue页面。
+> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢!
+
+## 安装
+
+#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?id=1593](https://ext.dcloud.net.cn/plugin?id=1593)
+
+请通过[官网安装文档](https://www.uviewui.com/components/install.html)了解更详细的内容
+
+## 快速上手
+
+请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
+
+## 使用方法
+配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
+
+```html
+<template>
+	<u-button text="按钮"></u-button>
+</template>
+```
+
+## 版权信息
+uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。
+

+ 357 - 0
plugins/uview-ui/changelog.md

@@ -0,0 +1,357 @@
+## 2.0.34(2022-09-25)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. `u-input`、`u-textarea`增加`ignoreCompositionEvent`属性
+2. 修复`route`方法调用可能报错的问题
+3. 修复`u-no-network`组件`z-index`无效的问题
+4. 修复`textarea`组件在h5上confirmType=""报错的问题
+5. `u-rate`适配`nvue`
+6. 优化验证手机号码的正则表达式(根据工信部发布的《电信网编号计划(2017年版)》进行修改。)
+7. `form-item`添加`labelPosition`属性
+8. `u-calendar`修复`maxDate`设置为当前日期,并且当前时间大于08:00时无法显示日期列表的问题 (#724)
+9. `u-radio`增加一个默认插槽用于自定义修改label内容 (#680)
+10. 修复`timeFormat`函数在safari重的兼容性问题 (#664)
+## 2.0.33(2022-06-17)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复`loadmore`组件`lineColor`类型错误问题
+2. 修复`u-parse`组件`imgtap`、`linktap`不生效问题
+## 2.0.32(2022-06-16)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+1. `u-loadmore`新增自定义颜色、虚/实线
+2. 修复`u-swiper-action`组件部分平台不能上下滑动的问题
+3. 修复`u-list`回弹问题
+4. 修复`notice-bar`组件动画在低端安卓机可能会抖动的问题
+5. `u-loading-page`添加控制图标大小的属性`iconSize`
+6. 修复`u-tooltip`组件`color`参数不生效的问题
+7. 修复`u--input`组件使用`blur`事件输出为`undefined`的bug
+8. `u-code-input`组件新增键盘弹起时,是否自动上推页面参数`adjustPosition`
+9. 修复`image`组件`load`事件无回调对象问题
+10. 修复`button`组件`loadingSize`设置无效问题
+10. 其他修复
+## 2.0.31(2022-04-19)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复`upload`在`vue`页面上传成功后没有成功标志的问题
+2. 解决演示项目中微信小程序模拟上传图片一直出于上传中问题
+3. 修复`u-code-input`组件在`nvue`页面编译到`app`平台上光标异常问题(`app`去除此功能)
+4. 修复`actionSheet`组件标题关闭按钮点击事件名称错误的问题
+5. 其他修复
+## 2.0.30(2022-04-04)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. `u-rate`增加`readonly`属性
+2. `tabs`滑块支持设置背景图片
+3. 修复`u-subsection` `mode`为`subsection`时,滑块样式不正确的问题
+4. `u-code-input`添加光标效果动画
+5. 修复`popup`的`open`事件不触发
+6. 修复`u-flex-column`无效的问题
+7. 修复`u-datetime-picker`索引在特定场合异常问题
+8. 修复`u-datetime-picker`最小时间字符串模板错误问题
+9. `u-swiper`添加`m3u8`验证
+10. `u-swiper`修改判断image和video逻辑
+11. 修复`swiper`无法使用本地图片问题,增加`type`参数
+12. 修复`u-row-notice`格式错误问题
+13. 修复`u-switch`组件当`unit`为`rpx`时,`nodeStyle`消失的问题
+14. 修复`datetime-picker`组件`showToolbar`与`visibleItemCount`属性无效的问题
+15. 修复`upload`组件条件编译位置判断错误,导致`previewImage`属性设置为`false`时,整个组件都会被隐藏的问题
+16. 修复`u-checkbox-group`设置`shape`属性无效的问题
+17. 修复`u-upload`的`capture`传入字符串的时候不生效的问题
+18. 修复`u-action-sheet`组件,关闭事件逻辑错误的问题
+19. 修复`u-list`触顶事件的触发错误的问题
+20. 修复`u-text`只有手机号可拨打的问题
+21. 修复`u-textarea`不能换行的问题
+22. 其他修复
+## 2.0.29(2022-03-13)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复`u--text`组件设置`decoration`属性未生效的问题
+2. 修复`u-datetime-picker`使用`formatter`后返回值不正确
+3. 修复`u-datetime-picker` `intercept` 可能为undefined
+4. 修复已设置单位 uni..config.unit = 'rpx'时,线型指示器 `transform` 的位置翻倍,导致指示器超出宽度
+5. 修复mixin中bem方法生成的类名在支付宝和字节小程序中失效
+6. 修复默认值传值为空的时候,打开`u-datetime-picker`报错,不能选中第一列时间的bug
+7. 修复`u-datetime-picker`使用`formatter`后返回值不正确
+8. 修复`u-image`组件`loading`无效果的问题
+9. 修复`config.unit`属性设为`rpx`时,导航栏占用高度不足导致塌陷的问题
+10. 修复`u-datetime-picker`组件`itemHeight`无效问题
+11. 其他修复
+## 2.0.28(2022-02-22)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. search组件新增searchIconSize属性
+2. 兼容Safari/Webkit中传入时间格式如2022-02-17 12:00:56
+3. 修复text value.js 判断日期出format错误问题
+4. priceFormat格式化金额出现精度错误
+5. priceFormat在部分情况下出现精度损失问题
+6. 优化表单rules提示
+7. 修复avatar组件src为空时,展示状态不对
+8. 其他修复
+## 2.0.27(2022-01-28)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1.样式修复
+## 2.0.26(2022-01-28)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1.样式修复
+## 2.0.25(2022-01-27)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复text组件mode=price时,可能会导致精度错误的问题
+2. 添加$u.setConfig()方法,可设置uView内置的config, props, zIndex, color属性,详见:[修改uView内置配置方案](https://uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
+3. 优化form组件在errorType=toast时,如果输入错误页面会有抖动的问题
+4. 修复$u.addUnit()对配置默认单位可能无效的问题
+## 2.0.24(2022-01-25)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复swiper在current指定非0时缩放有误
+2. 修复u-icon添加stop属性的时候报错
+3. 优化遗留的通过正则判断rpx单位的问题
+4. 优化Layout布局 vue使用gutter时,会超出固定区域
+5. 优化search组件高度单位问题(rpx -> px)
+6. 修复u-image slot 加载和错误的图片失去了高度
+7. 修复u-index-list中footer插槽与header插槽存在性判断错误
+8. 修复部分机型下u-popup关闭时会闪烁
+9. 修复u-image在nvue-app下失去宽高
+10. 修复u-popup运行报错
+11. 修复u-tooltip报错
+12. 修复box-sizing在app下的警告
+13. 修复u-navbar在小程序中报运行时错误
+14. 其他修复
+## 2.0.23(2022-01-24)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复image组件在hx3.3.9的nvue下可能会显示异常的问题
+2. 修复col组件gutter参数带rpx单位处理不正确的问题
+3. 修复text组件单行时无法显示省略号的问题
+4. navbar添加titleStyle参数
+5. 升级到hx3.3.9可消除nvue下控制台样式警告的问题
+## 2.0.22(2022-01-19)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. $u.page()方法优化,避免在特殊场景可能报错的问题
+2. picker组件添加immediateChange参数
+3. 新增$u.pages()方法
+## 2.0.21(2022-01-19)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 优化:form组件在用户设置rules的时候提示用户model必传
+2. 优化遗留的通过正则判断rpx单位的问题
+3. 修复微信小程序环境中tabbar组件开启safeAreaInsetBottom属性后,placeholder高度填充不正确
+4. 修复swiper在current指定非0时缩放有误
+5. 修复u-icon添加stop属性的时候报错
+6. 修复upload组件在accept=all的时候没有作用
+7. 修复在text组件mode为phone时call属性无效的问题
+8. 处理u-form clearValidate方法
+9. 其他修复
+## 2.0.20(2022-01-14)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复calendar默认会选择一个日期,如果直接点确定的话,无法取到值的问题
+2. 修复Slider缺少disabled props 还有注释
+3. 修复u-notice-bar点击事件无法拿到index索引值的问题
+4. 修复u-collapse-item在vue文件下,app端自定义插槽不生效的问题
+5. 优化头像为空时显示默认头像 
+6. 修复图片地址赋值后判断加载状态为完成问题
+7. 修复日历滚动到默认日期月份区域
+8. search组件暴露点击左边icon事件
+9. 修复u-form clearValidate方法不生效
+10. upload h5端增加返回文件参数(文件的name参数)
+11. 处理upload选择文件后url为blob类型无法预览的问题
+12. u-code-input 修复输入框没有往左移出一半屏幕
+13. 修复Upload上传 disabled为true时,控制台报hoverClass类型错误
+14. 临时处理ios app下grid点击坍塌问题
+15. 其他修复
+## 2.0.19(2021-12-29)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 优化微信小程序包体积可在微信中预览,请升级HbuilderX3.3.4,同时在“运行->运行到小程序模拟器”中勾选“运行时是否压缩代码”
+2. 优化微信小程序setData性能,处理某些方法如$u.route()无法在模板中使用的问题
+3. navbar添加autoBack参数
+4. 允许avatar组件的事件冒泡
+5. 修复cell组件报错问题
+6. 其他修复
+## 2.0.18(2021-12-28)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复app端编译报错问题
+2. 重新处理微信小程序端setData过大的性能问题
+3. 修复边框问题
+4. 修复最大最小月份不大于0则没有数据出现的问题
+5. 修复SwipeAction微信小程序端无法上下滑动问题
+6. 修复input的placeholder在小程序端默认显示为true问题
+7. 修复divider组件click事件无效问题
+8. 修复u-code-input maxlength 属性值为 String 类型时显示异常
+9. 修复当 grid只有 1到2时 在小程序端algin设置无效的问题
+10. 处理form-item的label为top时,取消错误提示的左边距
+11. 其他修复
+## 2.0.17(2021-12-26)
+## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 解决HBuilderX3.3.3.20211225版本导致的样式问题
+2. calendar日历添加monthNum参数
+3. navbar添加center slot
+## 2.0.16(2021-12-25)
+## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 解决微信小程序setData性能问题
+2. 修复count-down组件change事件不触发问题
+## 2.0.15(2021-12-21)
+## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复Cell单元格titleWidth无效
+2. 修复cheakbox组件ischecked不更新
+3. 修复keyboard是否显示"."按键默认值问题
+4. 修复number-keyboard是否显示键盘的"."符号问题
+5. 修复Input输入框 readonly无效
+6. 修复u-avatar 导致打包app、H5时候报错问题
+7. 修复Upload上传deletable无效
+8. 修复upload当设置maxSize时无效的问题
+9. 修复tabs lineWidth传入带单位的字符串的时候偏移量计算错误问题
+10. 修复rate组件在有padding的view内,显示的星星位置和可触摸区域不匹配,无法正常选中星星
+## 2.0.13(2021-12-14)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复配置默认单位为rpx可能会导致自定义导航栏高度异常的问题
+## 2.0.12(2021-12-14)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复tabs组件在vue环境下划线消失的问题
+2. 修复upload组件在安卓小程序无法选择视频的问题
+3. 添加uni.$u.config.unit配置,用于配置参数默认单位,详见:[默认单位配置](https://www.uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
+4. 修复textarea组件在没绑定v-model时,字符统计不生效问题
+5. 修复nvue下控制是否出现滚动条失效问题
+## 2.0.11(2021-12-13)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. text组件align参数无效的问题
+2. subsection组件添加keyName参数
+3. upload组件无法判断[Object file]类型的问题
+4. 处理notify层级过低问题
+5. codeInput组件添加disabledDot参数
+6. 处理actionSheet组件round参数无效的问题
+7. calendar组件添加round参数用于控制圆角值
+8. 处理swipeAction组件在vue环境下默认被打开的问题
+9. button组件的throttleTime节流参数无效的问题
+10. 解决u-notify手动关闭方法close()无效的问题
+11. input组件readonly不生效问题
+12. tag组件type参数为info不生效问题
+## 2.0.10(2021-12-08)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复button sendMessagePath属性不生效
+2. 修复DatetimePicker选择器title无效
+3. 修复u-toast设置loading=true不生效
+4. 修复u-text金额模式传0报错
+5. 修复u-toast组件的icon属性配置不生效
+6. button的icon在特殊场景下的颜色优化
+7. IndexList优化,增加#
+## 2.0.9(2021-12-01)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 优化swiper的height支持100%值(仅vue有效),修复嵌入视频时click事件无法触发的问题
+2. 优化tabs组件对list值为空的判断,或者动态变化list时重新计算相关尺寸的问题
+3. 优化datetime-picker组件逻辑,让其后续打开的默认值为上一次的选中值,需要通过v-model绑定值才有效
+4. 修复upload内嵌在其他组件中,选择图片可能不会换行的问题
+## 2.0.8(2021-12-01)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复toast的position参数无效问题
+2. 处理input在ios nvue上无法获得焦点的问题
+3. avatar-group组件添加extraValue参数,让剩余展示数量可手动控制
+4. tabs组件添加keyName参数用于配置从对象中读取的键名
+5. 处理text组件名字脱敏默认配置无效的问题
+6. 处理picker组件item文本太长换行问题
+## 2.0.7(2021-11-30)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复radio和checkbox动态改变v-model无效的问题。
+2. 优化form规则validator在微信小程序用法
+3. 修复backtop组件mode参数在微信小程序无效的问题
+4. 处理Album的previewFullImage属性无效的问题
+5. 处理u-datetime-picker组件mode='time'在选择改变时间时,控制台报错的问题
+## 2.0.6(2021-11-27)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 处理tag组件在vue下边框无效的问题。
+2. 处理popup组件圆角参数可能无效的问题。
+3. 处理tabs组件lineColor参数可能无效的问题。
+4. propgress组件在值很小时,显示异常的问题。
+## 2.0.5(2021-11-25)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. calendar在vue下显示异常问题。 
+2. form组件labelPosition和errorType参数无效的问题
+3. input组件inputAlign无效的问题
+4. 其他一些修复
+## 2.0.4(2021-11-23)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+0. input组件缺失@confirm事件,以及subfix和prefix无效问题
+1. component.scss文件样式在vue下干扰全局布局问题
+2. 修复subsection在vue环境下表现异常的问题
+3. tag组件的bgColor等参数无效的问题
+4. upload组件不换行的问题
+5. 其他的一些修复处理
+## 2.0.3(2021-11-16)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 处理modal的confirm回调事件拼写错误问题
+6. 处理input组件@input事件参数错误问题
+7. 其他一些修复
+## 2.0.2(2021-11-16)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 修复input组件formatter参数缺失问题
+6. 优化loading-icon组件的scss写法问题,防止不兼容新版本scss
+## 2.0.0(2020-11-15)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 修复input组件formatter参数缺失问题
+
+

+ 78 - 0
plugins/uview-ui/components/u--form/u--form.vue

@@ -0,0 +1,78 @@
+<template>
+	<uvForm
+		ref="uForm"
+		:model="model"
+		:rules="rules"
+		:errorType="errorType"
+		:borderBottom="borderBottom"
+		:labelPosition="labelPosition"
+		:labelWidth="labelWidth"
+		:labelAlign="labelAlign"
+		:labelStyle="labelStyle"
+		:customStyle="customStyle"
+	>
+		<slot />
+	</uvForm>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-form被uni-app官方占用了,u-form在nvue中相当于form组件
+	 * 所以在nvue下,取名为u--form,内部其实还是u-form.vue,只不过做一层中转
+	 */
+	import uvForm from '../u-form/u-form.vue';
+	import props from '../u-form/props.js'
+	export default {
+		// #ifdef MP-WEIXIN
+		name: 'u-form',
+		// #endif
+		// #ifndef MP-WEIXIN
+		name: 'u--form',
+		// #endif
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvForm
+		},
+		created() {
+			this.children = []
+		},
+		methods: {
+			// 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则
+			setRules(rules) {
+				this.$refs.uForm.setRules(rules)
+			},
+			validate() {
+				/**
+				 * 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form
+				 * 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的
+				 * 对应方法的时候,在小程序中都先将u--form的children赋值给u-form中的children
+				 */
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.validate()
+			},
+			validateField(value, callback) {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.validateField(value, callback)
+			},
+			resetFields() {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.resetFields()
+			},
+			clearValidate(props) {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.clearValidate(props)
+			},
+			setMpData() {
+				this.$refs.uForm.children = this.children
+			}
+		},
+	}
+</script>

+ 73 - 0
plugins/uview-ui/components/u--input/u--input.vue

@@ -0,0 +1,73 @@
+<template>
+	<uvInput 
+		:value="value"
+		:type="type"
+		:fixed="fixed"
+		:disabled="disabled"
+		:disabledColor="disabledColor"
+		:clearable="clearable"
+		:password="password"
+		:maxlength="maxlength"
+		:placeholder="placeholder"
+		:placeholderClass="placeholderClass"
+		:placeholderStyle="placeholderStyle"
+		:showWordLimit="showWordLimit"
+		:confirmType="confirmType"
+		:confirmHold="confirmHold"
+		:holdKeyboard="holdKeyboard"
+		:focus="focus"
+		:autoBlur="autoBlur"
+		:disableDefaultPadding="disableDefaultPadding"
+		:cursor="cursor"
+		:cursorSpacing="cursorSpacing"
+		:selectionStart="selectionStart"
+		:selectionEnd="selectionEnd"
+		:adjustPosition="adjustPosition"
+		:inputAlign="inputAlign"
+		:fontSize="fontSize"
+		:color="color"
+		:prefixIcon="prefixIcon"
+		:suffixIcon="suffixIcon"
+		:suffixIconStyle="suffixIconStyle"
+		:prefixIconStyle="prefixIconStyle"
+		:border="border"
+		:readonly="readonly"
+		:shape="shape"
+		:customStyle="customStyle"
+		:formatter="formatter"
+		:ignoreCompositionEvent="ignoreCompositionEvent"
+		@focus="$emit('focus')"
+		@blur="e => $emit('blur', e)"
+		@keyboardheightchange="$emit('keyboardheightchange')"
+		@change="e => $emit('change', e)"
+		@input="e => $emit('input', e)"
+		@confirm="e => $emit('confirm', e)"
+		@clear="$emit('clear')"
+		@click="$emit('click')"
+	>
+		<!-- #ifdef MP -->
+		<slot name="prefix"></slot>
+		<slot name="suffix"></slot>
+		<!-- #endif -->
+		<!-- #ifndef MP -->
+		<slot name="prefix" slot="prefix"></slot>
+		<slot name="suffix" slot="suffix"></slot>
+		<!-- #endif -->
+	</uvInput>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件
+	 * 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转
+	 */
+	import uvInput from '../u-input/u-input.vue';
+	import props from '../u-input/props.js'
+	export default {
+		name: 'u--input',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvInput
+		},
+	}
+</script>

+ 44 - 0
plugins/uview-ui/components/u--text/u--text.vue

@@ -0,0 +1,44 @@
+<template>
+    <uvText
+        :type="type"
+        :show="show"
+        :text="text"
+        :prefixIcon="prefixIcon"
+        :suffixIcon="suffixIcon"
+        :mode="mode"
+        :href="href"
+        :format="format"
+        :call="call"
+        :openType="openType"
+        :bold="bold"
+        :block="block"
+        :lines="lines"
+        :color="color"
+		:decoration="decoration"
+        :size="size"
+        :iconStyle="iconStyle"
+        :margin="margin"
+        :lineHeight="lineHeight"
+        :align="align"
+        :wordWrap="wordWrap"
+        :customStyle="customStyle"
+        @click="$emit('click')"
+    ></uvText>
+</template>
+
+<script>
+/**
+ * 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件
+ * 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转
+ * 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法
+ */
+import uvText from "../u-text/u-text.vue";
+import props from "../u-text/props.js";
+export default {
+    name: "u--text",
+    mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+    components: {
+        uvText,
+    },
+};
+</script>

+ 48 - 0
plugins/uview-ui/components/u--textarea/u--textarea.vue

@@ -0,0 +1,48 @@
+<template>
+	<uvTextarea
+		:value="value"
+		:placeholder="placeholder"
+		:height="height"
+		:confirmType="confirmType"
+		:disabled="disabled"
+		:count="count"
+		:focus="focus"
+		:autoHeight="autoHeight"
+		:fixed="fixed"
+		:cursorSpacing="cursorSpacing"
+		:cursor="cursor"
+		:showConfirmBar="showConfirmBar"
+		:selectionStart="selectionStart"
+		:selectionEnd="selectionEnd"
+		:adjustPosition="adjustPosition"
+		:disableDefaultPadding="disableDefaultPadding"
+		:holdKeyboard="holdKeyboard"
+		:maxlength="maxlength"
+		:border="border"
+		:customStyle="customStyle"
+		:formatter="formatter"
+		:ignoreCompositionEvent="ignoreCompositionEvent"
+		@focus="e => $emit('focus')"
+		@blur="e => $emit('blur')"
+		@linechange="e => $emit('linechange', e)"
+		@confirm="e => $emit('confirm')"
+		@input="e => $emit('input', e)"
+		@keyboardheightchange="e => $emit('keyboardheightchange')"
+	></uvTextarea>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件
+	 * 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转
+	 */
+	import uvTextarea from '../u-textarea/u-textarea.vue';
+	import props from '../u-textarea/props.js'
+	export default {
+		name: 'u--textarea',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvTextarea
+		},
+	}
+</script>

+ 72 - 0
plugins/uview-ui/components/u-badge/props.js

@@ -0,0 +1,72 @@
+export default {
+    props: {
+        // 是否显示圆点
+        isDot: {
+            type: Boolean,
+            default: uni.$u.props.badge.isDot
+        },
+        // 显示的内容
+        value: {
+            type: [Number, String],
+            default: uni.$u.props.badge.value
+        },
+        // 是否显示
+        show: {
+            type: Boolean,
+            default: uni.$u.props.badge.show
+        },
+        // 最大值,超过最大值会显示 '{max}+'
+        max: {
+            type: [Number, String],
+            default: uni.$u.props.badge.max
+        },
+        // 主题类型,error|warning|success|primary
+        type: {
+            type: String,
+            default: uni.$u.props.badge.type
+        },
+        // 当数值为 0 时,是否展示 Badge
+        showZero: {
+            type: Boolean,
+            default: uni.$u.props.badge.showZero
+        },
+        // 背景颜色,优先级比type高,如设置,type参数会失效
+        bgColor: {
+            type: [String, null],
+            default: uni.$u.props.badge.bgColor
+        },
+        // 字体颜色
+        color: {
+            type: [String, null],
+            default: uni.$u.props.badge.color
+        },
+        // 徽标形状,circle-四角均为圆角,horn-左下角为直角
+        shape: {
+            type: String,
+            default: uni.$u.props.badge.shape
+        },
+        // 设置数字的显示方式,overflow|ellipsis|limit
+        // overflow会根据max字段判断,超出显示`${max}+`
+        // ellipsis会根据max判断,超出显示`${max}...`
+        // limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数
+        numberType: {
+            type: String,
+            default: uni.$u.props.badge.numberType
+        },
+        // 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
+        offset: {
+            type: Array,
+            default: uni.$u.props.badge.offset
+        },
+        // 是否反转背景和字体颜色
+        inverted: {
+            type: Boolean,
+            default: uni.$u.props.badge.inverted
+        },
+        // 是否绝对定位
+        absolute: {
+            type: Boolean,
+            default: uni.$u.props.badge.absolute
+        }
+    }
+}

+ 171 - 0
plugins/uview-ui/components/u-badge/u-badge.vue

@@ -0,0 +1,171 @@
+<template>
+	<text
+		v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)"
+		:class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]"
+		:style="[$u.addStyle(customStyle), badgeStyle]"
+		class="u-badge"
+	>{{ isDot ? '' :showValue }}</text>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * badge 徽标数
+	 * @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
+	 * @tutorial https://uviewui.com/components/badge.html
+	 * 
+	 * @property {Boolean} 			isDot 		是否显示圆点 (默认 false )
+	 * @property {String | Number} 	value 		显示的内容
+	 * @property {Boolean} 			show 		是否显示 (默认 true )
+	 * @property {String | Number} 	max 		最大值,超过最大值会显示 '{max}+'  (默认999)
+	 * @property {String} 			type 		主题类型,error|warning|success|primary (默认 'error' )
+	 * @property {Boolean} 			showZero	当数值为 0 时,是否展示 Badge (默认 false )
+	 * @property {String} 			bgColor 	背景颜色,优先级比type高,如设置,type参数会失效
+	 * @property {String} 			color 		字体颜色 (默认 '#ffffff' )
+	 * @property {String} 			shape 		徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' )
+	 * @property {String} 			numberType	设置数字的显示方式,overflow|ellipsis|limit  (默认 'overflow' )
+	 * @property {Array}} 			offset		设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
+	 * @property {Boolean} 			inverted	是否反转背景和字体颜色(默认 false )
+	 * @property {Boolean} 			absolute	是否绝对定位(默认 false )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @example <u-badge :type="type" :count="count"></u-badge>
+	 */
+	export default {
+		name: 'u-badge',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		computed: {
+			// 是否将badge中心与父组件右上角重合
+			boxStyle() {
+				let style = {};
+				return style;
+			},
+			// 整个组件的样式
+			badgeStyle() {
+				const style = {}
+				if(this.color) {
+					style.color = this.color
+				}
+				if (this.bgColor && !this.inverted) {
+					style.backgroundColor = this.bgColor
+				}
+				if (this.absolute) {
+					style.position = 'absolute'
+					// 如果有设置offset参数
+					if(this.offset.length) {
+						// top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top
+						const top = this.offset[0]
+						const right = this.offset[1] || top
+						style.top = uni.$u.addUnit(top)
+						style.right = uni.$u.addUnit(right)
+					}
+				}
+				return style
+			},
+			showValue() {
+				switch (this.numberType) {
+					case "overflow":
+						return Number(this.value) > Number(this.max) ? this.max + "+" : this.value
+						break;
+					case "ellipsis":
+						return Number(this.value) > Number(this.max) ? "..." : this.value
+						break;
+					case "limit":
+						return Number(this.value) > 999 ? Number(this.value) >= 9999 ?
+							Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value /
+								1e3 * 100) / 100 + "k" : this.value
+						break;
+					default:
+						return Number(this.value)
+				}
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	$u-badge-primary: $u-primary !default;
+	$u-badge-error: $u-error !default;
+	$u-badge-success: $u-success !default;
+	$u-badge-info: $u-info !default;
+	$u-badge-warning: $u-warning !default;
+	$u-badge-dot-radius: 100px !default;
+	$u-badge-dot-size: 8px !default;
+	$u-badge-dot-right: 4px !default;
+	$u-badge-dot-top: 0 !default;
+	$u-badge-text-font-size: 11px !default;
+	$u-badge-text-right: 10px !default;
+	$u-badge-text-padding: 2px 5px !default;
+	$u-badge-text-align: center !default;
+	$u-badge-text-color: #FFFFFF !default;
+
+	.u-badge {
+		border-top-right-radius: $u-badge-dot-radius;
+		border-top-left-radius: $u-badge-dot-radius;
+		border-bottom-left-radius: $u-badge-dot-radius;
+		border-bottom-right-radius: $u-badge-dot-radius;
+		@include flex;
+		line-height: $u-badge-text-font-size;
+		text-align: $u-badge-text-align;
+		font-size: $u-badge-text-font-size;
+		color: $u-badge-text-color;
+
+		&--dot {
+			height: $u-badge-dot-size;
+			width: $u-badge-dot-size;
+		}
+		
+		&--inverted {
+			font-size: 13px;
+		}
+		
+		&--not-dot {
+			padding: $u-badge-text-padding;
+		}
+
+		&--horn {
+			border-bottom-left-radius: 0;
+		}
+
+		&--primary {
+			background-color: $u-badge-primary;
+		}
+		
+		&--primary--inverted {
+			color: $u-badge-primary;
+		}
+
+		&--error {
+			background-color: $u-badge-error;
+		}
+		
+		&--error--inverted {
+			color: $u-badge-error;
+		}
+
+		&--success {
+			background-color: $u-badge-success;
+		}
+		
+		&--success--inverted {
+			color: $u-badge-success;
+		}
+
+		&--info {
+			background-color: $u-badge-info;
+		}
+		
+		&--info--inverted {
+			color: $u-badge-info;
+		}
+
+		&--warning {
+			background-color: $u-badge-warning;
+		}
+		
+		&--warning--inverted {
+			color: $u-badge-warning;
+		}
+	}
+</style>

+ 46 - 0
plugins/uview-ui/components/u-button/nvue.scss

@@ -0,0 +1,46 @@
+$u-button-active-opacity:0.75 !default;
+$u-button-loading-text-margin-left:4px !default;
+$u-button-text-color: #FFFFFF !default;
+$u-button-text-plain-error-color:$u-error !default;
+$u-button-text-plain-warning-color:$u-warning !default;
+$u-button-text-plain-success-color:$u-success !default;
+$u-button-text-plain-info-color:$u-info !default;
+$u-button-text-plain-primary-color:$u-primary !default;
+.u-button {
+	&--active {
+		opacity: $u-button-active-opacity;
+	}
+	
+	&--active--plain {
+		background-color: rgb(217, 217, 217);
+	}
+	
+	&__loading-text {
+		margin-left:$u-button-loading-text-margin-left;
+	}
+	
+	&__text,
+	&__loading-text {
+		color:$u-button-text-color;
+	}
+	
+	&__text--plain--error {
+		color:$u-button-text-plain-error-color;
+	}
+	
+	&__text--plain--warning {
+		color:$u-button-text-plain-warning-color;
+	}
+	
+	&__text--plain--success{
+		color:$u-button-text-plain-success-color;
+	}
+	
+	&__text--plain--info {
+		color:$u-button-text-plain-info-color;
+	}
+	
+	&__text--plain--primary {
+		color:$u-button-text-plain-primary-color;
+	}
+}

+ 161 - 0
plugins/uview-ui/components/u-button/props.js

@@ -0,0 +1,161 @@
+/*
+ * @Author       : LQ
+ * @Description  :
+ * @version      : 1.0
+ * @Date         : 2021-08-16 10:04:04
+ * @LastAuthor   : LQ
+ * @lastTime     : 2021-08-16 10:04:24
+ * @FilePath     : /u-view2.0/uview-ui/components/u-button/props.js
+ */
+export default {
+    props: {
+        // 是否细边框
+        hairline: {
+            type: Boolean,
+            default: uni.$u.props.button.hairline
+        },
+        // 按钮的预置样式,info,primary,error,warning,success
+        type: {
+            type: String,
+            default: uni.$u.props.button.type
+        },
+        // 按钮尺寸,large,normal,small,mini
+        size: {
+            type: String,
+            default: uni.$u.props.button.size
+        },
+        // 按钮形状,circle(两边为半圆),square(带圆角)
+        shape: {
+            type: String,
+            default: uni.$u.props.button.shape
+        },
+        // 按钮是否镂空
+        plain: {
+            type: Boolean,
+            default: uni.$u.props.button.plain
+        },
+        // 是否禁止状态
+        disabled: {
+            type: Boolean,
+            default: uni.$u.props.button.disabled
+        },
+        // 是否加载中
+        loading: {
+            type: Boolean,
+            default: uni.$u.props.button.loading
+        },
+        // 加载中提示文字
+        loadingText: {
+            type: [String, Number],
+            default: uni.$u.props.button.loadingText
+        },
+        // 加载状态图标类型
+        loadingMode: {
+            type: String,
+            default: uni.$u.props.button.loadingMode
+        },
+        // 加载图标大小
+        loadingSize: {
+            type: [String, Number],
+            default: uni.$u.props.button.loadingSize
+        },
+        // 开放能力,具体请看uniapp稳定关于button组件部分说明
+        // https://uniapp.dcloud.io/component/button
+        openType: {
+            type: String,
+            default: uni.$u.props.button.openType
+        },
+        // 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+        // 取值为submit(提交表单),reset(重置表单)
+        formType: {
+            type: String,
+            default: uni.$u.props.button.formType
+        },
+        // 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
+        // 只微信小程序、QQ小程序有效
+        appParameter: {
+            type: String,
+            default: uni.$u.props.button.appParameter
+        },
+        // 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
+        hoverStopPropagation: {
+            type: Boolean,
+            default: uni.$u.props.button.hoverStopPropagation
+        },
+        // 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
+        lang: {
+            type: String,
+            default: uni.$u.props.button.lang
+        },
+        // 会话来源,open-type="contact"时有效。只微信小程序有效
+        sessionFrom: {
+            type: String,
+            default: uni.$u.props.button.sessionFrom
+        },
+        // 会话内消息卡片标题,open-type="contact"时有效
+        // 默认当前标题,只微信小程序有效
+        sendMessageTitle: {
+            type: String,
+            default: uni.$u.props.button.sendMessageTitle
+        },
+        // 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
+        // 默认当前分享路径,只微信小程序有效
+        sendMessagePath: {
+            type: String,
+            default: uni.$u.props.button.sendMessagePath
+        },
+        // 会话内消息卡片图片,open-type="contact"时有效
+        // 默认当前页面截图,只微信小程序有效
+        sendMessageImg: {
+            type: String,
+            default: uni.$u.props.button.sendMessageImg
+        },
+        // 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
+        // 用户点击后可以快速发送小程序消息,open-type="contact"时有效
+        showMessageCard: {
+            type: Boolean,
+            default: uni.$u.props.button.showMessageCard
+        },
+        // 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+        dataName: {
+            type: String,
+            default: uni.$u.props.button.dataName
+        },
+        // 节流,一定时间内只能触发一次
+        throttleTime: {
+            type: [String, Number],
+            default: uni.$u.props.button.throttleTime
+        },
+        // 按住后多久出现点击态,单位毫秒
+        hoverStartTime: {
+            type: [String, Number],
+            default: uni.$u.props.button.hoverStartTime
+        },
+        // 手指松开后点击态保留时间,单位毫秒
+        hoverStayTime: {
+            type: [String, Number],
+            default: uni.$u.props.button.hoverStayTime
+        },
+        // 按钮文字,之所以通过props传入,是因为slot传入的话
+        // nvue中无法控制文字的样式
+        text: {
+            type: [String, Number],
+            default: uni.$u.props.button.text
+        },
+        // 按钮图标
+        icon: {
+            type: String,
+            default: uni.$u.props.button.icon
+        },
+        // 按钮图标
+        iconColor: {
+            type: String,
+            default: uni.$u.props.button.icon
+        },
+        // 按钮颜色,支持传入linear-gradient渐变色
+        color: {
+            type: String,
+            default: uni.$u.props.button.color
+        }
+    }
+}

+ 493 - 0
plugins/uview-ui/components/u-button/u-button.vue

@@ -0,0 +1,493 @@
+<template>
+    <!-- #ifndef APP-NVUE -->
+    <button
+        :hover-start-time="Number(hoverStartTime)"
+        :hover-stay-time="Number(hoverStayTime)"
+        :form-type="formType"
+        :open-type="openType"
+        :app-parameter="appParameter"
+        :hover-stop-propagation="hoverStopPropagation"
+        :send-message-title="sendMessageTitle"
+        :send-message-path="sendMessagePath"
+        :lang="lang"
+        :data-name="dataName"
+        :session-from="sessionFrom"
+        :send-message-img="sendMessageImg"
+        :show-message-card="showMessageCard"
+        @getphonenumber="getphonenumber"
+        @getuserinfo="getuserinfo"
+        @error="error"
+        @opensetting="opensetting"
+        @launchapp="launchapp"
+        :hover-class="!disabled && !loading ? 'u-button--active' : ''"
+        class="u-button u-reset-button"
+        :style="[baseColor, $u.addStyle(customStyle)]"
+        @tap="clickHandler"
+        :class="bemClass"
+    >
+        <template v-if="loading">
+            <u-loading-icon
+                :mode="loadingMode"
+                :size="loadingSize * 1.15"
+                :color="loadingColor"
+            ></u-loading-icon>
+            <text
+                class="u-button__loading-text"
+                :style="[{ fontSize: textSize + 'px' }]"
+                >{{ loadingText || text }}</text
+            >
+        </template>
+        <template v-else>
+            <u-icon
+                v-if="icon"
+                :name="icon"
+                :color="iconColorCom"
+                :size="textSize * 1.35"
+                :customStyle="{ marginRight: '2px' }"
+            ></u-icon>
+            <slot>
+                <text
+                    class="u-button__text"
+                    :style="[{ fontSize: textSize + 'px' }]"
+                    >{{ text }}</text
+                >
+            </slot>
+        </template>
+    </button>
+    <!-- #endif -->
+
+    <!-- #ifdef APP-NVUE -->
+    <view
+        :hover-start-time="Number(hoverStartTime)"
+        :hover-stay-time="Number(hoverStayTime)"
+        class="u-button"
+        :hover-class="
+            !disabled && !loading && !color && (plain || type === 'info')
+                ? 'u-button--active--plain'
+                : !disabled && !loading && !plain
+                ? 'u-button--active'
+                : ''
+        "
+        @tap="clickHandler"
+        :class="bemClass"
+        :style="[baseColor, $u.addStyle(customStyle)]"
+    >
+        <template v-if="loading">
+            <u-loading-icon
+                :mode="loadingMode"
+                :size="loadingSize * 1.15"
+                :color="loadingColor"
+            ></u-loading-icon>
+            <text
+                class="u-button__loading-text"
+                :style="[nvueTextStyle]"
+                :class="[plain && `u-button__text--plain--${type}`]"
+                >{{ loadingText || text }}</text
+            >
+        </template>
+        <template v-else>
+            <u-icon
+                v-if="icon"
+                :name="icon"
+                :color="iconColorCom"
+                :size="textSize * 1.35"
+            ></u-icon>
+            <text
+                class="u-button__text"
+                :style="[
+                    {
+                        marginLeft: icon ? '2px' : 0,
+                    },
+                    nvueTextStyle,
+                ]"
+                :class="[plain && `u-button__text--plain--${type}`]"
+                >{{ text }}</text
+            >
+        </template>
+    </view>
+    <!-- #endif -->
+</template>
+
+<script>
+import button from "../../libs/mixin/button.js";
+import openType from "../../libs/mixin/openType.js";
+import props from "./props.js";
+/**
+ * button 按钮
+ * @description Button 按钮
+ * @tutorial https://www.uviewui.com/components/button.html
+ *
+ * @property {Boolean}			hairline				是否显示按钮的细边框 (默认 true )
+ * @property {String}			type					按钮的预置样式,info,primary,error,warning,success (默认 'info' )
+ * @property {String}			size					按钮尺寸,large,normal,mini (默认 normal)
+ * @property {String}			shape					按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' )
+ * @property {Boolean}			plain					按钮是否镂空,背景色透明 (默认 false)
+ * @property {Boolean}			disabled				是否禁用 (默认 false)
+ * @property {Boolean}			loading					按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false)
+ * @property {String | Number}	loadingText				加载中提示文字
+ * @property {String}			loadingMode				加载状态图标类型 (默认 'spinner' )
+ * @property {String | Number}	loadingSize				加载图标大小 (默认 15 )
+ * @property {String}			openType				开放能力,具体请看uniapp稳定关于button组件部分说明
+ * @property {String}			formType				用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+ * @property {String}			appParameter			打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效)
+ * @property {Boolean}			hoverStopPropagation	指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true )
+ * @property {String}			lang					指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en )
+ * @property {String}			sessionFrom				会话来源,openType="contact"时有效
+ * @property {String}			sendMessageTitle		会话内消息卡片标题,openType="contact"时有效
+ * @property {String}			sendMessagePath			会话内消息卡片点击跳转小程序路径,openType="contact"时有效
+ * @property {String}			sendMessageImg			会话内消息卡片图片,openType="contact"时有效
+ * @property {Boolean}			showMessageCard			是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false)
+ * @property {String}			dataName				额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+ * @property {String | Number}	throttleTime			节流,一定时间内只能触发一次 (默认 0 )
+ * @property {String | Number}	hoverStartTime			按住后多久出现点击态,单位毫秒 (默认 0 )
+ * @property {String | Number}	hoverStayTime			手指松开后点击态保留时间,单位毫秒 (默认 200 )
+ * @property {String | Number}	text					按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式)
+ * @property {String}			icon					按钮图标
+ * @property {String}			iconColor				按钮图标颜色
+ * @property {String}			color					按钮颜色,支持传入linear-gradient渐变色
+ * @property {Object}			customStyle				定义需要用到的外部样式
+ *
+ * @event {Function}	click			非禁止并且非加载中,才能点击
+ * @event {Function}	getphonenumber	open-type="getPhoneNumber"时有效
+ * @event {Function}	getuserinfo		用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
+ * @event {Function}	error			当使用开放能力时,发生错误的回调
+ * @event {Function}	opensetting		在打开授权设置页并关闭后回调
+ * @event {Function}	launchapp		打开 APP 成功的回调
+ * @example <u-button>月落</u-button>
+ */
+export default {
+    name: "u-button",
+    // #ifdef MP
+    mixins: [uni.$u.mpMixin, uni.$u.mixin, button, openType, props],
+    // #endif
+    // #ifndef MP
+    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+    // #endif
+    options: {
+                styleIsolation: 'shared', 
+            },
+    data() {
+        return {};
+    },
+    computed: {
+        // 生成bem风格的类名
+        bemClass() {
+            // this.bem为一个computed变量,在mixin中
+            if (!this.color) {
+                return this.bem(
+                    "button",
+                    ["type", "shape", "size"],
+                    ["disabled", "plain", "hairline"]
+                );
+            } else {
+                // 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式
+                return this.bem(
+                    "button",
+                    ["shape", "size"],
+                    ["disabled", "plain", "hairline"]
+                );
+            }
+        },
+        loadingColor() {
+            if (this.plain) {
+                // 如果有设置color值,则用color值,否则使用type主题颜色
+                return this.color
+                    ? this.color
+                    : uni.$u.config.color[`u-${this.type}`];
+            }
+            if (this.type === "info") {
+                return "#c9c9c9";
+            }
+            return "rgb(200, 200, 200)";
+        },
+        iconColorCom() {
+            // 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
+            // u-icon的color能接受一个主题颜色的值
+			if (this.iconColor) return this.iconColor;
+			if (this.plain) {
+                return this.color ? this.color : this.type;
+            } else {
+                return this.type === "info" ? "#000000" : "#ffffff";
+            }
+        },
+        baseColor() {
+            let style = {};
+            if (this.color) {
+                // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
+                style.color = this.plain ? this.color : "white";
+                if (!this.plain) {
+                    // 非镂空,背景色使用自定义的颜色
+                    style["background-color"] = this.color;
+                }
+                if (this.color.indexOf("gradient") !== -1) {
+                    // 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色
+                    // weex文档说明可以写borderWidth的形式,为什么这里需要分开写?
+                    // 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效
+                    style.borderTopWidth = 0;
+                    style.borderRightWidth = 0;
+                    style.borderBottomWidth = 0;
+                    style.borderLeftWidth = 0;
+                    if (!this.plain) {
+                        style.backgroundImage = this.color;
+                    }
+                } else {
+                    // 非渐变色,则设置边框相关的属性
+                    style.borderColor = this.color;
+                    style.borderWidth = "1px";
+                    style.borderStyle = "solid";
+                }
+            }
+            return style;
+        },
+        // nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
+        nvueTextStyle() {
+            let style = {};
+            // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
+            if (this.type === "info") {
+                style.color = "#323233";
+            }
+            if (this.color) {
+                style.color = this.plain ? this.color : "white";
+            }
+            style.fontSize = this.textSize + "px";
+            return style;
+        },
+        // 字体大小
+        textSize() {
+            let fontSize = 14,
+                { size } = this;
+            if (size === "large") fontSize = 16;
+            if (size === "normal") fontSize = 14;
+            if (size === "small") fontSize = 12;
+            if (size === "mini") fontSize = 10;
+            return fontSize;
+        },
+    },
+    methods: {
+        clickHandler() {
+            // 非禁止并且非加载中,才能点击
+            if (!this.disabled && !this.loading) {
+				// 进行节流控制,每this.throttle毫秒内,只在开始处执行
+				uni.$u.throttle(() => {
+					this.$emit("click");
+				}, this.throttleTime);
+            }
+        },
+        // 下面为对接uniapp官方按钮开放能力事件回调的对接
+        getphonenumber(res) {
+            this.$emit("getphonenumber", res);
+        },
+        getuserinfo(res) {
+            this.$emit("getuserinfo", res);
+        },
+        error(res) {
+            this.$emit("error", res);
+        },
+        opensetting(res) {
+            this.$emit("opensetting", res);
+        },
+        launchapp(res) {
+            this.$emit("launchapp", res);
+        },
+    },
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../libs/css/components.scss";
+
+/* #ifndef APP-NVUE */
+@import "./vue.scss";
+/* #endif */
+
+/* #ifdef APP-NVUE */
+@import "./nvue.scss";
+/* #endif */
+
+$u-button-u-button-height: 40px !default;
+$u-button-text-font-size: 15px !default;
+$u-button-loading-text-font-size: 15px !default;
+$u-button-loading-text-margin-left: 4px !default;
+$u-button-large-width: 100% !default;
+$u-button-large-height: 50px !default;
+$u-button-normal-padding: 0 12px !default;
+$u-button-large-padding: 0 15px !default;
+$u-button-normal-font-size: 14px !default;
+$u-button-small-min-width: 60px !default;
+$u-button-small-height: 30px !default;
+$u-button-small-padding: 0px 8px !default;
+$u-button-mini-padding: 0px 8px !default;
+$u-button-small-font-size: 12px !default;
+$u-button-mini-height: 22px !default;
+$u-button-mini-font-size: 10px !default;
+$u-button-mini-min-width: 50px !default;
+$u-button-disabled-opacity: 0.5 !default;
+$u-button-info-color: #323233 !default;
+$u-button-info-background-color: #fff !default;
+$u-button-info-border-color: #ebedf0 !default;
+$u-button-info-border-width: 1px !default;
+$u-button-info-border-style: solid !default;
+$u-button-success-color: #fff !default;
+$u-button-success-background-color: $u-success !default;
+$u-button-success-border-color: $u-button-success-background-color !default;
+$u-button-success-border-width: 1px !default;
+$u-button-success-border-style: solid !default;
+$u-button-primary-color: #fff !default;
+$u-button-primary-background-color: $u-primary !default;
+$u-button-primary-border-color: $u-button-primary-background-color !default;
+$u-button-primary-border-width: 1px !default;
+$u-button-primary-border-style: solid !default;
+$u-button-error-color: #fff !default;
+$u-button-error-background-color: $u-error !default;
+$u-button-error-border-color: $u-button-error-background-color !default;
+$u-button-error-border-width: 1px !default;
+$u-button-error-border-style: solid !default;
+$u-button-warning-color: #fff !default;
+$u-button-warning-background-color: $u-warning !default;
+$u-button-warning-border-color: $u-button-warning-background-color !default;
+$u-button-warning-border-width: 1px !default;
+$u-button-warning-border-style: solid !default;
+$u-button-block-width: 100% !default;
+$u-button-circle-border-top-right-radius: 100px !default;
+$u-button-circle-border-top-left-radius: 100px !default;
+$u-button-circle-border-bottom-left-radius: 100px !default;
+$u-button-circle-border-bottom-right-radius: 100px !default;
+$u-button-square-border-top-right-radius: 3px !default;
+$u-button-square-border-top-left-radius: 3px !default;
+$u-button-square-border-bottom-left-radius: 3px !default;
+$u-button-square-border-bottom-right-radius: 3px !default;
+$u-button-icon-min-width: 1em !default;
+$u-button-plain-background-color: #fff !default;
+$u-button-hairline-border-width: 0.5px !default;
+
+.u-button {
+    height: $u-button-u-button-height;
+    position: relative;
+    align-items: center;
+    justify-content: center;
+    @include flex;
+    /* #ifndef APP-NVUE */
+    box-sizing: border-box;
+    /* #endif */
+    flex-direction: row;
+
+    &__text {
+        font-size: $u-button-text-font-size;
+    }
+
+    &__loading-text {
+        font-size: $u-button-loading-text-font-size;
+        margin-left: $u-button-loading-text-margin-left;
+    }
+
+    &--large {
+        /* #ifndef APP-NVUE */
+        width: $u-button-large-width;
+        /* #endif */
+        height: $u-button-large-height;
+        padding: $u-button-large-padding;
+    }
+
+    &--normal {
+        padding: $u-button-normal-padding;
+        font-size: $u-button-normal-font-size;
+    }
+
+    &--small {
+        /* #ifndef APP-NVUE */
+        min-width: $u-button-small-min-width;
+        /* #endif */
+        height: $u-button-small-height;
+        padding: $u-button-small-padding;
+        font-size: $u-button-small-font-size;
+    }
+
+    &--mini {
+        height: $u-button-mini-height;
+        font-size: $u-button-mini-font-size;
+        /* #ifndef APP-NVUE */
+        min-width: $u-button-mini-min-width;
+        /* #endif */
+        padding: $u-button-mini-padding;
+    }
+
+    &--disabled {
+        opacity: $u-button-disabled-opacity;
+    }
+
+    &--info {
+        color: $u-button-info-color;
+        background-color: $u-button-info-background-color;
+        border-color: $u-button-info-border-color;
+        border-width: $u-button-info-border-width;
+        border-style: $u-button-info-border-style;
+    }
+
+    &--success {
+        color: $u-button-success-color;
+        background-color: $u-button-success-background-color;
+        border-color: $u-button-success-border-color;
+        border-width: $u-button-success-border-width;
+        border-style: $u-button-success-border-style;
+    }
+
+    &--primary {
+        color: $u-button-primary-color;
+        background-color: $u-button-primary-background-color;
+        border-color: $u-button-primary-border-color;
+        border-width: $u-button-primary-border-width;
+        border-style: $u-button-primary-border-style;
+    }
+
+    &--error {
+        color: $u-button-error-color;
+        background-color: $u-button-error-background-color;
+        border-color: $u-button-error-border-color;
+        border-width: $u-button-error-border-width;
+        border-style: $u-button-error-border-style;
+    }
+
+    &--warning {
+        color: $u-button-warning-color;
+        background-color: $u-button-warning-background-color;
+        border-color: $u-button-warning-border-color;
+        border-width: $u-button-warning-border-width;
+        border-style: $u-button-warning-border-style;
+    }
+
+    &--block {
+        @include flex;
+        width: $u-button-block-width;
+    }
+
+    &--circle {
+        border-top-right-radius: $u-button-circle-border-top-right-radius;
+        border-top-left-radius: $u-button-circle-border-top-left-radius;
+        border-bottom-left-radius: $u-button-circle-border-bottom-left-radius;
+        border-bottom-right-radius: $u-button-circle-border-bottom-right-radius;
+    }
+
+    &--square {
+        border-bottom-left-radius: $u-button-square-border-top-right-radius;
+        border-bottom-right-radius: $u-button-square-border-top-left-radius;
+        border-top-left-radius: $u-button-square-border-bottom-left-radius;
+        border-top-right-radius: $u-button-square-border-bottom-right-radius;
+    }
+
+    &__icon {
+        /* #ifndef APP-NVUE */
+        min-width: $u-button-icon-min-width;
+        line-height: inherit !important;
+        vertical-align: top;
+        /* #endif */
+    }
+
+    &--plain {
+        background-color: $u-button-plain-background-color;
+    }
+
+    &--hairline {
+        border-width: $u-button-hairline-border-width !important;
+    }
+}
+</style>

+ 80 - 0
plugins/uview-ui/components/u-button/vue.scss

@@ -0,0 +1,80 @@
+// nvue下hover-class无效
+$u-button-before-top:50% !default;
+$u-button-before-left:50% !default;
+$u-button-before-width:100% !default;
+$u-button-before-height:100% !default;
+$u-button-before-transform:translate(-50%, -50%) !default;
+$u-button-before-opacity:0 !default;
+$u-button-before-background-color:#000 !default;
+$u-button-before-border-color:#000 !default;
+$u-button-active-before-opacity:.15 !default;
+$u-button-icon-margin-left:4px !default;
+$u-button-plain-u-button-info-color:$u-info;
+$u-button-plain-u-button-success-color:$u-success;
+$u-button-plain-u-button-error-color:$u-error;
+$u-button-plain-u-button-warning-color:$u-error;
+
+.u-button {
+	width: 100%;
+	
+	&__text {
+		white-space: nowrap;
+		line-height: 1;
+	}
+	
+	&:before {
+		position: absolute;
+		top:$u-button-before-top;
+		left:$u-button-before-left;
+		width:$u-button-before-width;
+		height:$u-button-before-height;
+		border: inherit;
+		border-radius: inherit;
+		transform:$u-button-before-transform;
+		opacity:$u-button-before-opacity;
+		content: " ";
+		background-color:$u-button-before-background-color;
+		border-color:$u-button-before-border-color;
+	}
+	
+	&--active {
+		&:before {
+			opacity: .15
+		}
+	}
+	
+	&__icon+&__text:not(:empty),
+	&__loading-text {
+		margin-left:$u-button-icon-margin-left;
+	}
+	
+	&--plain {
+		&.u-button--primary {
+			color: $u-primary;
+		}
+	}
+	
+	&--plain {
+		&.u-button--info {
+			color:$u-button-plain-u-button-info-color;
+		}
+	}
+	
+	&--plain {
+		&.u-button--success {
+			color:$u-button-plain-u-button-success-color;
+		}
+	}
+	
+	&--plain {
+		&.u-button--error {
+			color:$u-button-plain-u-button-error-color;
+		}
+	}
+	
+	&--plain {
+		&.u-button--warning {
+			color:$u-button-plain-u-button-warning-color;
+		}
+	}
+}

+ 14 - 0
plugins/uview-ui/components/u-cell-group/props.js

@@ -0,0 +1,14 @@
+export default {
+    props: {
+        // 分组标题
+        title: {
+            type: String,
+            default: uni.$u.props.cellGroup.title
+        },
+        // 是否显示外边框
+        border: {
+            type: Boolean,
+            default: uni.$u.props.cellGroup.border
+        }
+    }
+}

+ 61 - 0
plugins/uview-ui/components/u-cell-group/u-cell-group.vue

@@ -0,0 +1,61 @@
+<template>
+    <view :style="[$u.addStyle(customStyle)]" :class="[customClass]" class="u-cell-group">
+        <view v-if="title" class="u-cell-group__title">
+            <slot name="title">
+				<text class="u-cell-group__title__text">{{ title }}</text>
+			</slot>
+        </view>
+        <view class="u-cell-group__wrapper">
+			<u-line v-if="border"></u-line>
+            <slot />
+        </view>
+    </view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * cellGroup  单元格
+	 * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+	 * @tutorial https://uviewui.com/components/cell.html
+	 * 
+	 * @property {String}	title		分组标题
+	 * @property {Boolean}	border		是否显示外边框 (默认 true )
+	 * @property {Object}	customStyle	定义需要用到的外部样式
+	 * 
+	 * @event {Function} click 	点击cell列表时触发
+	 * @example <u-cell-group title="设置喜好">
+	 */
+	export default {
+		name: 'u-cell-group',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+	
+	$u-cell-group-title-padding: 16px 16px 8px !default;
+	$u-cell-group-title-font-size: 15px !default;
+	$u-cell-group-title-line-height: 16px !default;
+	$u-cell-group-title-color: $u-main-color !default;
+
+    .u-cell-group {
+		flex: 1;
+		
+        &__title {
+            padding: $u-cell-group-title-padding;
+
+            &__text {
+                font-size: $u-cell-group-title-font-size;
+                line-height: $u-cell-group-title-line-height;
+                color: $u-cell-group-title-color;
+            }
+        }
+		
+		&__wrapper {
+			position: relative;
+		}
+    }
+</style>
+

+ 110 - 0
plugins/uview-ui/components/u-cell/props.js

@@ -0,0 +1,110 @@
+export default {
+    props: {
+        // 标题
+        title: {
+            type: [String, Number],
+            default: uni.$u.props.cell.title
+        },
+        // 标题下方的描述信息
+        label: {
+            type: [String, Number],
+            default: uni.$u.props.cell.label
+        },
+        // 右侧的内容
+        value: {
+            type: [String, Number],
+            default: uni.$u.props.cell.value
+        },
+        // 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
+        icon: {
+            type: String,
+            default: uni.$u.props.cell.icon
+        },
+        // 是否禁用cell
+        disabled: {
+            type: Boolean,
+            default: uni.$u.props.cell.disabled
+        },
+        // 是否显示下边框
+        border: {
+            type: Boolean,
+            default: uni.$u.props.cell.border
+        },
+        // 内容是否垂直居中(主要是针对右侧的value部分)
+        center: {
+            type: Boolean,
+            default: uni.$u.props.cell.center
+        },
+        // 点击后跳转的URL地址
+        url: {
+            type: String,
+            default: uni.$u.props.cell.url
+        },
+        // 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作
+        linkType: {
+            type: String,
+            default: uni.$u.props.cell.linkType
+        },
+        // 是否开启点击反馈(表现为点击时加上灰色背景)
+        clickable: {
+            type: Boolean,
+            default: uni.$u.props.cell.clickable
+        },
+        // 是否展示右侧箭头并开启点击反馈
+        isLink: {
+            type: Boolean,
+            default: uni.$u.props.cell.isLink
+        },
+        // 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件)
+        required: {
+            type: Boolean,
+            default: uni.$u.props.cell.required
+        },
+        // 右侧的图标箭头
+        rightIcon: {
+            type: String,
+            default: uni.$u.props.cell.rightIcon
+        },
+        // 右侧箭头的方向,可选值为:left,up,down
+        arrowDirection: {
+            type: String,
+            default: uni.$u.props.cell.arrowDirection
+        },
+        // 左侧图标样式
+        iconStyle: {
+            type: [Object, String],
+            default: () => {
+				return uni.$u.props.cell.iconStyle
+			}
+        },
+        // 右侧箭头图标的样式
+        rightIconStyle: {
+            type: [Object, String],
+            default: () => {
+				return uni.$u.props.cell.rightIconStyle
+			}
+        },
+        // 标题的样式
+        titleStyle: {
+            type: [Object, String],
+			default: () => {
+				return uni.$u.props.cell.titleStyle
+			}
+        },
+        // 单位元的大小,可选值为large
+        size: {
+            type: String,
+            default: uni.$u.props.cell.size
+        },
+        // 点击cell是否阻止事件传播
+        stop: {
+            type: Boolean,
+            default: uni.$u.props.cell.stop
+        },
+        // 标识符,cell被点击时返回
+        name: {
+            type: [Number, String],
+            default: uni.$u.props.cell.name
+        }
+    }
+}

+ 229 - 0
plugins/uview-ui/components/u-cell/u-cell.vue

@@ -0,0 +1,229 @@
+<template>
+	<view class="u-cell" :class="[customClass]" :style="[$u.addStyle(customStyle)]"
+		:hover-class="(!disabled && (clickable || isLink)) ? 'u-cell--clickable' : ''" :hover-stay-time="250"
+		@tap="clickHandler">
+		<view class="u-cell__body" :class="[ center && 'u-cell--center', size === 'large' && 'u-cell__body--large']">
+			<view class="u-cell__body__content">
+				<view class="u-cell__left-icon-wrap" v-if="$slots.icon || icon">
+					<slot name="icon" v-if="$slots.icon">
+					</slot>
+					<u-icon v-else :name="icon" :custom-style="iconStyle" :size="size === 'large' ? 22 : 18"></u-icon>
+				</view>
+				<view class="u-cell__title">
+					<slot name="title">
+						<text v-if="title" class="u-cell__title-text" :style="[titleTextStyle]"
+							:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text>
+					</slot>
+					<slot name="label">
+						<text class="u-cell__label" v-if="label"
+							:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__label--large']">{{ label }}</text>
+					</slot>
+				</view>
+			</view>
+			<slot name="value">
+				<text class="u-cell__value"
+					:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__value--large']"
+					v-if="!$u.test.empty(value)">{{ value }}</text>
+			</slot>
+			<view class="u-cell__right-icon-wrap" v-if="$slots['right-icon'] || isLink"
+				:class="[`u-cell__right-icon-wrap--${arrowDirection}`]">
+				<slot name="right-icon" v-if="$slots['right-icon']">
+				</slot>
+				<u-icon v-else :name="rightIcon" :custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'"
+					:size="size === 'large' ? 18 : 16"></u-icon>
+			</view>
+		</view>
+		<u-line v-if="border"></u-line>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * cell  单元格
+	 * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+	 * @tutorial https://uviewui.com/components/cell.html
+	 * @property {String | Number}	title			标题
+	 * @property {String | Number}	label			标题下方的描述信息
+	 * @property {String | Number}	value			右侧的内容
+	 * @property {String}			icon			左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
+	 * @property {Boolean}			disabled		是否禁用cell	
+	 * @property {Boolean}			border			是否显示下边框 (默认 true )
+	 * @property {Boolean}			center			内容是否垂直居中(主要是针对右侧的value部分) (默认 false )
+	 * @property {String}			url				点击后跳转的URL地址
+	 * @property {String}			linkType		链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作 (默认 'navigateTo' )
+	 * @property {Boolean}			clickable		是否开启点击反馈(表现为点击时加上灰色背景) (默认 false ) 
+	 * @property {Boolean}			isLink			是否展示右侧箭头并开启点击反馈 (默认 false )
+	 * @property {Boolean}			required		是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) (默认 false )
+	 * @property {String}			rightIcon		右侧的图标箭头 (默认 'arrow-right')
+	 * @property {String}			arrowDirection	右侧箭头的方向,可选值为:left,up,down
+	 * @property {Object | String}			rightIconStyle	右侧箭头图标的样式
+	 * @property {Object | String}			titleStyle		标题的样式
+	 * @property {Object | String}			iconStyle		左侧图标样式
+	 * @property {String}			size			单位元的大小,可选值为 large,normal,mini 
+	 * @property {Boolean}			stop			点击cell是否阻止事件传播 (默认 true )
+	 * @property {Object}			customStyle		定义需要用到的外部样式
+	 * 
+	 * @event {Function}			click			点击cell列表时触发
+	 * @example 该组件需要搭配cell-group组件使用,见官方文档示例
+	 */
+	export default {
+		name: 'u-cell',
+		data() {
+			return {
+
+			}
+		},
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		computed: {
+			titleTextStyle() {
+				return uni.$u.addStyle(this.titleStyle)
+			}
+		},
+		methods: {
+			// 点击cell
+			clickHandler(e) {
+				if (this.disabled) return
+				this.$emit('click', {
+					name: this.name
+				})
+				// 如果配置了url(此props参数通过mixin引入)参数,跳转页面
+				this.openPage()
+				// 是否阻止事件传播
+				this.stop && this.preventEvent(e)
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	$u-cell-padding: 10px 15px !default;
+	$u-cell-font-size: 15px !default;
+	$u-cell-line-height: 24px !default;
+	$u-cell-color: $u-main-color !default;
+	$u-cell-icon-size: 16px !default;
+	$u-cell-title-font-size: 15px !default;
+	$u-cell-title-line-height: 22px !default;
+	$u-cell-title-color: $u-main-color !default;
+	$u-cell-label-font-size: 12px !default;
+	$u-cell-label-color: $u-tips-color !default;
+	$u-cell-label-line-height: 18px !default;
+	$u-cell-value-font-size: 14px !default;
+	$u-cell-value-color: $u-content-color !default;
+	$u-cell-clickable-color: $u-bg-color !default;
+	$u-cell-disabled-color: #c8c9cc !default;
+	$u-cell-padding-top-large: 13px !default;
+	$u-cell-padding-bottom-large: 13px !default;
+	$u-cell-value-font-size-large: 15px !default;
+	$u-cell-label-font-size-large: 14px !default;
+	$u-cell-title-font-size-large: 16px !default;
+	$u-cell-left-icon-wrap-margin-right: 4px !default;
+	$u-cell-right-icon-wrap-margin-left: 4px !default;
+	$u-cell-title-flex:1 !default;
+	$u-cell-label-margin-top:5px !default;
+
+
+	.u-cell {
+		&__body {
+			@include flex();
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			/* #endif */
+			padding: $u-cell-padding;
+			font-size: $u-cell-font-size;
+			color: $u-cell-color;
+			// line-height: $u-cell-line-height;
+			align-items: center;
+
+			&__content {
+				@include flex(row);
+				align-items: center;
+				flex: 1;
+			}
+
+			&--large {
+				padding-top: $u-cell-padding-top-large;
+				padding-bottom: $u-cell-padding-bottom-large;
+			}
+		}
+
+		&__left-icon-wrap,
+		&__right-icon-wrap {
+			@include flex();
+			align-items: center;
+			// height: $u-cell-line-height;
+			font-size: $u-cell-icon-size;
+		}
+
+		&__left-icon-wrap {
+			margin-right: $u-cell-left-icon-wrap-margin-right;
+		}
+
+		&__right-icon-wrap {
+			margin-left: $u-cell-right-icon-wrap-margin-left;
+			transition: transform 0.3s;
+
+			&--up {
+				transform: rotate(-90deg);
+			}
+
+			&--down {
+				transform: rotate(90deg);
+			}
+		}
+
+		&__title {
+			flex: $u-cell-title-flex;
+
+			&-text {
+				font-size: $u-cell-title-font-size;
+				line-height: $u-cell-title-line-height;
+				color: $u-cell-title-color;
+
+				&--large {
+					font-size: $u-cell-title-font-size-large;
+				}
+			}
+
+		}
+
+		&__label {
+			margin-top: $u-cell-label-margin-top;
+			font-size: $u-cell-label-font-size;
+			color: $u-cell-label-color;
+			line-height: $u-cell-label-line-height;
+
+			&--large {
+				font-size: $u-cell-label-font-size-large;
+			}
+		}
+
+		&__value {
+			text-align: right;
+			font-size: $u-cell-value-font-size;
+			line-height: $u-cell-line-height;
+			color: $u-cell-value-color;
+
+			&--large {
+				font-size: $u-cell-value-font-size-large;
+			}
+		}
+
+		&--clickable {
+			background-color: $u-cell-clickable-color;
+		}
+
+		&--disabled {
+			color: $u-cell-disabled-color;
+			/* #ifndef APP-NVUE */
+			cursor: not-allowed;
+			/* #endif */
+		}
+
+		&--center {
+			align-items: center;
+		}
+	}
+</style>

+ 82 - 0
plugins/uview-ui/components/u-checkbox-group/props.js

@@ -0,0 +1,82 @@
+export default {
+    props: {
+        // 标识符
+        name: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.name
+        },
+        // 绑定的值
+        value: {
+            type: Array,
+            default: uni.$u.props.checkboxGroup.value
+        },
+        // 形状,circle-圆形,square-方形
+        shape: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.shape
+        },
+        // 是否禁用全部checkbox
+        disabled: {
+            type: Boolean,
+            default: uni.$u.props.checkboxGroup.disabled
+        },
+
+        // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+        activeColor: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.activeColor
+        },
+        // 未选中的颜色
+        inactiveColor: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.inactiveColor
+        },
+
+        // 整个组件的尺寸,默认px
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.checkboxGroup.size
+        },
+        // 布局方式,row-横向,column-纵向
+        placement: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.placement
+        },
+        // label的字体大小,px单位
+        labelSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkboxGroup.labelSize
+        },
+        // label的字体颜色
+        labelColor: {
+            type: [String],
+            default: uni.$u.props.checkboxGroup.labelColor
+        },
+        // 是否禁止点击文本操作
+        labelDisabled: {
+            type: Boolean,
+            default: uni.$u.props.checkboxGroup.labelDisabled
+        },
+        // 图标颜色
+        iconColor: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.iconColor
+        },
+        // 图标的大小,单位px
+        iconSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkboxGroup.iconSize
+        },
+        // 勾选图标的对齐方式,left-左边,right-右边
+        iconPlacement: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.iconPlacement
+        },
+        // 竖向配列时,是否显示下划线
+        borderBottom: {
+            type: Boolean,
+            default: uni.$u.props.checkboxGroup.borderBottom
+        }
+
+    }
+}

+ 103 - 0
plugins/uview-ui/components/u-checkbox-group/u-checkbox-group.vue

@@ -0,0 +1,103 @@
+<template>
+	<view
+	    class="u-checkbox-group"
+	    :class="bemClass"
+	>
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * checkboxGroup 复选框组
+	 * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
+	 * @tutorial https://www.uviewui.com/components/checkbox.html
+	 * @property {String}			name			标识符 
+	 * @property {Array}			value			绑定的值
+	 * @property {String}			shape			形状,circle-圆形,square-方形 (默认 'square' )
+	 * @property {Boolean}			disabled		是否禁用全部checkbox (默认 false )
+	 * @property {String}			activeColor		选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 (默认 '#2979ff' )
+	 * @property {String}			inactiveColor	未选中的颜色 (默认 '#c8c9cc' )
+	 * @property {String | Number}	size			整个组件的尺寸 单位px (默认 18 )
+	 * @property {String}			placement		布局方式,row-横向,column-纵向 (默认 'row' )
+	 * @property {String | Number}	labelSize		label的字体大小,px单位  (默认 14 )
+	 * @property {String}			labelColor		label的字体颜色 (默认 '#303133' )
+	 * @property {Boolean}			labelDisabled	是否禁止点击文本操作 (默认 false )
+	 * @property {String}			iconColor		图标颜色 (默认 '#ffffff' )
+	 * @property {String | Number}	iconSize		图标的大小,单位px (默认 12 )
+	 * @property {String}			iconPlacement	勾选图标的对齐方式,left-左边,right-右边  (默认 'left' )
+	 * @property {Boolean}			borderBottom	placement为row时,是否显示下边框 (默认 false )
+	 * @event {Function}	change	任一个checkbox状态发生变化时触发,回调为一个对象
+	 * @event {Function}	input	修改通过v-model绑定的值时触发,回调为一个对象
+	 * @example <u-checkbox-group></u-checkbox-group>
+	 */
+	export default {
+		name: 'u-checkbox-group',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		computed: {
+			// 这里computed的变量,都是子组件u-checkbox需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
+			// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(u-checkbox-group)
+			// 拉取父组件新的变化后的参数
+			parentData() {
+				return [this.value, this.disabled, this.inactiveColor, this.activeColor, this.size, this.labelDisabled, this.shape,
+					this.iconSize, this.borderBottom, this.placement
+				]
+			},
+			bemClass() {
+				// this.bem为一个computed变量,在mixin中
+				return this.bem('checkbox-group', ['placement'])
+			},
+		},
+		watch: {
+			// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
+			parentData() {
+				if (this.children.length) {
+					this.children.map(child => {
+						// 判断子组件(u-checkbox)如果有init方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+						typeof(child.init) === 'function' && child.init()
+					})
+				}
+			},
+		},
+		data() {
+			return {
+
+			}
+		},
+		created() {
+			this.children = []
+		},
+		methods: {
+			// 将其他的checkbox设置为未选中的状态
+			unCheckedOther(childInstance) {
+				const values = []
+				this.children.map(child => {
+					// 将被选中的checkbox,放到数组中返回
+					if (child.isChecked) {
+						values.push(child.name)
+					}
+				})
+				// 发出事件
+				this.$emit('change', values)
+				// 修改通过v-model绑定的值
+				this.$emit('input', values)
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-checkbox-group {
+
+		&--row {
+			@include flex;
+		}
+
+		&--column {
+			@include flex(column);
+		}
+	}
+</style>

+ 69 - 0
plugins/uview-ui/components/u-checkbox/props.js

@@ -0,0 +1,69 @@
+export default {
+    props: {
+        // checkbox的名称
+        name: {
+            type: [String, Number, Boolean],
+            default: uni.$u.props.checkbox.name
+        },
+        // 形状,square为方形,circle为圆型
+        shape: {
+            type: String,
+            default: uni.$u.props.checkbox.shape
+        },
+        // 整体的大小
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.checkbox.size
+        },
+        // 是否默认选中
+        checked: {
+            type: Boolean,
+            default: uni.$u.props.checkbox.checked
+        },
+        // 是否禁用
+        disabled: {
+            type: [String, Boolean],
+            default: uni.$u.props.checkbox.disabled
+        },
+        // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+        activeColor: {
+            type: String,
+            default: uni.$u.props.checkbox.activeColor
+        },
+        // 未选中的颜色
+        inactiveColor: {
+            type: String,
+            default: uni.$u.props.checkbox.inactiveColor
+        },
+        // 图标的大小,单位px
+        iconSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkbox.iconSize
+        },
+        // 图标颜色
+        iconColor: {
+            type: String,
+            default: uni.$u.props.checkbox.iconColor
+        },
+        // label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
+        label: {
+            type: [String, Number],
+            default: uni.$u.props.checkbox.label
+        },
+        // label的字体大小,px单位
+        labelSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkbox.labelSize
+        },
+        // label的颜色
+        labelColor: {
+            type: String,
+            default: uni.$u.props.checkbox.labelColor
+        },
+        // 是否禁止点击提示语选中复选框
+        labelDisabled: {
+            type: [String, Boolean],
+            default: uni.$u.props.checkbox.labelDisabled
+        }
+    }
+}

+ 344 - 0
plugins/uview-ui/components/u-checkbox/u-checkbox.vue

@@ -0,0 +1,344 @@
+<template>
+	<view
+	    class="u-checkbox"
+	    :style="[checkboxStyle]"
+	    @tap.stop="wrapperClickHandler"
+	    :class="[`u-checkbox-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'u-border-bottom']"
+	>
+		<view
+		    class="u-checkbox__icon-wrap"
+		    @tap.stop="iconClickHandler"
+		    :class="iconClasses"
+		    :style="[iconWrapStyle]"
+		>
+			<slot name="icon">
+				<u-icon
+				    class="u-checkbox__icon-wrap__icon"
+				    name="checkbox-mark"
+				    :size="elIconSize"
+				    :color="elIconColor"
+				/>
+			</slot>
+		</view>
+		<text
+		    @tap.stop="labelClickHandler"
+		    :style="{
+				color: elDisabled ? elInactiveColor : elLabelColor,
+				fontSize: elLabelSize,
+				lineHeight: elLabelSize
+			}"
+		>{{label}}</text>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * checkbox  复选框
+	 * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
+	 * @tutorial https://uviewui.com/components/checkbox.html
+	 * @property {String | Number | Boolean}	name			checkbox组件的标示符
+	 * @property {String}						shape			形状,square为方形,circle为圆型
+	 * @property {String | Number}				size			整体的大小
+	 * @property {Boolean}						checked			是否默认选中
+	 * @property {String | Boolean}				disabled		是否禁用
+	 * @property {String}						activeColor		选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+	 * @property {String}						inactiveColor	未选中的颜色
+	 * @property {String | Number}				iconSize		图标的大小,单位px
+	 * @property {String}						iconColor		图标颜色
+	 * @property {String | Number}				label			label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
+	 * @property {String}						labelColor 		label的颜色
+	 * @property {String | Number}				labelSize		label的字体大小,px单位
+	 * @property {String | Boolean}				labelDisabled	是否禁止点击提示语选中复选框
+	 * @property {Object}						customStyle		定义需要用到的外部样式
+	 * 
+	 * @event {Function}	change	任一个checkbox状态发生变化时触发,回调为一个对象
+	 * @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox>
+	 */
+	export default {
+		name: "u-checkbox",
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		data() {
+			return {
+				isChecked: false,
+				// 父组件的默认值,因为头条小程序不支持在computed中使用this.parent.shape的形式
+				// 故只能使用如此方法
+				parentData: {
+					iconSize: 12,
+					labelDisabled: null,
+					disabled: null,
+					shape: 'square',
+					activeColor: null,
+					inactiveColor: null,
+					size: 18,
+					value: null,
+					iconColor: null,
+					placement: 'row',
+					borderBottom: false,
+					iconPlacement: 'left'
+				}
+			}
+		},
+		computed: {
+			// 是否禁用,如果父组件u-raios-group禁用的话,将会忽略子组件的配置
+			elDisabled() {
+				return this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;
+			},
+			// 是否禁用label点击
+			elLabelDisabled() {
+				return this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled :
+					false;
+			},
+			// 组件尺寸,对应size的值,默认值为21px
+			elSize() {
+				return this.size ? this.size : (this.parentData.size ? this.parentData.size : 21);
+			},
+			// 组件的勾选图标的尺寸,默认12px
+			elIconSize() {
+				return this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12);
+			},
+			// 组件选中激活时的颜色
+			elActiveColor() {
+				return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff');
+			},
+			// 组件选未中激活时的颜色
+			elInactiveColor() {
+				return this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor :
+					'#c8c9cc');
+			},
+			// label的颜色
+			elLabelColor() {
+				return this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266')
+			},
+			// 组件的形状
+			elShape() {
+				return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');
+			},
+			// label大小
+			elLabelSize() {
+				return uni.$u.addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize :
+					'15'))
+			},
+			elIconColor() {
+				const iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor :
+					'#ffffff');
+				// 图标的颜色
+				if (this.elDisabled) {
+					// disabled状态下,已勾选的checkbox图标改为elInactiveColor
+					return this.isChecked ? this.elInactiveColor : 'transparent'
+				} else {
+					return this.isChecked ? iconColor : 'transparent'
+				}
+			},
+			iconClasses() {
+				let classes = []
+				// 组件的形状
+				classes.push('u-checkbox__icon-wrap--' + this.elShape)
+				if (this.elDisabled) {
+					classes.push('u-checkbox__icon-wrap--disabled')
+				}
+				if (this.isChecked && this.elDisabled) {
+					classes.push('u-checkbox__icon-wrap--disabled--checked')
+				}
+				// 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
+				// #ifdef MP-ALIPAY || MP-TOUTIAO
+				classes = classes.join(' ')
+				// #endif
+				return classes
+			},
+			iconWrapStyle() {
+				// checkbox的整体样式
+				const style = {}
+				style.backgroundColor = this.isChecked && !this.elDisabled ? this.elActiveColor : '#ffffff'
+				style.borderColor = this.isChecked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor
+				style.width = uni.$u.addUnit(this.elSize)
+				style.height = uni.$u.addUnit(this.elSize)
+				// 如果是图标在右边的话,移除它的右边距
+				if (this.parentData.iconPlacement === 'right') {
+					style.marginRight = 0
+				}
+				return style
+			},
+			checkboxStyle() {
+				const style = {}
+				if (this.parentData.borderBottom && this.parentData.placement === 'row') {
+					uni.$u.error('检测到您将borderBottom设置为true,需要同时将u-checkbox-group的placement设置为column才有效')
+				}
+				// 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔
+				if (this.parentData.borderBottom && this.parentData.placement === 'column') {
+					style.paddingBottom = '8px'
+				}
+				return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
+				this.updateParentData()
+				if (!this.parent) {
+					uni.$u.error('u-checkbox必须搭配u-checkbox-group组件使用')
+				}
+				// 设置初始化时,是否默认选中的状态,父组件u-checkbox-group的value可能是array,所以额外判断
+				if (this.checked) {
+					this.isChecked = true
+				} else if (uni.$u.test.array(this.parentData.value)) {
+					// 查找数组是是否存在this.name元素值
+					this.isChecked = this.parentData.value.some(item => {
+						return item === this.name
+					})
+				}
+			},
+			updateParentData() {
+				this.getParentData('u-checkbox-group')
+			},
+			// 横向两端排列时,点击组件即可触发选中事件
+			wrapperClickHandler(e) {
+				this.parentData.iconPlacement === 'right' && this.iconClickHandler(e)
+			},
+			// 点击图标
+			iconClickHandler(e) {
+				this.preventEvent(e)
+				// 如果整体被禁用,不允许被点击
+				if (!this.elDisabled) {
+					this.setRadioCheckedStatus()
+				}
+			},
+			// 点击label
+			labelClickHandler(e) {
+				this.preventEvent(e)
+				// 如果按钮整体被禁用或者label被禁用,则不允许点击文字修改状态
+				if (!this.elLabelDisabled && !this.elDisabled) {
+					this.setRadioCheckedStatus()
+				}
+			},
+			emitEvent() {
+				this.$emit('change', this.isChecked)
+				// 尝试调用u-form的验证方法,进行一定延迟,否则微信小程序更新可能会不及时
+				this.$nextTick(() => {
+					uni.$u.formValidate(this, 'change')
+				})
+			},
+			// 改变组件选中状态
+			// 这里的改变的依据是,更改本组件的checked值为true,同时通过父组件遍历所有u-checkbox实例
+			// 将本组件外的其他u-checkbox的checked都设置为false(都被取消选中状态),因而只剩下一个为选中状态
+			setRadioCheckedStatus() {
+				// 将本组件标记为与原来相反的状态
+				this.isChecked = !this.isChecked
+				this.emitEvent()
+				typeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this)
+			}
+		},
+		watch:{
+			checked(){
+				this.isChecked = this.checked
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+	$u-checkbox-icon-wrap-margin-right:6px !default;
+	$u-checkbox-icon-wrap-font-size:6px !default;
+	$u-checkbox-icon-wrap-border-width:1px !default;
+	$u-checkbox-icon-wrap-border-color:#c8c9cc !default;
+	$u-checkbox-icon-wrap-icon-line-height:0 !default;
+	$u-checkbox-icon-wrap-circle-border-radius:100% !default;
+	$u-checkbox-icon-wrap-square-border-radius:3px !default;
+	$u-checkbox-icon-wrap-checked-color:#fff !default;
+	$u-checkbox-icon-wrap-checked-background-color:red !default;
+	$u-checkbox-icon-wrap-checked-border-color:#2979ff !default;
+	$u-checkbox-icon-wrap-disabled-background-color:#ebedf0 !default;
+	$u-checkbox-icon-wrap-disabled-checked-color:#c8c9cc !default;
+	$u-checkbox-label-margin-left:5px !default;
+	$u-checkbox-label-margin-right:12px !default;
+	$u-checkbox-label-color:$u-content-color !default;
+	$u-checkbox-label-font-size:15px !default;
+	$u-checkbox-label-disabled-color:#c8c9cc !default;
+
+	.u-checkbox {
+		/* #ifndef APP-NVUE */
+		@include flex(row);
+		/* #endif */
+		overflow: hidden;
+		flex-direction: row;
+		align-items: center;
+
+		&-label--left {
+			flex-direction: row
+		}
+
+		&-label--right {
+			flex-direction: row-reverse;
+			justify-content: space-between
+		}
+
+		&__icon-wrap {
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			// nvue下,border-color过渡有问题
+			transition-property: border-color, background-color, color;
+			transition-duration: 0.2s;
+			/* #endif */
+			color: $u-content-color;
+			@include flex;
+			align-items: center;
+			justify-content: center;
+			color: transparent;
+			text-align: center;
+			margin-right: $u-checkbox-icon-wrap-margin-right;
+
+			font-size: $u-checkbox-icon-wrap-font-size;
+			border-width: $u-checkbox-icon-wrap-border-width;
+			border-color: $u-checkbox-icon-wrap-border-color;
+			border-style: solid;
+
+			/* #ifdef MP-TOUTIAO */
+			// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
+			&__icon {
+				line-height: $u-checkbox-icon-wrap-icon-line-height;
+			}
+
+			/* #endif */
+
+			&--circle {
+				border-radius: $u-checkbox-icon-wrap-circle-border-radius;
+			}
+
+			&--square {
+				border-radius: $u-checkbox-icon-wrap-square-border-radius;
+			}
+
+			&--checked {
+				color: $u-checkbox-icon-wrap-checked-color;
+				background-color: $u-checkbox-icon-wrap-checked-background-color;
+				border-color: $u-checkbox-icon-wrap-checked-border-color;
+			}
+
+			&--disabled {
+				background-color: $u-checkbox-icon-wrap-disabled-background-color !important;
+			}
+
+			&--disabled--checked {
+				color: $u-checkbox-icon-wrap-disabled-checked-color !important;
+			}
+		}
+
+		&__label {
+			/* #ifndef APP-NVUE */
+			word-wrap: break-word;
+			/* #endif */
+			margin-left: $u-checkbox-label-margin-left;
+			margin-right: $u-checkbox-label-margin-right;
+			color: $u-checkbox-label-color;
+			font-size: $u-checkbox-label-font-size;
+
+			&--disabled {
+				color: $u-checkbox-label-disabled-color;
+			}
+		}
+	}
+</style>

+ 79 - 0
plugins/uview-ui/components/u-code-input/props.js

@@ -0,0 +1,79 @@
+export default {
+    props: {
+		// 键盘弹起时,是否自动上推页面
+		adjustPosition: {
+			type: Boolean,
+            default: uni.$u.props.codeInput.adjustPosition
+		},
+        // 最大输入长度
+        maxlength: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.maxlength
+        },
+        // 是否用圆点填充
+        dot: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.dot
+        },
+        // 显示模式,box-盒子模式,line-底部横线模式
+        mode: {
+            type: String,
+            default: uni.$u.props.codeInput.mode
+        },
+        // 是否细边框
+        hairline: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.hairline
+        },
+        // 字符间的距离
+        space: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.space
+        },
+        // 预置值
+        value: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.value
+        },
+        // 是否自动获取焦点
+        focus: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.focus
+        },
+        // 字体是否加粗
+        bold: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.bold
+        },
+        // 字体颜色
+        color: {
+            type: String,
+            default: uni.$u.props.codeInput.color
+        },
+        // 字体大小
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.fontSize
+        },
+        // 输入框的大小,宽等于高
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.size
+        },
+        // 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true
+        disabledKeyboard: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.disabledKeyboard
+        },
+        // 边框和线条颜色
+        borderColor: {
+            type: String,
+            default: uni.$u.props.codeInput.borderColor
+        },
+		// 是否禁止输入"."符号
+		disabledDot: {
+			type: Boolean,
+			default: uni.$u.props.codeInput.disabledDot
+		}
+    }
+}

+ 252 - 0
plugins/uview-ui/components/u-code-input/u-code-input.vue

@@ -0,0 +1,252 @@
+<template>
+	<view class="u-code-input">
+		<view
+			class="u-code-input__item"
+			:style="[itemStyle(index)]"
+			v-for="(item, index) in codeLength"
+			:key="index"
+		>
+			<view
+				class="u-code-input__item__dot"
+				v-if="dot && codeArray.length > index"
+			></view>
+			<text
+				v-else
+				:style="{
+					fontSize: $u.addUnit(fontSize),
+					fontWeight: bold ? 'bold' : 'normal',
+					color: color
+				}"
+			>{{codeArray[index]}}</text>
+			<view
+				class="u-code-input__item__line"
+				v-if="mode === 'line'"
+				:style="[lineStyle]"
+			></view>
+			<!-- #ifndef APP-PLUS -->
+			<view v-if="isFocus && codeArray.length === index" :style="{backgroundColor: color}" class="u-code-input__item__cursor"></view>
+			<!-- #endif -->
+		</view>
+		<input
+			:disabled="disabledKeyboard"
+			type="number"
+			:focus="focus"
+			:value="inputValue"
+			:maxlength="maxlength"
+			:adjustPosition="adjustPosition"
+			class="u-code-input__input"
+			@input="inputHandler"
+			:style="{
+				height: $u.addUnit(size) 
+			}"
+			@focus="isFocus = true"
+			@blur="isFocus = false"
+		/>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * CodeInput 验证码输入
+	 * @description 该组件一般用于验证用户短信验证码的场景,也可以结合uView的键盘组件使用
+	 * @tutorial https://www.uviewui.com/components/codeInput.html
+	 * @property {String | Number}	maxlength			最大输入长度 (默认 6 )
+	 * @property {Boolean}			dot					是否用圆点填充 (默认 false )
+	 * @property {String}			mode				显示模式,box-盒子模式,line-底部横线模式 (默认 'box' )
+	 * @property {Boolean}			hairline			是否细边框 (默认 false )
+	 * @property {String | Number}	space				字符间的距离 (默认 10 )
+	 * @property {String | Number}	value				预置值
+	 * @property {Boolean}			focus				是否自动获取焦点 (默认 false )
+	 * @property {Boolean}			bold				字体和输入横线是否加粗 (默认 false )
+	 * @property {String}			color				字体颜色 (默认 '#606266' )
+	 * @property {String | Number}	fontSize			字体大小,单位px (默认 18 )
+	 * @property {String | Number}	size				输入框的大小,宽等于高 (默认 35 )
+	 * @property {Boolean}			disabledKeyboard	是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true (默认 false )
+	 * @property {String}			borderColor			边框和线条颜色 (默认 '#c9cacc' )
+	 * @property {Boolean}			disabledDot			是否禁止输入"."符号 (默认 true )
+	 * 
+	 * @event {Function}	change	输入内容发生改变时触发,具体见上方说明			value:当前输入的值
+	 * @event {Function}	finish	输入字符个数达maxlength值时触发,见上方说明	value:当前输入的值
+	 * @example	<u-code-input v-model="value4" :focus="true"></u-code-input>
+	 */
+	export default {
+		name: 'u-code-input',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+				inputValue: '',
+				isFocus: this.focus
+			}
+		},
+		watch: {
+			value: {
+				immediate: true,
+				handler(val) {
+					// 转为字符串,超出部分截掉
+					this.inputValue = String(val).substring(0, this.maxlength)
+				}
+			},
+		},
+		computed: {
+			// 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for
+			codeLength() {
+				return new Array(Number(this.maxlength))
+			},
+			// 循环item的样式
+			itemStyle() {
+				return index => {
+					const addUnit = uni.$u.addUnit
+					const style = {
+						width: addUnit(this.size),
+						height: addUnit(this.size)
+					}
+					// 盒子模式下,需要额外进行处理
+					if (this.mode === 'box') {
+						// 设置盒子的边框,如果是细边框,则设置为0.5px宽度
+						style.border = `${this.hairline ? 0.5 : 1}px solid ${this.borderColor}`
+						// 如果盒子间距为0的话
+						if (uni.$u.getPx(this.space) === 0) {
+							// 给第一和最后一个盒子设置圆角
+							if (index === 0) {
+								style.borderTopLeftRadius = '3px'
+								style.borderBottomLeftRadius = '3px'
+							}
+							if (index === this.codeLength.length - 1) {
+								style.borderTopRightRadius = '3px'
+								style.borderBottomRightRadius = '3px'
+							}
+							// 最后一个盒子的右边框需要保留
+							if (index !== this.codeLength.length - 1) {
+								style.borderRight = 'none'
+							}
+						}
+					}
+					if (index !== this.codeLength.length - 1) {
+						// 设置验证码字符之间的距离,通过margin-right设置,最后一个字符,无需右边框
+						style.marginRight = addUnit(this.space)
+					} else {
+						// 最后一个盒子的有边框需要保留
+						style.marginRight = 0
+					}
+
+					return style
+				}
+			},
+			// 将输入的值,转为数组,给item历遍时,根据当前的索引显示数组的元素
+			codeArray() {
+				return String(this.inputValue).split('')
+			},
+			// 下划线模式下,横线的样式
+			lineStyle() {
+				const style = {}
+				style.height = this.hairline ? '2px' : '4px'
+				style.width = uni.$u.addUnit(this.size)
+				// 线条模式下,背景色即为边框颜色
+				style.backgroundColor = this.borderColor
+				return style
+			}
+		},
+		methods: {
+			// 监听输入框的值发生变化
+			inputHandler(e) {
+				const value = e.detail.value
+				this.inputValue = value
+				// 是否允许输入“.”符号
+				if(this.disabledDot) {
+					this.$nextTick(() => {
+						this.inputValue = value.replace('.', '')
+					})
+				}
+				// 未达到maxlength之前,发送change事件,达到后发送finish事件
+				this.$emit('change', value)
+				// 修改通过v-model双向绑定的值
+				this.$emit('input', value)
+				// 达到用户指定输入长度时,发出完成事件
+				if (String(value).length >= Number(this.maxlength)) {
+					this.$emit('finish', value)
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+	$u-code-input-cursor-width: 1px;
+	$u-code-input-cursor-height: 40%;
+	$u-code-input-cursor-animation-duration: 1s;
+	$u-code-input-cursor-animation-name: u-cursor-flicker;
+
+	.u-code-input {
+		@include flex;
+		position: relative;
+		overflow: hidden;
+
+		&__item {
+			@include flex;
+			justify-content: center;
+			align-items: center;
+			position: relative;
+
+			&__text {
+				font-size: 15px;
+				color: $u-content-color;
+			}
+
+			&__dot {
+				width: 7px;
+				height: 7px;
+				border-radius: 100px;
+				background-color: $u-content-color;
+			}
+
+			&__line {
+				position: absolute;
+				bottom: 0;
+				height: 4px;
+				border-radius: 100px;
+				width: 40px;
+				background-color: $u-content-color;
+			}
+			/* #ifndef APP-PLUS */
+			&__cursor {
+				position: absolute;
+				top: 50%;
+				left: 50%;
+				transform: translate(-50%,-50%);
+				width: $u-code-input-cursor-width;
+				height: $u-code-input-cursor-height;
+				animation: $u-code-input-cursor-animation-duration u-cursor-flicker infinite;
+			}
+			/* #endif */
+			
+		}
+
+		&__input {
+			// 之所以需要input输入框,是因为有它才能唤起键盘
+			// 这里将它设置为两倍的屏幕宽度,再将左边的一半移出屏幕,为了不让用户看到输入的内容
+			position: absolute;
+			left: -750rpx;
+			width: 1500rpx;
+			top: 0;
+			background-color: transparent;
+			text-align: left;
+		}
+	}
+	
+	/* #ifndef APP-PLUS */
+	@keyframes u-cursor-flicker {
+		0% {
+		    opacity: 0;
+		}
+		50% {
+		    opacity: 1;
+		}
+		100% {
+		    opacity: 0;
+		}
+	}
+	/* #endif */
+
+</style>

+ 34 - 0
plugins/uview-ui/components/u-code/props.js

@@ -0,0 +1,34 @@
+export default {
+    props: {
+        // 倒计时总秒数
+        seconds: {
+            type: [String, Number],
+            default: uni.$u.props.code.seconds
+        },
+        // 尚未开始时提示
+        startText: {
+            type: String,
+            default: uni.$u.props.code.startText
+        },
+        // 正在倒计时中的提示
+        changeText: {
+            type: String,
+            default: uni.$u.props.code.changeText
+        },
+        // 倒计时结束时的提示
+        endText: {
+            type: String,
+            default: uni.$u.props.code.endText
+        },
+        // 是否在H5刷新或各端返回再进入时继续倒计时
+        keepRunning: {
+            type: Boolean,
+            default: uni.$u.props.code.keepRunning
+        },
+        // 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
+        uniqueKey: {
+            type: String,
+            default: uni.$u.props.code.uniqueKey
+        }
+    }
+}

+ 129 - 0
plugins/uview-ui/components/u-code/u-code.vue

@@ -0,0 +1,129 @@
+<template>
+	<view class="u-code">
+		<!-- 此组件功能由js完成,无需写html逻辑 -->
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * Code 验证码输入框
+	 * @description 考虑到用户实际发送验证码的场景,可能是一个按钮,也可能是一段文字,提示语各有不同,所以本组件 不提供界面显示,只提供提示语,由用户将提示语嵌入到具体的场景
+	 * @tutorial https://www.uviewui.com/components/code.html
+	 * @property {String | Number}	seconds			倒计时所需的秒数(默认 60 )
+	 * @property {String}			startText		开始前的提示语,见官网说明(默认 '获取验证码' )
+	 * @property {String}			changeText		倒计时期间的提示语,必须带有字母"x",见官网说明(默认 'X秒重新获取' )
+	 * @property {String}			endText			倒计结束的提示语,见官网说明(默认 '重新获取' )
+	 * @property {Boolean}			keepRunning		是否在H5刷新或各端返回再进入时继续倒计时( 默认false )
+	 * @property {String}			uniqueKey		为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
+	 *
+	 * @event {Function}	change	倒计时期间,每秒触发一次
+	 * @event {Function}	start	开始倒计时触发
+	 * @event {Function}	end		结束倒计时触发
+	 * @example <u-code ref="uCode" @change="codeChange" seconds="20"></u-code>
+	 */
+	export default {
+		name: "u-code",
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		data() {
+			return {
+				secNum: this.seconds,
+				timer: null,
+				canGetCode: true, // 是否可以执行验证码操作
+			}
+		},
+		mounted() {
+			this.checkKeepRunning()
+		},
+		watch: {
+			seconds: {
+				immediate: true,
+				handler(n) {
+					this.secNum = n
+				}
+			}
+		},
+		methods: {
+			checkKeepRunning() {
+				// 获取上一次退出页面(H5还包括刷新)时的时间戳,如果没有上次的保存,此值可能为空
+				let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$uCountDownTimestamp'))
+				if(!lastTimestamp) return this.changeEvent(this.startText)
+				// 当前秒的时间戳
+				let nowTimestamp = Math.floor((+ new Date()) / 1000)
+				// 判断当前的时间戳,是否小于上一次的本该按设定结束,却提前结束的时间戳
+				if(this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {
+					// 剩余尚未执行完的倒计秒数
+					this.secNum = lastTimestamp - nowTimestamp
+					// 清除本地保存的变量
+					uni.removeStorageSync(this.uniqueKey + '_$uCountDownTimestamp')
+					// 开始倒计时
+					this.start()
+				} else {
+					// 如果不存在需要继续上一次的倒计时,执行正常的逻辑
+					this.changeEvent(this.startText)
+				}
+			},
+			// 开始倒计时
+			start() {
+				// 防止快速点击获取验证码的按钮而导致内部产生多个定时器导致混乱
+				if(this.timer) {
+					clearInterval(this.timer)
+					this.timer = null
+				}
+				this.$emit('start')
+				this.canGetCode = false
+				// 这里放这句,是为了一开始时就提示,否则要等setInterval的1秒后才会有提示
+				this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
+				this.timer = setInterval(() => {
+					if (--this.secNum) {
+						// 用当前倒计时的秒数替换提示字符串中的"x"字母
+						this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
+					} else {
+						clearInterval(this.timer)
+						this.timer = null
+						this.changeEvent(this.endText)
+						this.secNum = this.seconds
+						this.$emit('end')
+						this.canGetCode = true
+					}
+				}, 1000)
+        this.setTimeToStorage()
+      },
+			// 重置,可以让用户再次获取验证码
+			reset() {
+				this.canGetCode = true
+				clearInterval(this.timer)
+				this.secNum = this.seconds
+				this.changeEvent(this.endText)
+			},
+			changeEvent(text) {
+				this.$emit('change', text)
+			},
+			// 保存时间戳,为了防止倒计时尚未结束,H5刷新或者各端的右上角返回上一页再进来
+			setTimeToStorage() {
+				if(!this.keepRunning || !this.timer) return
+				// 记录当前的时间戳,为了下次进入页面,如果还在倒计时内的话,继续倒计时
+				// 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理
+				if(this.secNum > 0 && this.secNum <= this.seconds) {
+					// 获取当前时间戳(+ new Date()为特殊写法),除以1000变成秒,再去除小数部分
+					let nowTimestamp = Math.floor((+ new Date()) / 1000)
+					// 将本该结束时候的时间戳保存起来 => 当前时间戳 + 剩余的秒数
+					uni.setStorage({
+						key: this.uniqueKey + '_$uCountDownTimestamp',
+						data: nowTimestamp + Number(this.secNum)
+					})
+				}
+			}
+		},
+		// 组件销毁的时候,清除定时器,否则定时器会继续存在,系统不会自动清除
+		beforeDestroy() {
+			this.setTimeToStorage()
+			clearTimeout(this.timer)
+			this.timer = null
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+</style>

+ 48 - 0
plugins/uview-ui/components/u-form-item/props.js

@@ -0,0 +1,48 @@
+export default {
+    props: {
+        // input的label提示语
+        label: {
+            type: String,
+            default: uni.$u.props.formItem.label
+        },
+        // 绑定的值
+        prop: {
+            type: String,
+            default: uni.$u.props.formItem.prop
+        },
+        // 是否显示表单域的下划线边框
+        borderBottom: {
+            type: [String, Boolean],
+            default: uni.$u.props.formItem.borderBottom
+        },
+        // label的位置,left-左边,top-上边
+        labelPosition: {
+            type: String,
+            default: uni.$u.props.formItem.labelPosition
+        },
+        // label的宽度,单位px
+        labelWidth: {
+            type: [String, Number],
+            default: uni.$u.props.formItem.labelWidth
+        },
+        // 右侧图标
+        rightIcon: {
+            type: String,
+            default: uni.$u.props.formItem.rightIcon
+        },
+        // 左侧图标
+        leftIcon: {
+            type: String,
+            default: uni.$u.props.formItem.leftIcon
+        },
+        // 是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置
+        required: {
+            type: Boolean,
+            default: uni.$u.props.formItem.required
+        },
+        leftIconStyle: {
+            type: [String, Object],
+            default: uni.$u.props.formItem.leftIconStyle,
+        }
+    }
+}

+ 238 - 0
plugins/uview-ui/components/u-form-item/u-form-item.vue

@@ -0,0 +1,238 @@
+<template>
+	<view class="u-form-item">
+		<view
+			class="u-form-item__body"
+			@tap="clickHandler"
+			:style="[$u.addStyle(customStyle), {
+				flexDirection: (labelPosition || parentData.labelPosition) === 'left' ? 'row' : 'column'
+			}]"
+		>
+			<!-- 微信小程序中,将一个参数设置空字符串,结果会变成字符串"true" -->
+			<slot name="label">
+				<!-- {{required}} -->
+				<view
+					class="u-form-item__body__left"
+					v-if="required || leftIcon || label"
+					:style="{
+						width: $u.addUnit(labelWidth || parentData.labelWidth),
+						marginBottom: parentData.labelPosition === 'left' ? 0 : '5px',
+					}"
+				>
+					<!-- 为了块对齐 -->
+					<view class="u-form-item__body__left__content">
+						<!-- nvue不支持伪元素before -->
+						<text
+							v-if="required"
+							class="u-form-item__body__left__content__required"
+						>*</text>
+						<view
+							class="u-form-item__body__left__content__icon"
+							v-if="leftIcon"
+						>
+							<u-icon
+								:name="leftIcon"
+								:custom-style="leftIconStyle"
+							></u-icon>
+						</view>
+						<text
+							class="u-form-item__body__left__content__label"
+							:style="[parentData.labelStyle, {
+								justifyContent: parentData.labelAlign === 'left' ? 'flex-start' : parentData.labelAlign === 'center' ? 'center' : 'flex-end'
+							}]"
+						>{{ label }}</text>
+					</view>
+				</view>
+			</slot>
+			<view class="u-form-item__body__right">
+				<view class="u-form-item__body__right__content">
+					<view class="u-form-item__body__right__content__slot">
+						<slot />
+					</view>
+					<view
+						class="item__body__right__content__icon"
+						v-if="$slots.right"
+					>
+						<slot name="right" />
+					</view>
+				</view>
+			</view>
+		</view>
+		<slot name="error">
+			<text
+				v-if="!!message && parentData.errorType === 'message'"
+				class="u-form-item__body__right__message"
+				:style="{
+					marginLeft:  $u.addUnit(parentData.labelPosition === 'top' ? 0 : (labelWidth || parentData.labelWidth))
+				}"
+			>{{ message }}</text>
+		</slot>
+		<u-line
+			v-if="borderBottom"
+			:color="message && parentData.errorType === 'border-bottom' ? $u.color.error : propsLine.color"
+			:customStyle="`margin-top: ${message && parentData.errorType === 'message' ? '5px' : 0}`"
+		></u-line>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * Form 表单
+	 * @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
+	 * @tutorial https://www.uviewui.com/components/form.html
+	 * @property {String}			label			input的label提示语
+	 * @property {String}			prop			绑定的值
+	 * @property {String | Boolean}	borderBottom	是否显示表单域的下划线边框
+	 * @property {String | Number}	labelWidth		label的宽度,单位px
+	 * @property {String}			rightIcon		右侧图标
+	 * @property {String}			leftIcon		左侧图标
+	 * @property {String | Object} leftIconStyle 左侧图标的样式
+	 * @property {Boolean}			required		是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置 (默认 false )
+	 *
+	 * @example <u-form-item label="姓名" prop="userInfo.name" borderBottom ref="item1"></u-form-item>
+	 */
+	export default {
+		name: 'u-form-item',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+    options: {
+      styleIsolation: 'shared', 
+    },
+		data() {
+			return {
+				// 错误提示语
+				message: '',
+				parentData: {
+					// 提示文本的位置
+					labelPosition: 'left',
+					// 提示文本对齐方式
+					labelAlign: 'left',
+					// 提示文本的样式
+					labelStyle: {},
+					// 提示文本的宽度
+					labelWidth: 45,
+					// 错误提示方式
+					errorType: 'message'
+				}
+			}
+		},
+		// 组件创建完成时,将当前实例保存到u-form中
+		computed: {
+			propsLine() {
+				return uni.$u.props.line
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				// 父组件的实例
+				this.updateParentData()
+				if (!this.parent) {
+					uni.$u.error('u-form-item需要结合u-form组件使用')
+				}
+			},
+			// 获取父组件的参数
+			updateParentData() {
+				// 此方法写在mixin中
+				this.getParentData('u-form');
+			},
+			// 移除u-form-item的校验结果
+			clearValidate() {
+				this.message = null
+			},
+			// 清空当前的组件的校验结果,并重置为初始值
+			resetField() {
+				// 找到原始值
+				const value = uni.$u.getProperty(this.parent.originalModel, this.prop)
+				// 将u-form的model的prop属性链还原原始值
+				uni.$u.setProperty(this.parent.model, this.prop, value)
+				// 移除校验结果
+				this.message = null
+			},
+			// 点击组件
+			clickHandler() {
+				this.$emit('click')
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-form-item {
+		@include flex(column);
+		font-size: 14px;
+		color: $u-main-color;
+
+		&__body {
+			@include flex;
+			padding: 10px 0;
+
+			&__left {
+				@include flex;
+				align-items: center;
+
+				&__content {
+					position: relative;
+					@include flex;
+					align-items: center;
+					padding-right: 10rpx;
+					flex: 1;
+
+					&__icon {
+						margin-right: 8rpx;
+					}
+
+					&__required {
+						position: absolute;
+						left: -9px;
+						color: $u-error;
+						line-height: 20px;
+						font-size: 20px;
+						top: 3px;
+					}
+
+					&__label {
+						@include flex;
+						align-items: center;
+						flex: 1;
+						color: $u-main-color;
+						font-size: 15px;
+					}
+				}
+			}
+
+			&__right {
+				flex: 1;
+
+				&__content {
+					@include flex;
+					align-items: center;
+					flex: 1;
+
+					&__slot {
+						flex: 1;
+						/* #ifndef MP */
+						@include flex;
+						align-items: center;
+						/* #endif */
+					}
+
+					&__icon {
+						margin-left: 10rpx;
+						color: $u-light-color;
+						font-size: 30rpx;
+					}
+				}
+
+				&__message {
+					font-size: 12px;
+					line-height: 12px;
+					color: $u-error;
+				}
+			}
+		}
+	}
+</style>

+ 45 - 0
plugins/uview-ui/components/u-form/props.js

@@ -0,0 +1,45 @@
+export default {
+    props: {
+        // 当前form的需要验证字段的集合
+        model: {
+            type: Object,
+            default: uni.$u.props.form.model
+        },
+        // 验证规则
+        rules: {
+            type: [Object, Function, Array],
+            default: uni.$u.props.form.rules
+        },
+        // 有错误时的提示方式,message-提示信息,toast-进行toast提示
+        // border-bottom-下边框呈现红色,none-无提示
+        errorType: {
+            type: String,
+            default: uni.$u.props.form.errorType
+        },
+        // 是否显示表单域的下划线边框
+        borderBottom: {
+            type: Boolean,
+            default: uni.$u.props.form.borderBottom
+        },
+        // label的位置,left-左边,top-上边
+        labelPosition: {
+            type: String,
+            default: uni.$u.props.form.labelPosition
+        },
+        // label的宽度,单位px
+        labelWidth: {
+            type: [String, Number],
+            default: uni.$u.props.form.labelWidth
+        },
+        // lable字体的对齐方式
+        labelAlign: {
+            type: String,
+            default: uni.$u.props.form.labelAlign
+        },
+        // lable的样式,对象形式
+        labelStyle: {
+            type: Object,
+            default: uni.$u.props.form.labelStyle
+        }
+    }
+}

+ 217 - 0
plugins/uview-ui/components/u-form/u-form.vue

@@ -0,0 +1,217 @@
+<template>
+	<view class="u-form">
+		<slot />
+	</view>
+</template>
+
+<script>
+	import props from "./props.js";
+	import Schema from "../../libs/util/async-validator";
+	// 去除警告信息
+	Schema.warning = function() {};
+	/**
+	 * Form 表单
+	 * @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
+	 * @tutorial https://www.uviewui.com/components/form.html
+	 * @property {Object}						model			当前form的需要验证字段的集合
+	 * @property {Object | Function | Array}	rules			验证规则
+	 * @property {String}						errorType		错误的提示方式,见上方说明 ( 默认 message )
+	 * @property {Boolean}						borderBottom	是否显示表单域的下划线边框   ( 默认 true )
+	 * @property {String}						labelPosition	表单域提示文字的位置,left-左侧,top-上方 ( 默认 'left' )
+	 * @property {String | Number}				labelWidth		提示文字的宽度,单位px  ( 默认 45 )
+	 * @property {String}						labelAlign		lable字体的对齐方式   ( 默认 ‘left' )
+	 * @property {Object}						labelStyle		lable的样式,对象形式
+	 * @example <u--formlabelPosition="left" :model="model1" :rules="rules" ref="form1"></u--form>
+	 */
+	export default {
+		name: "u-form",
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		provide() {
+			return {
+				uForm: this,
+			};
+		},
+    options: {
+      styleIsolation: 'shared', 
+    },
+		data() {
+			return {
+				formRules: {},
+				// 规则校验器
+				validator: {},
+				// 原始的model快照,用于resetFields方法重置表单时使用
+				originalModel: null,
+			};
+		},
+		watch: {
+			// 监听规则的变化
+			rules: {
+				immediate: true,
+				handler(n) {
+					this.setRules(n);
+				},
+			},
+			// 监听属性的变化,通知子组件u-form-item重新获取信息
+			propsChange(n) {
+				if (this.children?.length) {
+					this.children.map((child) => {
+						// 判断子组件(u-form-item)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+						typeof child.updateParentData == "function" &&
+							child.updateParentData();
+					});
+				}
+			},
+			// 监听model的初始值作为重置表单的快照
+			model: {
+				immediate: true,
+				handler(n) {
+					if (!this.originalModel) {
+						this.originalModel = uni.$u.deepClone(n);
+					}
+				},
+			},
+		},
+		computed: {
+			propsChange() {
+				return [
+					this.errorType,
+					this.borderBottom,
+					this.labelPosition,
+					this.labelWidth,
+					this.labelAlign,
+					this.labelStyle,
+				];
+			},
+		},
+		created() {
+			// 存储当前form下的所有u-form-item的实例
+			// 不能定义在data中,否则微信小程序会造成循环引用而报错
+			this.children = [];
+		},
+		methods: {
+			// 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则
+			setRules(rules) {
+				// 判断是否有规则
+				if (Object.keys(rules).length === 0) return;
+				if (process.env.NODE_ENV === 'development' && Object.keys(this.model).length === 0) {
+					uni.$u.error('设置rules,model必须设置!如果已经设置,请刷新页面。');
+					return;
+				};
+				this.formRules = rules;
+				// 重新将规则赋予Validator
+				this.validator = new Schema(rules);
+			},
+			// 清空所有u-form-item组件的内容,本质上是调用了u-form-item组件中的resetField()方法
+			resetFields() {
+				this.resetModel();
+			},
+			// 重置model为初始值的快照
+			resetModel(obj) {
+				// 历遍所有u-form-item,根据其prop属性,还原model的原始快照
+				this.children.map((child) => {
+					const prop = child?.prop;
+					const value = uni.$u.getProperty(this.originalModel, prop);
+					uni.$u.setProperty(this.model, prop, value);
+				});
+			},
+			// 清空校验结果
+			clearValidate(props) {
+				props = [].concat(props);
+				this.children.map((child) => {
+					// 如果u-form-item的prop在props数组中,则清除对应的校验结果信息
+					if (props[0] === undefined || props.includes(child.prop)) {
+						child.message = null;
+					}
+				});
+			},
+			// 对部分表单字段进行校验
+			async validateField(value, callback, event = null) {
+				// $nextTick是必须的,否则model的变更,可能会延后于此方法的执行
+				this.$nextTick(() => {
+					// 校验错误信息,返回给回调方法,用于存放所有form-item的错误信息
+					const errorsRes = [];
+					// 如果为字符串,转为数组
+					value = [].concat(value);
+					// 历遍children所有子form-item
+					this.children.map((child) => {
+						// 用于存放form-item的错误信息
+						const childErrors = [];
+						if (value.includes(child.prop)) {
+							// 获取对应的属性,通过类似'a.b.c'的形式
+							const propertyVal = uni.$u.getProperty(
+								this.model,
+								child.prop
+							);
+							// 属性链数组
+							const propertyChain = child.prop.split(".");
+							const propertyName =
+								propertyChain[propertyChain.length - 1];
+
+							const rule = this.formRules[child.prop];
+							// 如果不存在对应的规则,直接返回,否则校验器会报错
+							if (!rule) return;
+							// rule规则可为数组形式,也可为对象形式,此处拼接成为数组
+							const rules = [].concat(rule);
+
+							// 对rules数组进行校验
+							for (let i = 0; i < rules.length; i++) {
+								const ruleItem = rules[i];
+								// 将u-form-item的触发器转为数组形式
+								const trigger = [].concat(ruleItem?.trigger);
+								// 如果是有传入触发事件,但是此form-item却没有配置此触发器的话,不执行校验操作
+								if (event && !trigger.includes(event)) continue;
+								// 实例化校验对象,传入构造规则
+								const validator = new Schema({
+									[propertyName]: ruleItem,
+								});
+								validator.validate({
+										[propertyName]: propertyVal,
+									},
+									(errors, fields) => {
+										if (uni.$u.test.array(errors)) {
+											errorsRes.push(...errors);
+											childErrors.push(...errors);
+										}
+										child.message =
+											childErrors[0]?.message ?? null;
+									}
+								);
+							}
+						}
+					});
+					// 执行回调函数
+					typeof callback === "function" && callback(errorsRes);
+				});
+			},
+			// 校验全部数据
+			validate(callback) {
+				// 开发环境才提示,生产环境不会提示
+				if (process.env.NODE_ENV === 'development' && Object.keys(this.formRules).length === 0) {
+					uni.$u.error('未设置rules,请看文档说明!如果已经设置,请刷新页面。');
+					return;
+				}
+				return new Promise((resolve, reject) => {
+					// $nextTick是必须的,否则model的变更,可能会延后于validate方法
+					this.$nextTick(() => {
+						// 获取所有form-item的prop,交给validateField方法进行校验
+						const formItemProps = this.children.map(
+							(item) => item.prop
+						);
+						this.validateField(formItemProps, (errors) => {
+							if(errors.length) {
+								// 如果错误提示方式为toast,则进行提示
+								this.errorType === 'toast' && uni.$u.toast(errors[0].message)
+								reject(errors)
+							} else {
+								resolve(true)
+							}
+						});
+					});
+				});
+			},
+		},
+	};
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 24 - 0
plugins/uview-ui/components/u-gap/props.js

@@ -0,0 +1,24 @@
+export default {
+    props: {
+        // 背景颜色(默认transparent)
+        bgColor: {
+            type: String,
+            default: uni.$u.props.gap.bgColor
+        },
+        // 分割槽高度,单位px(默认30)
+        height: {
+            type: [String, Number],
+            default: uni.$u.props.gap.height
+        },
+        // 与上一个组件的距离
+        marginTop: {
+            type: [String, Number],
+            default: uni.$u.props.gap.marginTop
+        },
+        // 与下一个组件的距离
+        marginBottom: {
+            type: [String, Number],
+            default: uni.$u.props.gap.marginBottom
+        }
+    }
+}

+ 38 - 0
plugins/uview-ui/components/u-gap/u-gap.vue

@@ -0,0 +1,38 @@
+<template>
+	<view class="u-gap" :style="[gapStyle]"></view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * gap 间隔槽
+	 * @description 该组件一般用于内容块之间的用一个灰色块隔开的场景,方便用户风格统一,减少工作量
+	 * @tutorial https://www.uviewui.com/components/gap.html
+	 * @property {String}			bgColor			背景颜色 (默认 'transparent' )
+	 * @property {String | Number}	height			分割槽高度,单位px (默认 20 )
+	 * @property {String | Number}	marginTop		与前一个组件的距离,单位px( 默认 0 )
+	 * @property {String | Number}	marginBottom	与后一个组件的距离,单位px (默认 0 )
+	 * @property {Object}			customStyle		定义需要用到的外部样式
+	 * 
+	 * @example <u-gap height="80" bg-color="#bbb"></u-gap>
+	 */
+	export default {
+		name: "u-gap",
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		computed: {
+			gapStyle() {
+				const style = {
+					backgroundColor: this.bgColor,
+					height: uni.$u.addUnit(this.height),
+					marginTop: uni.$u.addUnit(this.marginTop),
+					marginBottom: uni.$u.addUnit(this.marginBottom),
+				}
+				return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
+			}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+</style>

+ 214 - 0
plugins/uview-ui/components/u-icon/icons.js

@@ -0,0 +1,214 @@
+export default {
+    'uicon-level': '\ue693',
+    'uicon-column-line': '\ue68e',
+    'uicon-checkbox-mark': '\ue807',
+    'uicon-folder': '\ue7f5',
+    'uicon-movie': '\ue7f6',
+    'uicon-star-fill': '\ue669',
+    'uicon-star': '\ue65f',
+    'uicon-phone-fill': '\ue64f',
+    'uicon-phone': '\ue622',
+    'uicon-apple-fill': '\ue881',
+    'uicon-chrome-circle-fill': '\ue885',
+    'uicon-backspace': '\ue67b',
+    'uicon-attach': '\ue632',
+    'uicon-cut': '\ue948',
+    'uicon-empty-car': '\ue602',
+    'uicon-empty-coupon': '\ue682',
+    'uicon-empty-address': '\ue646',
+    'uicon-empty-favor': '\ue67c',
+    'uicon-empty-permission': '\ue686',
+    'uicon-empty-news': '\ue687',
+    'uicon-empty-search': '\ue664',
+    'uicon-github-circle-fill': '\ue887',
+    'uicon-rmb': '\ue608',
+    'uicon-person-delete-fill': '\ue66a',
+    'uicon-reload': '\ue788',
+    'uicon-order': '\ue68f',
+    'uicon-server-man': '\ue6bc',
+    'uicon-search': '\ue62a',
+    'uicon-fingerprint': '\ue955',
+    'uicon-more-dot-fill': '\ue630',
+    'uicon-scan': '\ue662',
+    'uicon-share-square': '\ue60b',
+    'uicon-map': '\ue61d',
+    'uicon-map-fill': '\ue64e',
+    'uicon-tags': '\ue629',
+    'uicon-tags-fill': '\ue651',
+    'uicon-bookmark-fill': '\ue63b',
+    'uicon-bookmark': '\ue60a',
+    'uicon-eye': '\ue613',
+    'uicon-eye-fill': '\ue641',
+    'uicon-mic': '\ue64a',
+    'uicon-mic-off': '\ue649',
+    'uicon-calendar': '\ue66e',
+    'uicon-calendar-fill': '\ue634',
+    'uicon-trash': '\ue623',
+    'uicon-trash-fill': '\ue658',
+    'uicon-play-left': '\ue66d',
+    'uicon-play-right': '\ue610',
+    'uicon-minus': '\ue618',
+    'uicon-plus': '\ue62d',
+    'uicon-info': '\ue653',
+    'uicon-info-circle': '\ue7d2',
+    'uicon-info-circle-fill': '\ue64b',
+    'uicon-question': '\ue715',
+    'uicon-error': '\ue6d3',
+    'uicon-close': '\ue685',
+    'uicon-checkmark': '\ue6a8',
+    'uicon-android-circle-fill': '\ue67e',
+    'uicon-android-fill': '\ue67d',
+    'uicon-ie': '\ue87b',
+    'uicon-IE-circle-fill': '\ue889',
+    'uicon-google': '\ue87a',
+    'uicon-google-circle-fill': '\ue88a',
+    'uicon-setting-fill': '\ue872',
+    'uicon-setting': '\ue61f',
+    'uicon-minus-square-fill': '\ue855',
+    'uicon-plus-square-fill': '\ue856',
+    'uicon-heart': '\ue7df',
+    'uicon-heart-fill': '\ue851',
+    'uicon-camera': '\ue7d7',
+    'uicon-camera-fill': '\ue870',
+    'uicon-more-circle': '\ue63e',
+    'uicon-more-circle-fill': '\ue645',
+    'uicon-chat': '\ue620',
+    'uicon-chat-fill': '\ue61e',
+    'uicon-bag-fill': '\ue617',
+    'uicon-bag': '\ue619',
+    'uicon-error-circle-fill': '\ue62c',
+    'uicon-error-circle': '\ue624',
+    'uicon-close-circle': '\ue63f',
+    'uicon-close-circle-fill': '\ue637',
+    'uicon-checkmark-circle': '\ue63d',
+    'uicon-checkmark-circle-fill': '\ue635',
+    'uicon-question-circle-fill': '\ue666',
+    'uicon-question-circle': '\ue625',
+    'uicon-share': '\ue631',
+    'uicon-share-fill': '\ue65e',
+    'uicon-shopping-cart': '\ue621',
+    'uicon-shopping-cart-fill': '\ue65d',
+    'uicon-bell': '\ue609',
+    'uicon-bell-fill': '\ue640',
+    'uicon-list': '\ue650',
+    'uicon-list-dot': '\ue616',
+    'uicon-zhihu': '\ue6ba',
+    'uicon-zhihu-circle-fill': '\ue709',
+    'uicon-zhifubao': '\ue6b9',
+    'uicon-zhifubao-circle-fill': '\ue6b8',
+    'uicon-weixin-circle-fill': '\ue6b1',
+    'uicon-weixin-fill': '\ue6b2',
+    'uicon-twitter-circle-fill': '\ue6ab',
+    'uicon-twitter': '\ue6aa',
+    'uicon-taobao-circle-fill': '\ue6a7',
+    'uicon-taobao': '\ue6a6',
+    'uicon-weibo-circle-fill': '\ue6a5',
+    'uicon-weibo': '\ue6a4',
+    'uicon-qq-fill': '\ue6a1',
+    'uicon-qq-circle-fill': '\ue6a0',
+    'uicon-moments-circel-fill': '\ue69a',
+    'uicon-moments': '\ue69b',
+    'uicon-qzone': '\ue695',
+    'uicon-qzone-circle-fill': '\ue696',
+    'uicon-baidu-circle-fill': '\ue680',
+    'uicon-baidu': '\ue681',
+    'uicon-facebook-circle-fill': '\ue68a',
+    'uicon-facebook': '\ue689',
+    'uicon-car': '\ue60c',
+    'uicon-car-fill': '\ue636',
+    'uicon-warning-fill': '\ue64d',
+    'uicon-warning': '\ue694',
+    'uicon-clock-fill': '\ue638',
+    'uicon-clock': '\ue60f',
+    'uicon-edit-pen': '\ue612',
+    'uicon-edit-pen-fill': '\ue66b',
+    'uicon-email': '\ue611',
+    'uicon-email-fill': '\ue642',
+    'uicon-minus-circle': '\ue61b',
+    'uicon-minus-circle-fill': '\ue652',
+    'uicon-plus-circle': '\ue62e',
+    'uicon-plus-circle-fill': '\ue661',
+    'uicon-file-text': '\ue663',
+    'uicon-file-text-fill': '\ue665',
+    'uicon-pushpin': '\ue7e3',
+    'uicon-pushpin-fill': '\ue86e',
+    'uicon-grid': '\ue673',
+    'uicon-grid-fill': '\ue678',
+    'uicon-play-circle': '\ue647',
+    'uicon-play-circle-fill': '\ue655',
+    'uicon-pause-circle-fill': '\ue654',
+    'uicon-pause': '\ue8fa',
+    'uicon-pause-circle': '\ue643',
+    'uicon-eye-off': '\ue648',
+    'uicon-eye-off-outline': '\ue62b',
+    'uicon-gift-fill': '\ue65c',
+    'uicon-gift': '\ue65b',
+    'uicon-rmb-circle-fill': '\ue657',
+    'uicon-rmb-circle': '\ue677',
+    'uicon-kefu-ermai': '\ue656',
+    'uicon-server-fill': '\ue751',
+    'uicon-coupon-fill': '\ue8c4',
+    'uicon-coupon': '\ue8ae',
+    'uicon-integral': '\ue704',
+    'uicon-integral-fill': '\ue703',
+    'uicon-home-fill': '\ue964',
+    'uicon-home': '\ue965',
+    'uicon-hourglass-half-fill': '\ue966',
+    'uicon-hourglass': '\ue967',
+    'uicon-account': '\ue628',
+    'uicon-plus-people-fill': '\ue626',
+    'uicon-minus-people-fill': '\ue615',
+    'uicon-account-fill': '\ue614',
+    'uicon-thumb-down-fill': '\ue726',
+    'uicon-thumb-down': '\ue727',
+    'uicon-thumb-up': '\ue733',
+    'uicon-thumb-up-fill': '\ue72f',
+    'uicon-lock-fill': '\ue979',
+    'uicon-lock-open': '\ue973',
+    'uicon-lock-opened-fill': '\ue974',
+    'uicon-lock': '\ue97a',
+    'uicon-red-packet-fill': '\ue690',
+    'uicon-photo-fill': '\ue98b',
+    'uicon-photo': '\ue98d',
+    'uicon-volume-off-fill': '\ue659',
+    'uicon-volume-off': '\ue644',
+    'uicon-volume-fill': '\ue670',
+    'uicon-volume': '\ue633',
+    'uicon-red-packet': '\ue691',
+    'uicon-download': '\ue63c',
+    'uicon-arrow-up-fill': '\ue6b0',
+    'uicon-arrow-down-fill': '\ue600',
+    'uicon-play-left-fill': '\ue675',
+    'uicon-play-right-fill': '\ue676',
+    'uicon-rewind-left-fill': '\ue679',
+    'uicon-rewind-right-fill': '\ue67a',
+    'uicon-arrow-downward': '\ue604',
+    'uicon-arrow-leftward': '\ue601',
+    'uicon-arrow-rightward': '\ue603',
+    'uicon-arrow-upward': '\ue607',
+    'uicon-arrow-down': '\ue60d',
+    'uicon-arrow-right': '\ue605',
+    'uicon-arrow-left': '\ue60e',
+    'uicon-arrow-up': '\ue606',
+    'uicon-skip-back-left': '\ue674',
+    'uicon-skip-forward-right': '\ue672',
+    'uicon-rewind-right': '\ue66f',
+    'uicon-rewind-left': '\ue671',
+    'uicon-arrow-right-double': '\ue68d',
+    'uicon-arrow-left-double': '\ue68c',
+    'uicon-wifi-off': '\ue668',
+    'uicon-wifi': '\ue667',
+    'uicon-empty-data': '\ue62f',
+    'uicon-empty-history': '\ue684',
+    'uicon-empty-list': '\ue68b',
+    'uicon-empty-page': '\ue627',
+    'uicon-empty-order': '\ue639',
+    'uicon-man': '\ue697',
+    'uicon-woman': '\ue69c',
+    'uicon-man-add': '\ue61c',
+    'uicon-man-add-fill': '\ue64c',
+    'uicon-man-delete': '\ue61a',
+    'uicon-man-delete-fill': '\ue66a',
+    'uicon-zh': '\ue70a',
+    'uicon-en': '\ue692'
+}

+ 89 - 0
plugins/uview-ui/components/u-icon/props.js

@@ -0,0 +1,89 @@
+export default {
+    props: {
+        // 图标类名
+        name: {
+            type: String,
+            default: uni.$u.props.icon.name
+        },
+        // 图标颜色,可接受主题色
+        color: {
+            type: String,
+            default: uni.$u.props.icon.color
+        },
+        // 字体大小,单位px
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.icon.size
+        },
+        // 是否显示粗体
+        bold: {
+            type: Boolean,
+            default: uni.$u.props.icon.bold
+        },
+        // 点击图标的时候传递事件出去的index(用于区分点击了哪一个)
+        index: {
+            type: [String, Number],
+            default: uni.$u.props.icon.index
+        },
+        // 触摸图标时的类名
+        hoverClass: {
+            type: String,
+            default: uni.$u.props.icon.hoverClass
+        },
+        // 自定义扩展前缀,方便用户扩展自己的图标库
+        customPrefix: {
+            type: String,
+            default: uni.$u.props.icon.customPrefix
+        },
+        // 图标右边或者下面的文字
+        label: {
+            type: [String, Number],
+            default: uni.$u.props.icon.label
+        },
+        // label的位置,只能右边或者下边
+        labelPos: {
+            type: String,
+            default: uni.$u.props.icon.labelPos
+        },
+        // label的大小
+        labelSize: {
+            type: [String, Number],
+            default: uni.$u.props.icon.labelSize
+        },
+        // label的颜色
+        labelColor: {
+            type: String,
+            default: uni.$u.props.icon.labelColor
+        },
+        // label与图标的距离
+        space: {
+            type: [String, Number],
+            default: uni.$u.props.icon.space
+        },
+        // 图片的mode
+        imgMode: {
+            type: String,
+            default: uni.$u.props.icon.imgMode
+        },
+        // 用于显示图片小图标时,图片的宽度
+        width: {
+            type: [String, Number],
+            default: uni.$u.props.icon.width
+        },
+        // 用于显示图片小图标时,图片的高度
+        height: {
+            type: [String, Number],
+            default: uni.$u.props.icon.height
+        },
+        // 用于解决某些情况下,让图标垂直居中的用途
+        top: {
+            type: [String, Number],
+            default: uni.$u.props.icon.top
+        },
+        // 是否阻止事件传播
+        stop: {
+            type: Boolean,
+            default: uni.$u.props.icon.stop
+        }
+    }
+}

+ 234 - 0
plugins/uview-ui/components/u-icon/u-icon.vue

@@ -0,0 +1,234 @@
+<template>
+	<view
+	    class="u-icon"
+	    @tap="clickHandler"
+	    :class="['u-icon--' + labelPos]"
+	>
+		<image
+		    class="u-icon__img"
+		    v-if="isImg"
+		    :src="name"
+		    :mode="imgMode"
+		    :style="[imgStyle, $u.addStyle(customStyle)]"
+		></image>
+		<text
+		    v-else
+		    class="u-icon__icon"
+		    :class="uClasses"
+		    :style="[iconStyle, $u.addStyle(customStyle)]"
+		    :hover-class="hoverClass"
+		>{{icon}}</text>
+		<!-- 这里进行空字符串判断,如果仅仅是v-if="label",可能会出现传递0的时候,结果也无法显示 -->
+		<text
+		    v-if="label !== ''" 
+		    class="u-icon__label"
+		    :style="{
+			color: labelColor,
+			fontSize: $u.addUnit(labelSize),
+			marginLeft: labelPos == 'right' ? $u.addUnit(space) : 0,
+			marginTop: labelPos == 'bottom' ? $u.addUnit(space) : 0,
+			marginRight: labelPos == 'left' ? $u.addUnit(space) : 0,
+			marginBottom: labelPos == 'top' ? $u.addUnit(space) : 0,
+		}"
+		>{{ label }}</text>
+	</view>
+</template>
+
+<script>
+	// #ifdef APP-NVUE
+	// nvue通过weex的dom模块引入字体,相关文档地址如下:
+	// https://weex.apache.org/zh/docs/modules/dom.html#addrule
+	const fontUrl = 'https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf'
+	const domModule = weex.requireModule('dom')
+	domModule.addRule('fontFace', {
+		'fontFamily': "uicon-iconfont",
+		'src': `url('${fontUrl}')`
+	})
+	// #endif
+
+	// 引入图标名称,已经对应的unicode
+	import icons from './icons'
+	
+	import props from './props.js';;
+
+	/**
+	 * icon 图标
+	 * @description 基于字体的图标集,包含了大多数常见场景的图标。
+	 * @tutorial https://www.uviewui.com/components/icon.html
+	 * @property {String}			name			图标名称,见示例图标集
+	 * @property {String}			color			图标颜色,可接受主题色 (默认 color['u-content-color'] )
+	 * @property {String | Number}	size			图标字体大小,单位px (默认 '16px' )
+	 * @property {Boolean}			bold			是否显示粗体 (默认 false )
+	 * @property {String | Number}	index			点击图标的时候传递事件出去的index(用于区分点击了哪一个)
+	 * @property {String}			hoverClass		图标按下去的样式类,用法同uni的view组件的hoverClass参数,详情见官网
+	 * @property {String}			customPrefix	自定义扩展前缀,方便用户扩展自己的图标库 (默认 'uicon' )
+	 * @property {String | Number}	label			图标右侧的label文字
+	 * @property {String}			labelPos		label相对于图标的位置,只能right或bottom (默认 'right' )
+	 * @property {String | Number}	labelSize		label字体大小,单位px (默认 '15px' )
+	 * @property {String}			labelColor		图标右侧的label文字颜色 ( 默认 color['u-content-color'] )
+	 * @property {String | Number}	space			label与图标的距离,单位px (默认 '3px' )
+	 * @property {String}			imgMode			图片的mode
+	 * @property {String | Number}	width			显示图片小图标时的宽度
+	 * @property {String | Number}	height			显示图片小图标时的高度
+	 * @property {String | Number}	top				图标在垂直方向上的定位 用于解决某些情况下,让图标垂直居中的用途  (默认 0 )
+	 * @property {Boolean}			stop			是否阻止事件传播 (默认 false )
+	 * @property {Object}			customStyle		icon的样式,对象形式
+	 * @event {Function} click 点击图标时触发
+	 * @event {Function} touchstart 事件触摸时触发
+	 * @example <u-icon name="photo" color="#2979ff" size="28"></u-icon>
+	 */
+	export default {
+		name: 'u-icon',
+		data() {
+			return {
+
+			}
+		},
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		computed: {
+			uClasses() {
+				let classes = []
+				classes.push(this.customPrefix + '-' + this.name)
+				// // uView的自定义图标类名为u-iconfont
+				// if (this.customPrefix == 'uicon') {
+				// 	classes.push('u-iconfont')
+				// } else {
+				// 	classes.push(this.customPrefix)
+				// }
+				// 主题色,通过类配置
+				if (this.color && uni.$u.config.type.includes(this.color)) classes.push('u-icon__icon--' + this.color)
+				// 阿里,头条,百度小程序通过数组绑定类名时,无法直接使用[a, b, c]的形式,否则无法识别
+				// 故需将其拆成一个字符串的形式,通过空格隔开各个类名
+				//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
+				classes = classes.join(' ')
+				//#endif
+				return classes
+			},
+			iconStyle() {
+				let style = {}
+				style = {
+					fontSize: uni.$u.addUnit(this.size),
+					lineHeight: uni.$u.addUnit(this.size),
+					fontWeight: this.bold ? 'bold' : 'normal',
+					// 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中
+					top: uni.$u.addUnit(this.top)
+				}
+				// 非主题色值时,才当作颜色值
+				if (this.color && !uni.$u.config.type.includes(this.color)) style.color = this.color
+
+				return style
+			},
+			// 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式
+			isImg() {
+				return this.name.indexOf('/') !== -1
+			},
+			imgStyle() {
+				let style = {}
+				// 如果设置width和height属性,则优先使用,否则使用size属性
+				style.width = this.width ? uni.$u.addUnit(this.width) : uni.$u.addUnit(this.size)
+				style.height = this.height ? uni.$u.addUnit(this.height) : uni.$u.addUnit(this.size)
+				return style
+			},
+			// 通过图标名,查找对应的图标
+			icon() {
+				// 如果内置的图标中找不到对应的图标,就直接返回name值,因为用户可能传入的是unicode代码
+				return icons['uicon-' + this.name] || this.name
+			}
+		},
+		methods: {
+			clickHandler(e) {
+				this.$emit('click', this.index)
+				// 是否阻止事件冒泡
+				this.stop && this.preventEvent(e)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	// 变量定义
+	$u-icon-primary: $u-primary !default;
+	$u-icon-success: $u-success !default;
+	$u-icon-info: $u-info !default;
+	$u-icon-warning: $u-warning !default;
+	$u-icon-error: $u-error !default;
+	$u-icon-label-line-height:1 !default;
+
+	/* #ifndef APP-NVUE */
+	// 非nvue下加载字体
+	@font-face {
+		font-family: 'uicon-iconfont';
+		src: url('https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf') format('truetype');
+	}
+
+	/* #endif */
+
+	.u-icon {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		align-items: center;
+
+		&--left {
+			flex-direction: row-reverse;
+			align-items: center;
+		}
+
+		&--right {
+			flex-direction: row;
+			align-items: center;
+		}
+
+		&--top {
+			flex-direction: column-reverse;
+			justify-content: center;
+		}
+
+		&--bottom {
+			flex-direction: column;
+			justify-content: center;
+		}
+
+		&__icon {
+			font-family: uicon-iconfont;
+			position: relative;
+			@include flex;
+			align-items: center;
+
+			&--primary {
+				color: $u-icon-primary;
+			}
+
+			&--success {
+				color: $u-icon-success;
+			}
+
+			&--error {
+				color: $u-icon-error;
+			}
+
+			&--warning {
+				color: $u-icon-warning;
+			}
+
+			&--info {
+				color: $u-icon-info;
+			}
+		}
+
+		&__img {
+			/* #ifndef APP-NVUE */
+			height: auto;
+			will-change: transform;
+			/* #endif */
+		}
+
+		&__label {
+			/* #ifndef APP-NVUE */
+			line-height: $u-icon-label-line-height;
+			/* #endif */
+		}
+	}
+</style>

+ 187 - 0
plugins/uview-ui/components/u-input/props.js

@@ -0,0 +1,187 @@
+export default {
+	props: {
+		// 输入的值
+		value: {
+			type: [String, Number],
+			default: uni.$u.props.input.value
+		},
+		// 输入框类型
+		// number-数字输入键盘,app-vue下可以输入浮点数,app-nvue和小程序平台下只能输入整数
+		// idcard-身份证输入键盘,微信、支付宝、百度、QQ小程序
+		// digit-带小数点的数字键盘,App的nvue页面、微信、支付宝、百度、头条、QQ小程序
+		// text-文本输入键盘
+		type: {
+			type: String,
+			default: uni.$u.props.input.type
+		},
+		// 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true,
+		// 兼容性:微信小程序、百度小程序、字节跳动小程序、QQ小程序
+		fixed: {
+			type: Boolean,
+			default: uni.$u.props.input.fixed
+		},
+		// 是否禁用输入框
+		disabled: {
+			type: Boolean,
+			default: uni.$u.props.input.disabled
+		},
+		// 禁用状态时的背景色
+		disabledColor: {
+			type: String,
+			default: uni.$u.props.input.disabledColor
+		},
+		// 是否显示清除控件
+		clearable: {
+			type: Boolean,
+			default: uni.$u.props.input.clearable
+		},
+		// 是否密码类型
+		password: {
+			type: Boolean,
+			default: uni.$u.props.input.password
+		},
+		// 最大输入长度,设置为 -1 的时候不限制最大长度
+		maxlength: {
+			type: [String, Number],
+			default: uni.$u.props.input.maxlength
+		},
+		// 	输入框为空时的占位符
+		placeholder: {
+			type: String,
+			default: uni.$u.props.input.placeholder
+		},
+		// 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/
+		placeholderClass: {
+			type: String,
+			default: uni.$u.props.input.placeholderClass
+		},
+		// 指定placeholder的样式
+		placeholderStyle: {
+			type: [String, Object],
+			default: uni.$u.props.input.placeholderStyle
+		},
+		// 是否显示输入字数统计,只在 type ="text"或type ="textarea"时有效
+		showWordLimit: {
+			type: Boolean,
+			default: uni.$u.props.input.showWordLimit
+		},
+		// 设置右下角按钮的文字,有效值:send|search|next|go|done,兼容性详见uni-app文档
+		// https://uniapp.dcloud.io/component/input
+		// https://uniapp.dcloud.io/component/textarea
+		confirmType: {
+			type: String,
+			default: uni.$u.props.input.confirmType
+		},
+		// 点击键盘右下角按钮时是否保持键盘不收起,H5无效
+		confirmHold: {
+			type: Boolean,
+			default: uni.$u.props.input.confirmHold
+		},
+		// focus时,点击页面的时候不收起键盘,微信小程序有效
+		holdKeyboard: {
+			type: Boolean,
+			default: uni.$u.props.input.holdKeyboard
+		},
+		// 自动获取焦点
+		// 在 H5 平台能否聚焦以及软键盘是否跟随弹出,取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点
+		focus: {
+			type: Boolean,
+			default: uni.$u.props.input.focus
+		},
+		// 键盘收起时,是否自动失去焦点,目前仅App3.0.0+有效
+		autoBlur: {
+			type: Boolean,
+			default: uni.$u.props.input.autoBlur
+		},
+		// 是否去掉 iOS 下的默认内边距,仅微信小程序,且type=textarea时有效
+		disableDefaultPadding: {
+			type: Boolean,
+			default: uni.$u.props.input.disableDefaultPadding
+		},
+		// 指定focus时光标的位置
+		cursor: {
+			type: [String, Number],
+			default: uni.$u.props.input.cursor
+		},
+		// 输入框聚焦时底部与键盘的距离
+		cursorSpacing: {
+			type: [String, Number],
+			default: uni.$u.props.input.cursorSpacing
+		},
+		// 光标起始位置,自动聚集时有效,需与selection-end搭配使用
+		selectionStart: {
+			type: [String, Number],
+			default: uni.$u.props.input.selectionStart
+		},
+		// 光标结束位置,自动聚集时有效,需与selection-start搭配使用
+		selectionEnd: {
+			type: [String, Number],
+			default: uni.$u.props.input.selectionEnd
+		},
+		// 键盘弹起时,是否自动上推页面
+		adjustPosition: {
+			type: Boolean,
+			default: uni.$u.props.input.adjustPosition
+		},
+		// 输入框内容对齐方式,可选值为:left|center|right
+		inputAlign: {
+			type: String,
+			default: uni.$u.props.input.inputAlign
+		},
+		// 输入框字体的大小
+		fontSize: {
+			type: [String, Number],
+			default: uni.$u.props.input.fontSize
+		},
+		// 输入框字体颜色
+		color: {
+			type: String,
+			default: uni.$u.props.input.color
+		},
+		// 输入框前置图标
+		prefixIcon: {
+			type: String,
+			default: uni.$u.props.input.prefixIcon
+		},
+		// 前置图标样式,对象或字符串
+		prefixIconStyle: {
+			type: [String, Object],
+			default: uni.$u.props.input.prefixIconStyle
+		},
+		// 输入框后置图标
+		suffixIcon: {
+			type: String,
+			default: uni.$u.props.input.suffixIcon
+		},
+		// 后置图标样式,对象或字符串
+		suffixIconStyle: {
+			type: [String, Object],
+			default: uni.$u.props.input.suffixIconStyle
+		},
+		// 边框类型,surround-四周边框,bottom-底部边框,none-无边框
+		border: {
+			type: String,
+			default: uni.$u.props.input.border
+		},
+		// 是否只读,与disabled不同之处在于disabled会置灰组件,而readonly则不会
+		readonly: {
+			type: Boolean,
+			default: uni.$u.props.input.readonly
+		},
+		// 输入框形状,circle-圆形,square-方形
+		shape: {
+			type: String,
+			default: uni.$u.props.input.shape
+		},
+		// 用于处理或者过滤输入框内容的方法
+		formatter: {
+			type: [Function, null],
+			default: uni.$u.props.input.formatter
+		},
+		// 是否忽略组件内对文本合成系统事件的处理
+		ignoreCompositionEvent: {
+			type: Boolean,
+			default: true
+		}
+	}
+}

+ 354 - 0
plugins/uview-ui/components/u-input/u-input.vue

@@ -0,0 +1,354 @@
+<template>
+    <view class="u-input" :class="inputClass" :style="[wrapperStyle]">
+        <view class="u-input__content">
+            <view
+                class="u-input__content__prefix-icon"
+                v-if="prefixIcon || $slots.prefix"
+            >
+                <slot name="prefix">
+                    <u-icon
+                        :name="prefixIcon"
+                        size="18"
+                        :customStyle="prefixIconStyle"
+                    ></u-icon>
+                </slot>
+            </view>
+            <view class="u-input__content__field-wrapper" @tap="clickHandler">
+				<!-- 根据uni-app的input组件文档,H5和APP中只要声明了password参数(无论true还是false),type均失效,此时
+					为了防止type=number时,又存在password属性,type无效,此时需要设置password为undefined
+				 -->
+            	<input
+            	    class="u-input__content__field-wrapper__field"
+            	    :style="[inputStyle]"
+            	    :type="type"
+            	    :focus="focus"
+            	    :cursor="cursor"
+            	    :value="innerValue"
+            	    :auto-blur="autoBlur"
+            	    :disabled="disabled || readonly"
+            	    :maxlength="maxlength"
+            	    :placeholder="placeholder"
+            	    :placeholder-style="placeholderStyle"
+            	    :placeholder-class="placeholderClass"
+            	    :confirm-type="confirmType"
+            	    :confirm-hold="confirmHold"
+            	    :hold-keyboard="holdKeyboard"
+            	    :cursor-spacing="cursorSpacing"
+            	    :adjust-position="adjustPosition"
+            	    :selection-end="selectionEnd"
+            	    :selection-start="selectionStart"
+            	    :password="password || type === 'password' || undefined"
+                    :ignoreCompositionEvent="ignoreCompositionEvent"
+            	    @input="onInput"
+            	    @blur="onBlur"
+            	    @focus="onFocus"
+            	    @confirm="onConfirm"
+            	    @keyboardheightchange="onkeyboardheightchange"
+            	/>
+            </view>
+            <view
+                class="u-input__content__clear"
+                v-if="isShowClear"
+                @tap="onClear"
+            >
+                <u-icon
+                    name="close"
+                    size="11"
+                    color="#ffffff"
+                    customStyle="line-height: 12px"
+                ></u-icon>
+            </view>
+            <view
+                class="u-input__content__subfix-icon"
+                v-if="suffixIcon || $slots.suffix"
+            >
+                <slot name="suffix">
+                    <u-icon
+                        :name="suffixIcon"
+                        size="18"
+                        :customStyle="suffixIconStyle"
+                    ></u-icon>
+                </slot>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+import props from "./props.js";
+/**
+ * Input 输入框
+ * @description  此组件为一个输入框,默认没有边框和样式,是专门为配合表单组件u-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。
+ * @tutorial https://uviewui.com/components/input.html
+ * @property {String | Number}	value					输入的值
+ * @property {String}			type					输入框类型,见上方说明 ( 默认 'text' )
+ * @property {Boolean}			fixed					如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true,兼容性:微信小程序、百度小程序、字节跳动小程序、QQ小程序 ( 默认 false )
+ * @property {Boolean}			disabled				是否禁用输入框 ( 默认 false )
+ * @property {String}			disabledColor			禁用状态时的背景色( 默认 '#f5f7fa' )
+ * @property {Boolean}			clearable				是否显示清除控件 ( 默认 false )
+ * @property {Boolean}			password				是否密码类型 ( 默认 false )
+ * @property {String | Number}	maxlength				最大输入长度,设置为 -1 的时候不限制最大长度 ( 默认 -1 )
+ * @property {String}			placeholder				输入框为空时的占位符
+ * @property {String}			placeholderClass		指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ ( 默认 'input-placeholder' )
+ * @property {String | Object}	placeholderStyle		指定placeholder的样式,字符串/对象形式,如"color: red;"
+ * @property {Boolean}			showWordLimit			是否显示输入字数统计,只在 type ="text"或type ="textarea"时有效 ( 默认 false )
+ * @property {String}			confirmType				设置右下角按钮的文字,兼容性详见uni-app文档 ( 默认 'done' )
+ * @property {Boolean}			confirmHold				点击键盘右下角按钮时是否保持键盘不收起,H5无效 ( 默认 false )
+ * @property {Boolean}			holdKeyboard			focus时,点击页面的时候不收起键盘,微信小程序有效 ( 默认 false )
+ * @property {Boolean}			focus					自动获取焦点,在 H5 平台能否聚焦以及软键盘是否跟随弹出,取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点 ( 默认 false )
+ * @property {Boolean}			autoBlur				键盘收起时,是否自动失去焦点,目前仅App3.0.0+有效 ( 默认 false )
+ * @property {Boolean}			disableDefaultPadding	是否去掉 iOS 下的默认内边距,仅微信小程序,且type=textarea时有效 ( 默认 false )
+ * @property {String | Number}	cursor					指定focus时光标的位置( 默认 -1 )
+ * @property {String | Number}	cursorSpacing			输入框聚焦时底部与键盘的距离 ( 默认 30 )
+ * @property {String | Number}	selectionStart			光标起始位置,自动聚集时有效,需与selection-end搭配使用 ( 默认 -1 )
+ * @property {String | Number}	selectionEnd			光标结束位置,自动聚集时有效,需与selection-start搭配使用 ( 默认 -1 )
+ * @property {Boolean}			adjustPosition			键盘弹起时,是否自动上推页面 ( 默认 true )
+ * @property {String}			inputAlign				输入框内容对齐方式( 默认 'left' )
+ * @property {String | Number}	fontSize				输入框字体的大小 ( 默认 '15px' )
+ * @property {String}			color					输入框字体颜色	( 默认 '#303133' )
+ * @property {Function}			formatter			    内容式化函数
+ * @property {String}			prefixIcon				输入框前置图标
+ * @property {String | Object}	prefixIconStyle			前置图标样式,对象或字符串
+ * @property {String}			suffixIcon				输入框后置图标
+ * @property {String | Object}	suffixIconStyle			后置图标样式,对象或字符串
+ * @property {String}			border					边框类型,surround-四周边框,bottom-底部边框,none-无边框 ( 默认 'surround' )
+ * @property {Boolean}			readonly				是否只读,与disabled不同之处在于disabled会置灰组件,而readonly则不会 ( 默认 false )
+ * @property {String}			shape					输入框形状,circle-圆形,square-方形 ( 默认 'square' )
+ * @property {Object}			customStyle				定义需要用到的外部样式
+ * @property {Boolean}			ignoreCompositionEvent	是否忽略组件内对文本合成系统事件的处理。
+ * @example <u-input v-model="value" :password="true" suffix-icon="lock-fill" />
+ */
+export default {
+    name: "u-input",
+    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+    data() {
+        return {
+            // 输入框的值
+            innerValue: "",
+            // 是否处于获得焦点状态
+            focused: false,
+            // value是否第一次变化,在watch中,由于加入immediate属性,会在第一次触发,此时不应该认为value发生了变化
+            firstChange: true,
+            // value绑定值的变化是由内部还是外部引起的
+            changeFromInner: false,
+			// 过滤处理方法
+			innerFormatter: value => value
+        };
+    },
+    watch: {
+        value: {
+            immediate: true,
+            handler(newVal, oldVal) {
+                this.innerValue = newVal;
+                /* #ifdef H5 */
+                // 在H5中,外部value变化后,修改input中的值,不会触发@input事件,此时手动调用值变化方法
+                if (
+                    this.firstChange === false &&
+                    this.changeFromInner === false
+                ) {
+                    this.valueChange();
+                }
+                /* #endif */
+                this.firstChange = false;
+                // 重置changeFromInner的值为false,标识下一次引起默认为外部引起的
+                this.changeFromInner = false;
+            },
+        },
+    },
+    computed: {
+        // 是否显示清除控件
+        isShowClear() {
+            const { clearable, readonly, focused, innerValue } = this;
+            return !!clearable && !readonly && !!focused && innerValue !== "";
+        },
+        // 组件的类名
+        inputClass() {
+            let classes = [],
+                { border, disabled, shape } = this;
+            border === "surround" &&
+                (classes = classes.concat(["u-border", "u-input--radius"]));
+            classes.push(`u-input--${shape}`);
+            border === "bottom" &&
+                (classes = classes.concat([
+                    "u-border-bottom",
+                    "u-input--no-radius",
+                ]));
+            return classes.join(" ");
+        },
+        // 组件的样式
+        wrapperStyle() {
+            const style = {};
+            // 禁用状态下,被背景色加上对应的样式
+            if (this.disabled) {
+                style.backgroundColor = this.disabledColor;
+            }
+            // 无边框时,去除内边距
+            if (this.border === "none") {
+                style.padding = "0";
+            } else {
+                // 由于uni-app的iOS开发者能力有限,导致需要分开写才有效
+                style.paddingTop = "6px";
+                style.paddingBottom = "6px";
+                style.paddingLeft = "9px";
+                style.paddingRight = "9px";
+            }
+            return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));
+        },
+        // 输入框的样式
+        inputStyle() {
+            const style = {
+                color: this.color,
+                fontSize: uni.$u.addUnit(this.fontSize),
+				textAlign: this.inputAlign
+            };
+            return style;
+        },
+    },
+    methods: {
+		// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
+		setFormatter(e) {
+			this.innerFormatter = e
+		},
+        // 当键盘输入时,触发input事件
+        onInput(e) {
+            let { value = "" } = e.detail || {};
+            // 格式化过滤方法
+            const formatter = this.formatter || this.innerFormatter
+            const formatValue = formatter(value)
+            // 为了避免props的单向数据流特性,需要先将innerValue值设置为当前值,再在$nextTick中重新赋予设置后的值才有效
+            this.innerValue = value
+            this.$nextTick(() => {
+            	this.innerValue = formatValue;
+            	this.valueChange();
+            })
+        },
+        // 输入框失去焦点时触发
+        onBlur(event) {
+            this.$emit("blur", event.detail.value);
+            // H5端的blur会先于点击清除控件的点击click事件触发,导致focused
+            // 瞬间为false,从而隐藏了清除控件而无法被点击到
+            uni.$u.sleep(50).then(() => {
+                this.focused = false;
+            });
+            // 尝试调用u-form的验证方法
+            uni.$u.formValidate(this, "blur");
+        },
+        // 输入框聚焦时触发
+        onFocus(event) {
+            this.focused = true;
+            this.$emit("focus");
+        },
+        // 点击完成按钮时触发
+        onConfirm(event) {
+            this.$emit("confirm", this.innerValue);
+        },
+        // 键盘高度发生变化的时候触发此事件
+        // 兼容性:微信小程序2.7.0+、App 3.1.0+
+		onkeyboardheightchange() {
+            this.$emit("keyboardheightchange");
+        },
+        // 内容发生变化,进行处理
+        valueChange() {
+            const value = this.innerValue;
+            this.$nextTick(() => {
+                this.$emit("input", value);
+                // 标识value值的变化是由内部引起的
+                this.changeFromInner = true;
+                this.$emit("change", value);
+                // 尝试调用u-form的验证方法
+                uni.$u.formValidate(this, "change");
+            });
+        },
+        // 点击清除控件
+        onClear() {
+            this.innerValue = "";
+            this.$nextTick(() => {
+                this.valueChange();
+                this.$emit("clear");
+            });
+        },
+        /**
+         * 在安卓nvue上,事件无法冒泡
+         * 在某些时间,我们希望监听u-from-item的点击事件,此时会导致点击u-form-item内的u-input后
+         * 无法触发u-form-item的点击事件,这里通过手动调用u-form-item的方法进行触发
+         */
+        clickHandler() {
+            // #ifdef APP-NVUE
+            if (uni.$u.os() === "android") {
+                const formItem = uni.$u.$parent.call(this, "u-form-item");
+                if (formItem) {
+                    formItem.clickHandler();
+                }
+            }
+            // #endif
+        },
+    },
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../libs/css/components.scss";
+
+.u-input {
+    @include flex(row);
+    align-items: center;
+    justify-content: space-between;
+    flex: 1;
+
+    &--radius,
+    &--square {
+        border-radius: 4px;
+    }
+
+    &--no-radius {
+        border-radius: 0;
+    }
+
+    &--circle {
+        border-radius: 100px;
+    }
+
+    &__content {
+        flex: 1;
+        @include flex(row);
+        align-items: center;
+        justify-content: space-between;
+
+        &__field-wrapper {
+            position: relative;
+            @include flex(row);
+            margin: 0;
+            flex: 1;
+			
+			&__field {
+				line-height: 26px;
+				text-align: left;
+				color: $u-main-color;
+				height: 24px;
+				font-size: 15px;
+				flex: 1;
+			}
+        }
+
+        &__clear {
+            width: 20px;
+            height: 20px;
+            border-radius: 100px;
+            background-color: #c6c7cb;
+            @include flex(row);
+            align-items: center;
+            justify-content: center;
+            transform: scale(0.82);
+            margin-left: 4px;
+        }
+
+        &__subfix-icon {
+            margin-left: 4px;
+        }
+
+        &__prefix-icon {
+            margin-right: 4px;
+        }
+    }
+}
+</style>

+ 33 - 0
plugins/uview-ui/components/u-line/props.js

@@ -0,0 +1,33 @@
+export default {
+    props: {
+        color: {
+            type: String,
+            default: uni.$u.props.line.color
+        },
+        // 长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带px单位的值等
+        length: {
+            type: [String, Number],
+            default: uni.$u.props.line.length
+        },
+        // 线条方向,col-竖向,row-横向
+        direction: {
+            type: String,
+            default: uni.$u.props.line.direction
+        },
+        // 是否显示细边框
+        hairline: {
+            type: Boolean,
+            default: uni.$u.props.line.hairline
+        },
+        // 线条与上下左右元素的间距,字符串形式,如"30px"、"20px 30px"
+        margin: {
+            type: [String, Number],
+            default: uni.$u.props.line.margin
+        },
+        // 是否虚线,true-虚线,false-实线
+        dashed: {
+            type: Boolean,
+            default: uni.$u.props.line.dashed
+        }
+    }
+}

+ 62 - 0
plugins/uview-ui/components/u-line/u-line.vue

@@ -0,0 +1,62 @@
+<template>
+	<view
+	    class="u-line"
+	    :style="[lineStyle]"
+	>
+
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * line 线条
+	 * @description 此组件一般用于显示一根线条,用于分隔内容块,有横向和竖向两种模式,且能设置0.5px线条,使用也很简单
+	 * @tutorial https://www.uviewui.com/components/line.html
+	 * @property {String}			color		线条的颜色 ( 默认 '#d6d7d9' )
+	 * @property {String | Number}	length		长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带px单位的值等 ( 默认 '100%' )
+	 * @property {String}			direction	线条的方向,row-横向,col-竖向 (默认 'row' )
+	 * @property {Boolean}			hairline	是否显示细线条 (默认 true )
+	 * @property {String | Number}	margin		线条与上下左右元素的间距,字符串形式,如"30px"  (默认 0 )
+	 * @property {Boolean}			dashed		是否虚线,true-虚线,false-实线 (默认 false )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @example <u-line color="red"></u-line>
+	 */
+	export default {
+		name: 'u-line',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		computed: {
+			lineStyle() {
+				const style = {}
+				style.margin = this.margin
+				// 如果是水平线条,边框高度为1px,再通过transform缩小一半,就是0.5px了
+				if (this.direction === 'row') {
+					// 此处采用兼容分开写,兼容nvue的写法
+					style.borderBottomWidth = '1px'
+					style.borderBottomStyle = this.dashed ? 'dashed' : 'solid'
+					style.width = uni.$u.addUnit(this.length)
+					if (this.hairline) style.transform = 'scaleY(0.5)'
+				} else {
+					// 如果是竖向线条,边框宽度为1px,再通过transform缩小一半,就是0.5px了
+					style.borderLeftWidth = '1px'
+					style.borderLeftStyle = this.dashed ? 'dashed' : 'solid'
+					style.height = uni.$u.addUnit(this.length)
+					if (this.hairline) style.transform = 'scaleX(0.5)'
+				}
+
+				style.borderColor = this.color
+				return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-line {
+		/* #ifndef APP-NVUE */
+		vertical-align: middle;
+		/* #endif */
+	}
+</style>

+ 39 - 0
plugins/uview-ui/components/u-link/props.js

@@ -0,0 +1,39 @@
+export default {
+    props: {
+        // 文字颜色
+        color: {
+            type: String,
+            default: uni.$u.props.link.color
+        },
+        // 字体大小,单位px
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.link.fontSize
+        },
+        // 是否显示下划线
+        underLine: {
+            type: Boolean,
+            default: uni.$u.props.link.underLine
+        },
+        // 要跳转的链接
+        href: {
+            type: String,
+            default: uni.$u.props.link.href
+        },
+        // 小程序中复制到粘贴板的提示语
+        mpTips: {
+            type: String,
+            default: uni.$u.props.link.mpTips
+        },
+        // 下划线颜色
+        lineColor: {
+            type: String,
+            default: uni.$u.props.link.lineColor
+        },
+        // 超链接的问题,不使用slot形式传入,是因为nvue下无法修改颜色
+        text: {
+            type: String,
+            default: uni.$u.props.link.text
+        }
+    }
+}

+ 0 - 0
plugins/uview-ui/components/u-link/u-link.vue


Some files were not shown because too many files changed in this diff