achao 2 anos atrás
commit
5e96a58871
100 arquivos alterados com 12054 adições e 0 exclusões
  1. 32 0
      App.vue
  2. 88 0
      common/appUpdate.js
  3. 361 0
      common/components/uni-rate.vue
  4. 34 0
      common/helper.js
  5. 10 0
      config/index.js
  6. 20 0
      index.html
  7. 0 0
      js_sdk/js-amap/__MACOSX/amap-wx.130.js
  8. 31 0
      js_sdk/js-amap/amap-wx.130.js
  9. 1122 0
      js_sdk/qqmap-wx-jssdk.js
  10. 0 0
      js_sdk/qqmap-wx-jssdk.min.js
  11. 272 0
      js_sdk/wa-permission/permission.js
  12. 594 0
      js_sdk/xfl-DateFormat/DateFormat.js
  13. 30 0
      main.js
  14. 60 0
      manifest.json
  15. 17 0
      pages.json
  16. 92 0
      pages/index/index.vue
  17. BIN
      static/c1.png
  18. BIN
      static/c2.png
  19. BIN
      static/c3.png
  20. BIN
      static/c4.png
  21. BIN
      static/c5.png
  22. BIN
      static/c6.png
  23. BIN
      static/c7.png
  24. BIN
      static/c8.png
  25. BIN
      static/c9.png
  26. 20 0
      static/customicons.css
  27. BIN
      static/customicons.ttf
  28. BIN
      static/logo.png
  29. BIN
      static/uni.png
  30. 109 0
      store/index.js
  31. 1 0
      uni.scss
  32. 6 0
      uni_modules/mescroll-uni/changelog.md
  33. 19 0
      uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.css
  34. 400 0
      uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.vue
  35. 47 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.css
  36. 39 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.vue
  37. 360 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-body.vue
  38. 49 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni-option.js
  39. 437 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni.vue
  40. 44 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.css
  41. 53 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.vue
  42. 32 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.css
  43. 40 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.vue
  44. 380 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-body.vue
  45. 64 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni-option.js
  46. 462 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni.vue
  47. 116 0
      uni_modules/mescroll-uni/components/mescroll-empty/mescroll-empty.vue
  48. 55 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.css
  49. 47 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.vue
  50. 83 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-top.vue
  51. 47 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.css
  52. 39 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.vue
  53. 15 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-i18n.js
  54. 57 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js
  55. 64 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni-option.js
  56. 36 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.css
  57. 799 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.js
  58. 477 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.vue
  59. 47 0
      uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js
  60. 66 0
      uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more-item.js
  61. 74 0
      uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more.js
  62. 109 0
      uni_modules/mescroll-uni/components/mescroll-uni/wxs/mixins.js
  63. 92 0
      uni_modules/mescroll-uni/components/mescroll-uni/wxs/renderjs.js
  64. 268 0
      uni_modules/mescroll-uni/components/mescroll-uni/wxs/wxs.wxs
  65. 80 0
      uni_modules/mescroll-uni/package.json
  66. 45 0
      uni_modules/mescroll-uni/readme.md
  67. 31 0
      uni_modules/uni-badge/changelog.md
  68. 268 0
      uni_modules/uni-badge/components/uni-badge/uni-badge.vue
  69. 85 0
      uni_modules/uni-badge/package.json
  70. 10 0
      uni_modules/uni-badge/readme.md
  71. 6 0
      uni_modules/uni-breadcrumb/changelog.md
  72. 121 0
      uni_modules/uni-breadcrumb/components/uni-breadcrumb-item/uni-breadcrumb-item.vue
  73. 41 0
      uni_modules/uni-breadcrumb/components/uni-breadcrumb/uni-breadcrumb.vue
  74. 88 0
      uni_modules/uni-breadcrumb/package.json
  75. 66 0
      uni_modules/uni-breadcrumb/readme.md
  76. 20 0
      uni_modules/uni-calendar/changelog.md
  77. 546 0
      uni_modules/uni-calendar/components/uni-calendar/calendar.js
  78. 12 0
      uni_modules/uni-calendar/components/uni-calendar/i18n/en.json
  79. 8 0
      uni_modules/uni-calendar/components/uni-calendar/i18n/index.js
  80. 12 0
      uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hans.json
  81. 12 0
      uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hant.json
  82. 188 0
      uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue
  83. 562 0
      uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue
  84. 350 0
      uni_modules/uni-calendar/components/uni-calendar/util.js
  85. 85 0
      uni_modules/uni-calendar/package.json
  86. 103 0
      uni_modules/uni-calendar/readme.md
  87. 26 0
      uni_modules/uni-card/changelog.md
  88. 270 0
      uni_modules/uni-card/components/uni-card/uni-card.vue
  89. 90 0
      uni_modules/uni-card/package.json
  90. 12 0
      uni_modules/uni-card/readme.md
  91. 36 0
      uni_modules/uni-collapse/changelog.md
  92. 402 0
      uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue
  93. 147 0
      uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue
  94. 89 0
      uni_modules/uni-collapse/package.json
  95. 12 0
      uni_modules/uni-collapse/readme.md
  96. 15 0
      uni_modules/uni-combox/changelog.md
  97. 275 0
      uni_modules/uni-combox/components/uni-combox/uni-combox.vue
  98. 90 0
      uni_modules/uni-combox/package.json
  99. 11 0
      uni_modules/uni-combox/readme.md
  100. 24 0
      uni_modules/uni-countdown/changelog.md

+ 32 - 0
App.vue

@@ -0,0 +1,32 @@
+<script>
+	export default {
+		onLaunch: function() {
+			console.warn('当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!')
+			console.log('App Launch')
+		},
+		onShow: function() {
+			console.log('App Show')
+		},
+		onHide: function() {
+			console.log('App Hide')
+		}
+	}
+</script>
+
+<style lang="scss">
+	/*每个页面公共css */
+	@import '@/uni_modules/uni-scss/index.scss';
+	/* #ifndef APP-NVUE */
+	@import '@/static/customicons.css';
+	// 设置整个项目的背景色
+	page {
+		background-color: #f5f5f5;
+	}
+
+	/* #endif */
+	.example-info {
+		font-size: 14px;
+		color: #333;
+		padding: 10px;
+	}
+</style>

+ 88 - 0
common/appUpdate.js

@@ -0,0 +1,88 @@
+
+//APP更新
+
+import * as config from '../config'
+export default function appUpdate() {
+		
+	let baseUrlNew = config.def().baseUrlNew
+	console.log('id',plus.runtime.appid)
+	console.log('version',plus.runtime.version)
+	
+	uni.request({
+	    url: 'https://apitest.eliangeyun.com/appVersion/selectInfo',
+	    data: {
+	    	appid: plus.runtime.appid,
+	    	version: plus.runtime.version,
+	    	imei: "1",
+			type:'hz'
+	    },
+	    method: 'GET',
+	    success: (res) => {			
+			console.log(222222222222222222222222222)
+	    	if (res.statusCode === 200) {
+				console.log("uni.request update success",res)
+				plus.runtime.getProperty(plus.runtime.appid, function(wgtinfo) {
+					let client_version = wgtinfo.version
+					var flag_update = client_version.split(".").splice(0, 2).join(".") != res.data.data.version.split(".").splice(0, 2)
+						.join(".")
+					var flag_hot = (Number(client_version.split(".")[2]) < Number(res.data.data.version.split(".")[2])) & !flag_update
+					console.log("client_version",client_version) 
+					console.log("flag_update",flag_update)
+					console.log("flag_hot",flag_hot)
+				
+					if (flag_update) {
+						console.log("更新弹窗")
+						// 提醒用户更新
+						uni.showModal({
+							title: '更新提示',
+							content: res.data.data.note,
+							success: (showResult) => {
+								if (showResult.confirm) {
+									plus.nativeUI.toast("正在准备环境,请稍后!");
+									console.log(res.data.data.url, )
+									var dtask = plus.downloader.createDownload(res.data.data.url, {
+										method: 'GET',
+										filename: '_doc/update/'
+									}, function(d, status) {
+										if (status == 200) {
+											var path = d.filename; //下载apk
+											plus.runtime.install(path); // 自动安装apk文件
+										} else {
+											plus.nativeUI.alert('版本更新失败:' + status);
+										}
+									});
+									dtask.start();
+								}
+							}
+						})
+					} else if (flag_hot) {
+						console.log("热更新") 
+						uni.downloadFile({
+							url: res.data.data.wgtUrl,
+							success: (downloadResult) => {
+								console.log(downloadResult.tempFilePath)
+								if (downloadResult.statusCode === 200) {
+									plus.nativeUI.toast(`正在热更新!${res.data.data.versionCode}`);
+									plus.runtime.install(downloadResult.tempFilePath, {
+										force: false
+									}, function() {
+										plus.nativeUI.toast("热更新成功");
+										plus.runtime.restart();
+									}, function(e) {
+										console.log(e)
+										plus.nativeUI.toast(`热更新失败:${e.message}`);
+									});
+								}
+							}
+						});
+					}
+				
+				});
+	    	}
+	    },fail(res) {
+	    	console.log(222222222222222222222222222)
+			console.log(res)
+	    }
+	})
+
+}

+ 361 - 0
common/components/uni-rate.vue

@@ -0,0 +1,361 @@
+<template>
+	<view>
+		<view ref="uni-rate" class="uni-rate">
+			<view class="uni-rate__icon" :class="{'uni-cursor-not-allowed': disabled}"
+				:style="{ 'margin-right': marginNumber + 'px' }" v-for="(star, index) in stars" :key="index"
+				@touchstart.stop="touchstart" @touchmove.stop="touchmove" @mousedown.stop="mousedown"
+				@mousemove.stop="mousemove" @mouseleave="mouseleave">
+				<uni-icons :color="color" :size="size" :type="isFill ? 'star-filled' : 'star'" />
+				<!-- #ifdef APP-NVUE -->
+				<view :style="{ width: star.activeWitch.replace('%','')*size/100+'px'}" class="uni-rate__icon-on">
+					<uni-icons style="text-align: left;" :color="disabled?'#ccc':activeColor" :size="size"
+						type="star-filled" />
+				</view>
+				<!-- #endif -->
+				<!-- #ifndef APP-NVUE -->
+				<view :style="{ width: star.activeWitch}" class="uni-rate__icon-on">
+					<uni-icons :color="disabled?disabledColor:activeColor" :size="size" type="star-filled" />
+				</view>
+				<!-- #endif -->
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	// #ifdef APP-NVUE
+	const dom = uni.requireNativePlugin('dom');
+	// #endif
+	/**
+	 * Rate 评分
+	 * @description 评分组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=33
+	 * @property {Boolean} 	isFill = [true|false] 		星星的类型,是否为实心类型, 默认为实心
+	 * @property {String} 	color 						未选中状态的星星颜色,默认为 "#ececec"
+	 * @property {String} 	activeColor 				选中状态的星星颜色,默认为 "#ffca3e"
+	 * @property {String} 	disabledColor 				禁用状态的星星颜色,默认为 "#c0c0c0"
+	 * @property {Number} 	size 						星星的大小
+	 * @property {Number} 	value/v-model 				当前评分
+	 * @property {Number} 	max 						最大评分评分数量,目前一分一颗星
+	 * @property {Number} 	margin 						星星的间距,单位 px
+	 * @property {Boolean} 	disabled = [true|false] 	是否为禁用状态,默认为 false
+	 * @property {Boolean} 	readonly = [true|false] 	是否为只读状态,默认为 false
+	 * @property {Boolean} 	allowHalf = [true|false] 	是否实现半星,默认为 false
+	 * @property {Boolean} 	touchable = [true|false] 	是否支持滑动手势,默认为 true
+	 * @event {Function} change 						uniRate 的 value 改变时触发事件,e={value:Number}
+	 */
+
+	export default {
+		name: "UniRate",
+		props: {
+			isFill: {
+				// 星星的类型,是否镂空
+				type: [Boolean, String],
+				default: true
+			},
+			color: {
+				// 星星未选中的颜色
+				type: String,
+				default: "#ececec"
+			},
+			activeColor: {
+				// 星星选中状态颜色
+				type: String,
+				default: "#ffca3e"
+			},
+			disabledColor: {
+				// 星星禁用状态颜色
+				type: String,
+				default: "#c0c0c0"
+			},
+			size: {
+				// 星星的大小
+				type: [Number, String],
+				default: 24
+			},
+			value: {
+				// 当前评分
+				type: [Number, String],
+				default: 0
+			},
+			modelValue: {
+				// 当前评分
+				type: [Number, String],
+				default: 0
+			},
+			max: {
+				// 最大评分
+				type: [Number, String],
+				default: 5
+			},
+			margin: {
+				// 星星的间距
+				type: [Number, String],
+				default: 0
+			},
+			disabled: {
+				// 是否可点击
+				type: [Boolean, String],
+				default: false
+			},
+			readonly: {
+				// 是否只读
+				type: [Boolean, String],
+				default: false
+			},
+			allowHalf: {
+				// 是否显示半星
+				type: [Boolean, String],
+				default: false
+			},
+			touchable: {
+				// 是否支持滑动手势
+				type: [Boolean, String],
+				default: true
+			}
+		},
+		data() {
+			return {
+				valueSync: "",
+				userMouseFristMove: true,
+				userRated: false,
+				userLastRate: 1
+			};
+		},
+		watch: {
+			value(newVal) {
+				this.valueSync = Number(newVal);
+			},
+			modelValue(newVal) {
+				this.valueSync = Number(newVal);
+			},
+		},
+		computed: {
+			stars() {
+				const value = this.valueSync ? this.valueSync : 0;
+				const starList = [];
+				const floorValue = Math.floor(value);
+				const ceilValue = Math.ceil(value);
+				for (let i = 0; i < this.max; i++) {
+					if (floorValue > i) {
+						starList.push({
+							activeWitch: "100%"
+						});
+					} else if (ceilValue - 1 === i) {
+						starList.push({
+							activeWitch: (value - floorValue) * 100 + "%"
+						});
+					} else {
+						starList.push({
+							activeWitch: "0"
+						});
+					}
+				}
+				return starList;
+			},
+
+			marginNumber() {
+				return Number(this.margin)
+			}
+		},
+		created() {
+			this.valueSync = Number(this.value || this.modelValue);
+			this._rateBoxLeft = 0
+			this._oldValue = null
+		},
+		mounted() {
+			setTimeout(() => {
+				this._getSize()
+			}, 100)
+			// #ifdef H5
+			this.PC = this.IsPC()
+			// #endif
+		},
+		methods: {
+			touchstart(e) {
+				// #ifdef H5
+				if (this.IsPC()) return
+				// #endif
+				if (this.readonly || this.disabled) return
+				const {
+					clientX,
+					screenX
+				} = e.changedTouches[0]
+				// TODO 做一下兼容,只有 Nvue 下才有 screenX,其他平台式 clientX
+				this._getRateCount(clientX || screenX)
+			},
+			touchmove(e) {
+				// #ifdef H5
+				if (this.IsPC()) return
+				// #endif
+				if (this.readonly || this.disabled || !this.touchable) return
+				const {
+					clientX,
+					screenX
+				} = e.changedTouches[0]
+				this._getRateCount(clientX || screenX)
+			},
+
+			/**
+			 * 兼容 PC @tian
+			 */
+
+			mousedown(e) {
+				// #ifdef H5
+				if (!this.IsPC()) return
+				if (this.readonly || this.disabled) return
+				const {
+					clientX,
+				} = e
+				this.userLastRate = this.valueSync
+				this._getRateCount(clientX)
+				this.userRated = true
+				// #endif
+			},
+			mousemove(e) {
+				// #ifdef H5
+				if (!this.IsPC()) return
+				if (this.userRated) return
+				if (this.userMouseFristMove) {
+					console.log('---mousemove----', this.valueSync);
+					this.userLastRate = this.valueSync
+					this.userMouseFristMove = false
+				}
+				if (this.readonly || this.disabled || !this.touchable) return
+				const {
+					clientX,
+				} = e
+				this._getRateCount(clientX)
+				// #endif
+			},
+			mouseleave(e) {
+				// #ifdef H5
+				if (!this.IsPC()) return
+				if (this.readonly || this.disabled || !this.touchable) return
+				if (this.userRated) {
+					this.userRated = false
+					return
+				}
+				this.valueSync = this.userLastRate
+				// #endif
+			},
+			// #ifdef H5
+			IsPC() {
+				var userAgentInfo = navigator.userAgent;
+				var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
+				var flag = true;
+				for (let v = 0; v < Agents.length - 1; v++) {
+					if (userAgentInfo.indexOf(Agents[v]) > 0) {
+						flag = false;
+						break;
+					}
+				}
+				return flag;
+			},
+			// #endif
+
+			/**
+			 * 获取星星个数
+			 */
+			_getRateCount(clientX) {
+				this._getSize()
+				const size = Number(this.size)
+				if (size === NaN) {
+					return new Error('size 属性只能设置为数字')
+				}
+				const rateMoveRange = clientX - this._rateBoxLeft
+				let index = parseInt(rateMoveRange / (size + this.marginNumber))
+				index = index < 0 ? 0 : index;
+				index = index > this.max ? this.max : index;
+				const range = parseInt(rateMoveRange - (size + this.marginNumber) * index);
+				let value = 0;
+				if (this._oldValue === index && !this.PC) return;
+				this._oldValue = index;
+				if (this.allowHalf) {
+					if (range > (size / 2)) {
+						value = index + 1
+					} else {
+						value = index + 0.5
+					}
+				} else {
+					value = index + 1
+				}
+
+				value = Math.max(0.5, Math.min(value, this.max))
+				this.valueSync = value
+				this._onChange()
+			},
+
+			/**
+			 * 触发动态修改
+			 */
+			_onChange() {
+
+				this.$emit("input", this.valueSync);
+				this.$emit("update:modelValue", this.valueSync);
+				this.$emit("change", {
+					value: this.valueSync
+				});
+			},
+			/**
+			 * 获取星星距离屏幕左侧距离
+			 */
+			_getSize() {
+				// #ifndef APP-NVUE
+				uni.createSelectorQuery()
+					.in(this)
+					.select('.uni-rate')
+					.boundingClientRect()
+					.exec(ret => {
+						if (ret) {
+							this._rateBoxLeft = ret[0].left
+						}
+					})
+				// #endif
+				// #ifdef APP-NVUE
+				dom.getComponentRect(this.$refs['uni-rate'], (ret) => {
+					const size = ret.size
+					if (size) {
+						this._rateBoxLeft = size.left
+					}
+				})
+				// #endif
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	.uni-rate {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		line-height: 1;
+		font-size: 0;
+		flex-direction: row;
+		/* #ifdef H5 */
+		cursor: pointer;
+		/* #endif */
+	}
+
+	.uni-rate__icon {
+		position: relative;
+		line-height: 1;
+		font-size: 0;
+	}
+
+	.uni-rate__icon-on {
+		overflow: hidden;
+		position: absolute;
+		top: 0;
+		left: 0;
+		line-height: 1;
+		text-align: left;
+	}
+
+	.uni-cursor-not-allowed {
+		/* #ifdef H5 */
+		cursor: not-allowed !important;
+		/* #endif */
+	}
+</style>

+ 34 - 0
common/helper.js

@@ -0,0 +1,34 @@
+import * as config from '../config'
+const ossUploadUrl = 'https://taohaoliang.oss-cn-beijing.aliyuncs.com/';
+
+const contactCustomerService = (item) => {
+	console.log(item)
+	if (!item) {
+		item = '110'
+	}
+	console.log("联系客服")
+	uni.makePhoneCall({
+		phoneNumber: item,
+		success: (res) => {
+			console.log('调用成功!')
+		},
+		// 失败回调
+		fail: (res) => {
+			console.log('调用失败!')
+			contactCustomerService(item)
+		}
+	});
+}
+
+const logout = () => {
+	uni.removeStorage({
+		key: 'userInfo'
+	})
+}
+
+
+
+export default {
+logout,
+contactCustomerService
+}

+ 10 - 0
config/index.js

@@ -0,0 +1,10 @@
+const dev = {
+	baseUrlNew: 'http://192.168.110.82:8090',
+	// baseUrlNew: 'https://shiyaopinche.cspc.cn:10443',
+	h5Appid: 'wxb66b599f7f61b46f',
+	debug: false
+}
+
+export function def() {
+	return dev
+}

+ 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>

+ 0 - 0
js_sdk/js-amap/__MACOSX/amap-wx.130.js


+ 31 - 0
js_sdk/js-amap/amap-wx.130.js

@@ -0,0 +1,31 @@
+function AMapWX(a){this.key=a.key;this.requestConfig={key:a.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};this.MeRequestConfig={key:a.key,serviceName:"https://restapi.amap.com/rest/me"}}
+AMapWX.prototype.getWxLocation=function(a,b){wx.getLocation({type:"gcj02",success:function(c){c=c.longitude+","+c.latitude;wx.setStorage({key:"userLocation",data:c});b(c)},fail:function(c){wx.getStorage({key:"userLocation",success:function(d){d.data&&b(d.data)}});a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
+AMapWX.prototype.getMEKeywordsSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.city&&(d.city=b.city);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&(d.pageSize=b.pageSize);b.sig&&(d.sig=
+b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/local",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
+AMapWX.prototype.getMEIdSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.id&&(d.id=b.id);b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/id",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&
+0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
+AMapWX.prototype.getMEPolygonSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.polygon&&(d.polygon=b.polygon);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&(d.pageSize=b.pageSize);
+b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/polygon",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
+AMapWX.prototype.getMEaroundSearch=function(a){if(!a.options)return a.fail({errCode:"0",errMsg:"\u7f3a\u5c11\u5fc5\u8981\u53c2\u6570"});var b=a.options,c=this.MeRequestConfig,d={key:c.key,s:"rsx",platform:"WXJS",appname:a.key,sdkversion:"1.2.0",logversion:"2.0"};b.layerId&&(d.layerId=b.layerId);b.keywords&&(d.keywords=b.keywords);b.center&&(d.center=b.center);b.radius&&(d.radius=b.radius);b.filter&&(d.filter=b.filter);b.sortrule&&(d.sortrule=b.sortrule);b.pageNum&&(d.pageNum=b.pageNum);b.pageSize&&
+(d.pageSize=b.pageSize);b.sig&&(d.sig=b.sig);wx.request({url:c.serviceName+"/cpoint/datasearch/around",data:d,method:"GET",header:{"content-type":"application/json"},success:function(e){(e=e.data)&&e.status&&"1"===e.status&&0===e.code?a.success(e.data):a.fail({errCode:"0",errMsg:e})},fail:function(e){a.fail({errCode:"0",errMsg:e.errMsg||""})}})};
+AMapWX.prototype.getGeo=function(a){var b=this.requestConfig,c=a.options;b={key:this.key,extensions:"all",s:b.s,platform:b.platform,appname:this.key,sdkversion:b.sdkversion,logversion:b.logversion};c.address&&(b.address=c.address);c.city&&(b.city=c.city);c.batch&&(b.batch=c.batch);c.sig&&(b.sig=c.sig);wx.request({url:"https://restapi.amap.com/v3/geocode/geo",data:b,method:"GET",header:{"content-type":"application/json"},success:function(d){(d=d.data)&&d.status&&"1"===d.status?a.success(d):a.fail({errCode:"0",
+errMsg:d})},fail:function(d){a.fail({errCode:"0",errMsg:d.errMsg||""})}})};
+AMapWX.prototype.getRegeo=function(a){function b(d){var e=c.requestConfig;wx.request({url:"https://restapi.amap.com/v3/geocode/regeo",data:{key:c.key,location:d,extensions:"all",s:e.s,platform:e.platform,appname:c.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(g){if(g.data.status&&"1"==g.data.status){g=g.data.regeocode;var h=g.addressComponent,f=[],k=g.roads[0].name+"\u9644\u8fd1",m=d.split(",")[0],n=d.split(",")[1];if(g.pois&&
+g.pois[0]){k=g.pois[0].name+"\u9644\u8fd1";var l=g.pois[0].location;l&&(m=parseFloat(l.split(",")[0]),n=parseFloat(l.split(",")[1]))}h.provice&&f.push(h.provice);h.city&&f.push(h.city);h.district&&f.push(h.district);h.streetNumber&&h.streetNumber.street&&h.streetNumber.number?(f.push(h.streetNumber.street),f.push(h.streetNumber.number)):f.push(g.roads[0].name);f=f.join("");a.success([{iconPath:a.iconPath,width:a.iconWidth,height:a.iconHeight,name:f,desc:k,longitude:m,latitude:n,id:0,regeocodeData:g}])}else a.fail({errCode:g.data.infocode,
+errMsg:g.data.info})},fail:function(g){a.fail({errCode:"0",errMsg:g.errMsg||""})}})}var c=this;a.location?b(a.location):c.getWxLocation(a,function(d){b(d)})};
+AMapWX.prototype.getWeather=function(a){function b(g){var h="base";a.type&&"forecast"==a.type&&(h="all");wx.request({url:"https://restapi.amap.com/v3/weather/weatherInfo",data:{key:d.key,city:g,extensions:h,s:e.s,platform:e.platform,appname:d.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(f){if(f.data.status&&"1"==f.data.status)if(f.data.lives){if((f=f.data.lives)&&0<f.length){f=f[0];var k={city:{text:"\u57ce\u5e02",data:f.city},
+weather:{text:"\u5929\u6c14",data:f.weather},temperature:{text:"\u6e29\u5ea6",data:f.temperature},winddirection:{text:"\u98ce\u5411",data:f.winddirection+"\u98ce"},windpower:{text:"\u98ce\u529b",data:f.windpower+"\u7ea7"},humidity:{text:"\u6e7f\u5ea6",data:f.humidity+"%"}};k.liveData=f;a.success(k)}}else f.data.forecasts&&f.data.forecasts[0]&&a.success({forecast:f.data.forecasts[0]});else a.fail({errCode:f.data.infocode,errMsg:f.data.info})},fail:function(f){a.fail({errCode:"0",errMsg:f.errMsg||""})}})}
+function c(g){wx.request({url:"https://restapi.amap.com/v3/geocode/regeo",data:{key:d.key,location:g,extensions:"all",s:e.s,platform:e.platform,appname:d.key,sdkversion:e.sdkversion,logversion:e.logversion},method:"GET",header:{"content-type":"application/json"},success:function(h){if(h.data.status&&"1"==h.data.status){h=h.data.regeocode;if(h.addressComponent)var f=h.addressComponent.adcode;else h.aois&&0<h.aois.length&&(f=h.aois[0].adcode);b(f)}else a.fail({errCode:h.data.infocode,errMsg:h.data.info})},
+fail:function(h){a.fail({errCode:"0",errMsg:h.errMsg||""})}})}var d=this,e=d.requestConfig;a.city?b(a.city):d.getWxLocation(a,function(g){c(g)})};
+AMapWX.prototype.getPoiAround=function(a){function b(e){e={key:c.key,location:e,s:d.s,platform:d.platform,appname:c.key,sdkversion:d.sdkversion,logversion:d.logversion};a.querytypes&&(e.types=a.querytypes);a.querykeywords&&(e.keywords=a.querykeywords);wx.request({url:"https://restapi.amap.com/v3/place/around",data:e,method:"GET",header:{"content-type":"application/json"},success:function(g){if(g.data.status&&"1"==g.data.status){if((g=g.data)&&g.pois){for(var h=[],f=0;f<g.pois.length;f++){var k=0==
+f?a.iconPathSelected:a.iconPath;h.push({latitude:parseFloat(g.pois[f].location.split(",")[1]),longitude:parseFloat(g.pois[f].location.split(",")[0]),iconPath:k,width:22,height:32,id:f,name:g.pois[f].name,address:g.pois[f].address})}a.success({markers:h,poisData:g.pois})}}else a.fail({errCode:g.data.infocode,errMsg:g.data.info})},fail:function(g){a.fail({errCode:"0",errMsg:g.errMsg||""})}})}var c=this,d=c.requestConfig;a.location?b(a.location):c.getWxLocation(a,function(e){b(e)})};
+AMapWX.prototype.getStaticmap=function(a){function b(e){c.push("location="+e);a.zoom&&c.push("zoom="+a.zoom);a.size&&c.push("size="+a.size);a.scale&&c.push("scale="+a.scale);a.markers&&c.push("markers="+a.markers);a.labels&&c.push("labels="+a.labels);a.paths&&c.push("paths="+a.paths);a.traffic&&c.push("traffic="+a.traffic);e="https://restapi.amap.com/v3/staticmap?"+c.join("&");a.success({url:e})}var c=[];c.push("key="+this.key);var d=this.requestConfig;c.push("s="+d.s);c.push("platform="+d.platform);
+c.push("appname="+d.appname);c.push("sdkversion="+d.sdkversion);c.push("logversion="+d.logversion);a.location?b(a.location):this.getWxLocation(a,function(e){b(e)})};
+AMapWX.prototype.getInputtips=function(a){var b=Object.assign({},this.requestConfig);a.location&&(b.location=a.location);a.keywords&&(b.keywords=a.keywords);a.type&&(b.type=a.type);a.city&&(b.city=a.city);a.citylimit&&(b.citylimit=a.citylimit);wx.request({url:"https://restapi.amap.com/v3/assistant/inputtips",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.tips&&a.success({tips:c.data.tips})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||
+""})}})};
+AMapWX.prototype.getDrivingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);a.strategy&&(b.strategy=a.strategy);a.waypoints&&(b.waypoints=a.waypoints);a.avoidpolygons&&(b.avoidpolygons=a.avoidpolygons);a.avoidroad&&(b.avoidroad=a.avoidroad);wx.request({url:"https://restapi.amap.com/v3/direction/driving",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths,
+taxi_cost:c.data.route.taxi_cost||""})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
+AMapWX.prototype.getWalkingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);wx.request({url:"https://restapi.amap.com/v3/direction/walking",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
+AMapWX.prototype.getTransitRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);a.strategy&&(b.strategy=a.strategy);a.city&&(b.city=a.city);a.cityd&&(b.cityd=a.cityd);wx.request({url:"https://restapi.amap.com/v3/direction/transit/integrated",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&(c=c.data.route,a.success({distance:c.distance||"",taxi_cost:c.taxi_cost||
+"",transits:c.transits}))},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};
+AMapWX.prototype.getRidingRoute=function(a){var b=Object.assign({},this.requestConfig);a.origin&&(b.origin=a.origin);a.destination&&(b.destination=a.destination);wx.request({url:"https://restapi.amap.com/v3/direction/riding",data:b,method:"GET",header:{"content-type":"application/json"},success:function(c){c&&c.data&&c.data.route&&a.success({paths:c.data.route.paths})},fail:function(c){a.fail({errCode:"0",errMsg:c.errMsg||""})}})};module.exports.AMapWX=AMapWX;

+ 1122 - 0
js_sdk/qqmap-wx-jssdk.js

@@ -0,0 +1,1122 @@
+/**
+ * 微信小程序JavaScriptSDK
+ * 
+ * @version 1.2
+ * @date 2019-03-06
+ */
+
+var ERROR_CONF = {
+    KEY_ERR: 311,
+    KEY_ERR_MSG: 'key格式错误',
+    PARAM_ERR: 310,
+    PARAM_ERR_MSG: '请求参数信息有误',
+    SYSTEM_ERR: 600,
+    SYSTEM_ERR_MSG: '系统错误',
+    WX_ERR_CODE: 1000,
+    WX_OK_CODE: 200
+};
+var BASE_URL = 'https://apis.map.qq.com/ws/';
+var URL_SEARCH = BASE_URL + 'place/v1/search';
+var URL_SUGGESTION = BASE_URL + 'place/v1/suggestion';
+var URL_GET_GEOCODER = BASE_URL + 'geocoder/v1/';
+var URL_CITY_LIST = BASE_URL + 'district/v1/list';
+var URL_AREA_LIST = BASE_URL + 'district/v1/getchildren';
+var URL_DISTANCE = BASE_URL + 'distance/v1/';
+var URL_DIRECTION = BASE_URL + 'direction/v1/';
+var MODE = {
+  driving: 'driving',
+  transit: 'transit'
+};
+var EARTH_RADIUS = 6378136.49;
+var Utils = {
+  /**
+  * md5加密方法
+  * 版权所有©2011 Sebastian Tschan,https://blueimp.net
+  */
+  safeAdd(x, y) {
+    var lsw = (x & 0xffff) + (y & 0xffff);
+    var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+    return (msw << 16) | (lsw & 0xffff);
+  },
+  bitRotateLeft(num, cnt) {
+    return (num << cnt) | (num >>> (32 - cnt));
+  },
+  md5cmn(q, a, b, x, s, t) {
+    return this.safeAdd(this.bitRotateLeft(this.safeAdd(this.safeAdd(a, q), this.safeAdd(x, t)), s), b);
+  },
+  md5ff(a, b, c, d, x, s, t) {
+    return this.md5cmn((b & c) | (~b & d), a, b, x, s, t);
+  },
+  md5gg(a, b, c, d, x, s, t) {
+    return this.md5cmn((b & d) | (c & ~d), a, b, x, s, t);
+  },
+  md5hh(a, b, c, d, x, s, t) {
+    return this.md5cmn(b ^ c ^ d, a, b, x, s, t);
+  },
+  md5ii(a, b, c, d, x, s, t) {
+    return this.md5cmn(c ^ (b | ~d), a, b, x, s, t);
+  },
+  binlMD5(x, len) {
+    /* append padding */
+    x[len >> 5] |= 0x80 << (len % 32);
+    x[((len + 64) >>> 9 << 4) + 14] = len;
+
+    var i;
+    var olda;
+    var oldb;
+    var oldc;
+    var oldd;
+    var a = 1732584193;
+    var b = -271733879;
+    var c = -1732584194;
+    var d = 271733878;
+
+    for (i = 0; i < x.length; i += 16) {
+      olda = a;
+      oldb = b;
+      oldc = c;
+      oldd = d;
+
+      a = this.md5ff(a, b, c, d, x[i], 7, -680876936);
+      d = this.md5ff(d, a, b, c, x[i + 1], 12, -389564586);
+      c = this.md5ff(c, d, a, b, x[i + 2], 17, 606105819);
+      b = this.md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
+      a = this.md5ff(a, b, c, d, x[i + 4], 7, -176418897);
+      d = this.md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
+      c = this.md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
+      b = this.md5ff(b, c, d, a, x[i + 7], 22, -45705983);
+      a = this.md5ff(a, b, c, d, x[i + 8], 7, 1770035416);
+      d = this.md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
+      c = this.md5ff(c, d, a, b, x[i + 10], 17, -42063);
+      b = this.md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
+      a = this.md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
+      d = this.md5ff(d, a, b, c, x[i + 13], 12, -40341101);
+      c = this.md5ff(c, d, a, b, x[i + 14], 17, -1502002290);
+      b = this.md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
+
+      a = this.md5gg(a, b, c, d, x[i + 1], 5, -165796510);
+      d = this.md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
+      c = this.md5gg(c, d, a, b, x[i + 11], 14, 643717713);
+      b = this.md5gg(b, c, d, a, x[i], 20, -373897302);
+      a = this.md5gg(a, b, c, d, x[i + 5], 5, -701558691);
+      d = this.md5gg(d, a, b, c, x[i + 10], 9, 38016083);
+      c = this.md5gg(c, d, a, b, x[i + 15], 14, -660478335);
+      b = this.md5gg(b, c, d, a, x[i + 4], 20, -405537848);
+      a = this.md5gg(a, b, c, d, x[i + 9], 5, 568446438);
+      d = this.md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
+      c = this.md5gg(c, d, a, b, x[i + 3], 14, -187363961);
+      b = this.md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
+      a = this.md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
+      d = this.md5gg(d, a, b, c, x[i + 2], 9, -51403784);
+      c = this.md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
+      b = this.md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
+
+      a = this.md5hh(a, b, c, d, x[i + 5], 4, -378558);
+      d = this.md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
+      c = this.md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
+      b = this.md5hh(b, c, d, a, x[i + 14], 23, -35309556);
+      a = this.md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
+      d = this.md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
+      c = this.md5hh(c, d, a, b, x[i + 7], 16, -155497632);
+      b = this.md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
+      a = this.md5hh(a, b, c, d, x[i + 13], 4, 681279174);
+      d = this.md5hh(d, a, b, c, x[i], 11, -358537222);
+      c = this.md5hh(c, d, a, b, x[i + 3], 16, -722521979);
+      b = this.md5hh(b, c, d, a, x[i + 6], 23, 76029189);
+      a = this.md5hh(a, b, c, d, x[i + 9], 4, -640364487);
+      d = this.md5hh(d, a, b, c, x[i + 12], 11, -421815835);
+      c = this.md5hh(c, d, a, b, x[i + 15], 16, 530742520);
+      b = this.md5hh(b, c, d, a, x[i + 2], 23, -995338651);
+
+      a = this.md5ii(a, b, c, d, x[i], 6, -198630844);
+      d = this.md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
+      c = this.md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
+      b = this.md5ii(b, c, d, a, x[i + 5], 21, -57434055);
+      a = this.md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
+      d = this.md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
+      c = this.md5ii(c, d, a, b, x[i + 10], 15, -1051523);
+      b = this.md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
+      a = this.md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
+      d = this.md5ii(d, a, b, c, x[i + 15], 10, -30611744);
+      c = this.md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
+      b = this.md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
+      a = this.md5ii(a, b, c, d, x[i + 4], 6, -145523070);
+      d = this.md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
+      c = this.md5ii(c, d, a, b, x[i + 2], 15, 718787259);
+      b = this.md5ii(b, c, d, a, x[i + 9], 21, -343485551);
+
+      a = this.safeAdd(a, olda);
+      b = this.safeAdd(b, oldb);
+      c = this.safeAdd(c, oldc);
+      d = this.safeAdd(d, oldd);
+    }
+    return [a, b, c, d];
+  },
+  binl2rstr(input) {
+    var i;
+    var output = '';
+    var length32 = input.length * 32;
+    for (i = 0; i < length32; i += 8) {
+      output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xff);
+    }
+    return output;
+  },
+  rstr2binl(input) {
+    var i;
+    var output = [];
+    output[(input.length >> 2) - 1] = undefined;
+    for (i = 0; i < output.length; i += 1) {
+      output[i] = 0;
+    }
+    var length8 = input.length * 8;
+    for (i = 0; i < length8; i += 8) {
+      output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << (i % 32);
+    }
+    return output;
+  },
+  rstrMD5(s) {
+    return this.binl2rstr(this.binlMD5(this.rstr2binl(s), s.length * 8));
+  },
+  rstrHMACMD5(key, data) {
+    var i;
+    var bkey = this.rstr2binl(key);
+    var ipad = [];
+    var opad = [];
+    var hash;
+    ipad[15] = opad[15] = undefined;
+    if (bkey.length > 16) {
+      bkey = this.binlMD5(bkey, key.length * 8);
+    }
+    for (i = 0; i < 16; i += 1) {
+      ipad[i] = bkey[i] ^ 0x36363636;
+      opad[i] = bkey[i] ^ 0x5c5c5c5c;
+    }
+    hash = this.binlMD5(ipad.concat(this.rstr2binl(data)), 512 + data.length * 8);
+    return this.binl2rstr(this.binlMD5(opad.concat(hash), 512 + 128));
+  },
+  rstr2hex(input) {
+    var hexTab = '0123456789abcdef';
+    var output = '';
+    var x;
+    var i;
+    for (i = 0; i < input.length; i += 1) {
+      x = input.charCodeAt(i);
+      output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f);
+    }
+    return output;
+  },
+  str2rstrUTF8(input) {
+    return unescape(encodeURIComponent(input));
+  },
+  rawMD5(s) {
+    return this.rstrMD5(this.str2rstrUTF8(s));
+  },
+  hexMD5(s) {
+    return this.rstr2hex(this.rawMD5(s));
+  },
+  rawHMACMD5(k, d) {
+    return this.rstrHMACMD5(this.str2rstrUTF8(k), str2rstrUTF8(d));
+  },
+  hexHMACMD5(k, d) {
+    return this.rstr2hex(this.rawHMACMD5(k, d));
+  },
+
+  md5(string, key, raw) {
+    if (!key) {
+      if (!raw) {
+        return this.hexMD5(string);
+      }
+      return this.rawMD5(string);
+    }
+    if (!raw) {
+      return this.hexHMACMD5(key, string);
+    }
+    return this.rawHMACMD5(key, string);
+  },
+  /**
+   * 得到md5加密后的sig参数
+   * @param {Object} requestParam 接口参数
+   * @param {String} sk签名字符串
+   * @param {String} featrue 方法名
+   * @return 返回加密后的sig参数
+   */
+  getSig(requestParam, sk, feature, mode) {
+    var sig = null;
+    var requestArr = [];
+    Object.keys(requestParam).sort().forEach(function(key){
+      requestArr.push(key + '=' + requestParam[key]);
+    });
+    if (feature == 'search') {
+      sig = '/ws/place/v1/search?' + requestArr.join('&') + sk;
+    }
+    if (feature == 'suggest') {
+      sig = '/ws/place/v1/suggestion?' + requestArr.join('&') + sk;
+    }
+    if (feature == 'reverseGeocoder') {
+      sig = '/ws/geocoder/v1/?' + requestArr.join('&') + sk;
+    }
+    if (feature == 'geocoder') {
+      sig = '/ws/geocoder/v1/?' + requestArr.join('&') + sk;
+    }
+    if (feature == 'getCityList') {
+      sig = '/ws/district/v1/list?' + requestArr.join('&') + sk;
+    }
+    if (feature == 'getDistrictByCityId') {
+      sig = '/ws/district/v1/getchildren?' + requestArr.join('&') + sk;
+    }
+    if (feature == 'calculateDistance') {
+      sig = '/ws/distance/v1/?' + requestArr.join('&') + sk;
+    }
+    if (feature == 'direction') {
+      sig = '/ws/direction/v1/' + mode + '?' + requestArr.join('&') + sk;
+    }
+    sig = this.md5(sig);
+    return sig;
+  },
+    /**
+     * 得到终点query字符串
+     * @param {Array|String} 检索数据
+     */
+    location2query(data) {
+        if (typeof data == 'string') {
+            return data;
+        }
+        var query = '';
+        for (var i = 0; i < data.length; i++) {
+            var d = data[i];
+            if (!!query) {
+                query += ';';
+            }
+            if (d.location) {
+                query = query + d.location.lat + ',' + d.location.lng;
+            }
+            if (d.latitude && d.longitude) {
+                query = query + d.latitude + ',' + d.longitude;
+            }
+        }
+        return query;
+    },
+
+    /**
+     * 计算角度
+     */
+    rad(d) {
+      return d * Math.PI / 180.0;
+    },  
+    /**
+     * 处理终点location数组
+     * @return 返回终点数组
+     */
+    getEndLocation(location){
+      var to = location.split(';');
+      var endLocation = [];
+      for (var i = 0; i < to.length; i++) {
+        endLocation.push({
+          lat: parseFloat(to[i].split(',')[0]),
+          lng: parseFloat(to[i].split(',')[1])
+        })
+      }
+      return endLocation;
+    },
+
+    /**
+     * 计算两点间直线距离
+     * @param a 表示纬度差
+     * @param b 表示经度差
+     * @return 返回的是距离,单位m
+     */
+    getDistance(latFrom, lngFrom, latTo, lngTo) {
+      var radLatFrom = this.rad(latFrom);
+      var radLatTo = this.rad(latTo);
+      var a = radLatFrom - radLatTo;
+      var b = this.rad(lngFrom) - this.rad(lngTo);
+      var distance = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLatFrom) * Math.cos(radLatTo) * Math.pow(Math.sin(b / 2), 2)));
+      distance = distance * EARTH_RADIUS;
+      distance = Math.round(distance * 10000) / 10000;
+      return parseFloat(distance.toFixed(0));
+    },
+    /**
+     * 使用微信接口进行定位
+     */
+    getWXLocation(success, fail, complete) {
+        wx.getLocation({
+            type: 'gcj02',
+            success: success,
+            fail: fail,
+            complete: complete
+        });
+    },
+
+    /**
+     * 获取location参数
+     */
+    getLocationParam(location) {
+        if (typeof location == 'string') {
+            var locationArr = location.split(',');
+            if (locationArr.length === 2) {
+                location = {
+                    latitude: location.split(',')[0],
+                    longitude: location.split(',')[1]
+                };
+            } else {
+                location = {};
+            }
+        }
+        return location;
+    },
+
+    /**
+     * 回调函数默认处理
+     */
+    polyfillParam(param) {
+        param.success = param.success || function () { };
+        param.fail = param.fail || function () { };
+        param.complete = param.complete || function () { };
+    },
+
+    /**
+     * 验证param对应的key值是否为空
+     * 
+     * @param {Object} param 接口参数
+     * @param {String} key 对应参数的key
+     */
+    checkParamKeyEmpty(param, key) {
+        if (!param[key]) {
+            var errconf = this.buildErrorConfig(ERROR_CONF.PARAM_ERR, ERROR_CONF.PARAM_ERR_MSG + key +'参数格式有误');
+            param.fail(errconf);
+            param.complete(errconf);
+            return true;
+        }
+        return false;
+    },
+
+    /**
+     * 验证参数中是否存在检索词keyword
+     * 
+     * @param {Object} param 接口参数
+     */
+    checkKeyword(param){
+        return !this.checkParamKeyEmpty(param, 'keyword');
+    },
+
+    /**
+     * 验证location值
+     * 
+     * @param {Object} param 接口参数
+     */
+    checkLocation(param) {
+        var location = this.getLocationParam(param.location);
+        if (!location || !location.latitude || !location.longitude) {
+            var errconf = this.buildErrorConfig(ERROR_CONF.PARAM_ERR, ERROR_CONF.PARAM_ERR_MSG + ' location参数格式有误');
+            param.fail(errconf);
+            param.complete(errconf);
+            return false;
+        }
+        return true;
+    },
+
+    /**
+     * 构造错误数据结构
+     * @param {Number} errCode 错误码
+     * @param {Number} errMsg 错误描述
+     */
+    buildErrorConfig(errCode, errMsg) {
+        return {
+            status: errCode,
+            message: errMsg
+        };
+    },
+
+    /**
+     * 
+     * 数据处理函数
+     * 根据传入参数不同处理不同数据
+     * @param {String} feature 功能名称
+     * search 地点搜索
+     * suggest关键词提示
+     * reverseGeocoder逆地址解析
+     * geocoder地址解析
+     * getCityList获取城市列表:父集
+     * getDistrictByCityId获取区县列表:子集
+     * calculateDistance距离计算
+     * @param {Object} param 接口参数
+     * @param {Object} data 数据
+     */
+    handleData(param,data,feature){
+      if (feature == 'search') {
+        var searchResult = data.data;
+        var searchSimplify = [];
+        for (var i = 0; i < searchResult.length; i++) {
+          searchSimplify.push({
+            id: searchResult[i].id || null,
+            title: searchResult[i].title || null,
+            latitude: searchResult[i].location && searchResult[i].location.lat || null,
+            longitude: searchResult[i].location && searchResult[i].location.lng || null,
+            address: searchResult[i].address || null,
+            category: searchResult[i].category || null,
+            tel: searchResult[i].tel || null,
+            adcode: searchResult[i].ad_info && searchResult[i].ad_info.adcode || null,
+            city: searchResult[i].ad_info && searchResult[i].ad_info.city || null,
+            district: searchResult[i].ad_info && searchResult[i].ad_info.district || null,
+            province: searchResult[i].ad_info && searchResult[i].ad_info.province || null
+          })
+        }
+        param.success(data, {
+          searchResult: searchResult,
+          searchSimplify: searchSimplify
+        })
+      } else if (feature == 'suggest') {
+        var suggestResult = data.data;
+        var suggestSimplify = [];
+        for (var i = 0; i < suggestResult.length; i++) {
+          suggestSimplify.push({
+            adcode: suggestResult[i].adcode || null,
+            address: suggestResult[i].address || null,
+            category: suggestResult[i].category || null,
+            city: suggestResult[i].city || null,
+            district: suggestResult[i].district || null,
+            id: suggestResult[i].id || null,
+            latitude: suggestResult[i].location && suggestResult[i].location.lat || null,
+            longitude: suggestResult[i].location && suggestResult[i].location.lng || null,
+            province: suggestResult[i].province || null,
+            title: suggestResult[i].title || null,
+            type: suggestResult[i].type || null
+          })
+        }
+        param.success(data, {
+          suggestResult: suggestResult,
+          suggestSimplify: suggestSimplify
+          })
+      } else if (feature == 'reverseGeocoder') {
+        var reverseGeocoderResult = data.result;
+        var reverseGeocoderSimplify = {
+          address: reverseGeocoderResult.address || null,
+          latitude: reverseGeocoderResult.location && reverseGeocoderResult.location.lat || null,
+          longitude: reverseGeocoderResult.location && reverseGeocoderResult.location.lng || null,
+          adcode: reverseGeocoderResult.ad_info && reverseGeocoderResult.ad_info.adcode || null,
+          city: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.city || null,
+          district: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.district || null,
+          nation: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.nation || null,
+          province: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.province || null,
+          street: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.street || null,
+          street_number: reverseGeocoderResult.address_component && reverseGeocoderResult.address_component.street_number || null,
+          recommend: reverseGeocoderResult.formatted_addresses && reverseGeocoderResult.formatted_addresses.recommend || null,
+          rough: reverseGeocoderResult.formatted_addresses && reverseGeocoderResult.formatted_addresses.rough || null
+        };
+        if (reverseGeocoderResult.pois) {//判断是否返回周边poi
+          var pois = reverseGeocoderResult.pois;
+          var poisSimplify = [];
+          for (var i = 0;i < pois.length;i++) {
+            poisSimplify.push({
+              id: pois[i].id || null,
+              title: pois[i].title || null,
+              latitude: pois[i].location && pois[i].location.lat || null,
+              longitude: pois[i].location && pois[i].location.lng || null,
+              address: pois[i].address || null,
+              category: pois[i].category || null,
+              adcode: pois[i].ad_info && pois[i].ad_info.adcode || null,
+              city: pois[i].ad_info && pois[i].ad_info.city || null,
+              district: pois[i].ad_info && pois[i].ad_info.district || null,
+              province: pois[i].ad_info && pois[i].ad_info.province || null
+            })
+          }
+          param.success(data,{
+            reverseGeocoderResult: reverseGeocoderResult,
+            reverseGeocoderSimplify: reverseGeocoderSimplify,
+            pois: pois,
+            poisSimplify: poisSimplify
+          })
+        } else {
+          param.success(data, {
+            reverseGeocoderResult: reverseGeocoderResult,
+            reverseGeocoderSimplify: reverseGeocoderSimplify
+          })
+        }
+      } else if (feature == 'geocoder') {
+        var geocoderResult = data.result;
+        var geocoderSimplify = {
+          title: geocoderResult.title || null,
+          latitude: geocoderResult.location && geocoderResult.location.lat || null,
+          longitude: geocoderResult.location && geocoderResult.location.lng || null,
+          adcode: geocoderResult.ad_info && geocoderResult.ad_info.adcode || null,
+          province: geocoderResult.address_components && geocoderResult.address_components.province || null,
+          city: geocoderResult.address_components && geocoderResult.address_components.city || null,
+          district: geocoderResult.address_components && geocoderResult.address_components.district || null,
+          street: geocoderResult.address_components && geocoderResult.address_components.street || null,
+          street_number: geocoderResult.address_components && geocoderResult.address_components.street_number || null,
+          level: geocoderResult.level || null
+        };
+        param.success(data,{
+          geocoderResult: geocoderResult,
+          geocoderSimplify: geocoderSimplify
+        });
+      } else if (feature == 'getCityList') {
+        var provinceResult = data.result[0];
+        var cityResult = data.result[1];
+        var districtResult = data.result[2];
+        param.success(data,{
+          provinceResult: provinceResult,
+          cityResult: cityResult,
+          districtResult: districtResult
+        });
+      } else if (feature == 'getDistrictByCityId') {
+        var districtByCity = data.result[0];
+        param.success(data, districtByCity);
+      } else if (feature == 'calculateDistance') {
+        var calculateDistanceResult = data.result.elements;  
+        var distance = [];
+        for (var i = 0; i < calculateDistanceResult.length; i++){
+          distance.push(calculateDistanceResult[i].distance);
+        }   
+        param.success(data, {
+          calculateDistanceResult: calculateDistanceResult,
+          distance: distance
+          });
+      } else if (feature == 'direction') {
+        var direction = data.result.routes;
+        param.success(data,direction);
+      } else {
+        param.success(data);
+      }
+    },
+
+    /**
+     * 构造微信请求参数,公共属性处理
+     * 
+     * @param {Object} param 接口参数
+     * @param {Object} param 配置项
+     * @param {String} feature 方法名
+     */
+    buildWxRequestConfig(param, options, feature) {
+        var that = this;
+        options.header = { "content-type": "application/json" };
+        options.method = 'GET';
+        options.success = function (res) {
+            var data = res.data;
+            if (data.status === 0) {
+              that.handleData(param, data, feature);
+            } else {
+                param.fail(data);
+            }
+        };
+        options.fail = function (res) {
+            res.statusCode = ERROR_CONF.WX_ERR_CODE;
+            param.fail(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg));
+        };
+        options.complete = function (res) {
+            var statusCode = +res.statusCode;
+            switch(statusCode) {
+                case ERROR_CONF.WX_ERR_CODE: {
+                    param.complete(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg));
+                    break;
+                }
+                case ERROR_CONF.WX_OK_CODE: {
+                    var data = res.data;
+                    if (data.status === 0) {
+                        param.complete(data);
+                    } else {
+                        param.complete(that.buildErrorConfig(data.status, data.message));
+                    }
+                    break;
+                }
+                default:{
+                    param.complete(that.buildErrorConfig(ERROR_CONF.SYSTEM_ERR, ERROR_CONF.SYSTEM_ERR_MSG));
+                }
+
+            }
+        };
+        return options;
+    },
+
+    /**
+     * 处理用户参数是否传入坐标进行不同的处理
+     */
+    locationProcess(param, locationsuccess, locationfail, locationcomplete) {
+        var that = this;
+        locationfail = locationfail || function (res) {
+            res.statusCode = ERROR_CONF.WX_ERR_CODE;
+            param.fail(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg));
+        };
+        locationcomplete = locationcomplete || function (res) {
+            if (res.statusCode == ERROR_CONF.WX_ERR_CODE) {
+                param.complete(that.buildErrorConfig(ERROR_CONF.WX_ERR_CODE, res.errMsg));
+            }
+        };
+        if (!param.location) {
+            that.getWXLocation(locationsuccess, locationfail, locationcomplete);
+        } else if (that.checkLocation(param)) {
+            var location = Utils.getLocationParam(param.location);
+            locationsuccess(location);
+        }
+    }
+};
+
+
+class QQMapWX {
+
+    /**
+     * 构造函数
+     * 
+     * @param {Object} options 接口参数,key 为必选参数
+     */
+    constructor(options) {
+        if (!options.key) {
+            throw Error('key值不能为空');
+        }
+        this.key = options.key;
+    };
+
+    /**
+     * POI周边检索
+     *
+     * @param {Object} options 接口参数对象
+     * 
+     * 参数对象结构可以参考
+     * @see http://lbs.qq.com/webservice_v1/guide-search.html
+     */
+    search(options) {
+        var that = this;
+        options = options || {};
+
+        Utils.polyfillParam(options);
+
+        if (!Utils.checkKeyword(options)) {
+            return;
+        }
+
+        var requestParam = {
+            keyword: options.keyword,
+            orderby: options.orderby || '_distance',
+            page_size: options.page_size || 10,
+            page_index: options.page_index || 1,
+            output: 'json',
+            key: that.key
+        };
+
+        if (options.address_format) {
+            requestParam.address_format = options.address_format;
+        }
+
+        if (options.filter) {
+            requestParam.filter = options.filter;
+        }
+
+        var distance = options.distance || "1000";
+        var auto_extend = options.auto_extend || 1;
+        var region = null;
+        var rectangle = null;
+
+        //判断城市限定参数
+        if (options.region) {
+          region = options.region;
+        }
+
+        //矩形限定坐标(暂时只支持字符串格式)
+        if (options.rectangle) {
+          rectangle = options.rectangle;
+        }
+
+        var locationsuccess = function (result) {        
+          if (region && !rectangle) {
+            //城市限定参数拼接
+            requestParam.boundary = "region(" + region + "," + auto_extend + "," + result.latitude + "," + result.longitude + ")";
+            if (options.sig) {
+              requestParam.sig = Utils.getSig(requestParam, options.sig, 'search');
+            }
+          } else if (rectangle && !region) {
+            //矩形搜索
+            requestParam.boundary = "rectangle(" + rectangle + ")";
+            if (options.sig) {
+              requestParam.sig = Utils.getSig(requestParam, options.sig, 'search');
+            }
+            } else {
+              requestParam.boundary = "nearby(" + result.latitude + "," + result.longitude + "," + distance + "," + auto_extend + ")";
+            if (options.sig) {
+              requestParam.sig = Utils.getSig(requestParam, options.sig, 'search');
+            }
+            }            
+            wx.request(Utils.buildWxRequestConfig(options, {
+                url: URL_SEARCH,
+                data: requestParam
+            }, 'search'));
+        };
+        Utils.locationProcess(options, locationsuccess);
+    };
+
+    /**
+     * sug模糊检索
+     *
+     * @param {Object} options 接口参数对象
+     * 
+     * 参数对象结构可以参考
+     * http://lbs.qq.com/webservice_v1/guide-suggestion.html
+     */
+    getSuggestion(options) {
+        var that = this;
+        options = options || {};
+        Utils.polyfillParam(options);
+
+        if (!Utils.checkKeyword(options)) {
+            return;
+        }
+
+        var requestParam = {
+            keyword: options.keyword,
+            region: options.region || '全国',
+            region_fix: options.region_fix || 0,
+            policy: options.policy || 0,
+            page_size: options.page_size || 10,//控制显示条数
+            page_index: options.page_index || 1,//控制页数
+            get_subpois : options.get_subpois || 0,//返回子地点
+            output: 'json',
+            key: that.key
+        };
+        //长地址
+        if (options.address_format) {
+          requestParam.address_format = options.address_format;
+        }
+        //过滤
+        if (options.filter) {
+          requestParam.filter = options.filter;
+        }
+        //排序
+        if (options.location) {
+          var locationsuccess = function (result) {
+            requestParam.location = result.latitude + ',' + result.longitude;
+            if (options.sig) {
+              requestParam.sig = Utils.getSig(requestParam, options.sig, 'suggest');
+            }
+            wx.request(Utils.buildWxRequestConfig(options, {
+              url: URL_SUGGESTION,
+              data: requestParam
+            }, "suggest"));      
+          };
+          Utils.locationProcess(options, locationsuccess);
+        } else {
+          if (options.sig) {
+            requestParam.sig = Utils.getSig(requestParam, options.sig, 'suggest');
+          }
+          wx.request(Utils.buildWxRequestConfig(options, {
+            url: URL_SUGGESTION,
+            data: requestParam
+          }, "suggest"));      
+        }        
+    };
+
+    /**
+     * 逆地址解析
+     *
+     * @param {Object} options 接口参数对象
+     * 
+     * 请求参数结构可以参考
+     * http://lbs.qq.com/webservice_v1/guide-gcoder.html
+     */
+    reverseGeocoder(options) {
+        var that = this;
+        options = options || {};
+        Utils.polyfillParam(options);
+        var requestParam = {
+            coord_type: options.coord_type || 5,
+            get_poi: options.get_poi || 0,
+            output: 'json',
+            key: that.key
+        };
+        if (options.poi_options) {
+            requestParam.poi_options = options.poi_options
+        }
+
+        var locationsuccess = function (result) {
+            requestParam.location = result.latitude + ',' + result.longitude;
+          if (options.sig) {
+            requestParam.sig = Utils.getSig(requestParam, options.sig, 'reverseGeocoder');
+          }
+            wx.request(Utils.buildWxRequestConfig(options, {
+                url: URL_GET_GEOCODER,
+                data: requestParam
+            }, 'reverseGeocoder'));
+        };
+        Utils.locationProcess(options, locationsuccess);
+    };
+
+    /**
+     * 地址解析
+     *
+     * @param {Object} options 接口参数对象
+     * 
+     * 请求参数结构可以参考
+     * http://lbs.qq.com/webservice_v1/guide-geocoder.html
+     */
+    geocoder(options) {
+        var that = this;
+        options = options || {};
+        Utils.polyfillParam(options);
+
+        if (Utils.checkParamKeyEmpty(options, 'address')) {
+            return;
+        }
+
+        var requestParam = {
+            address: options.address,
+            output: 'json',
+            key: that.key
+        };
+
+        //城市限定
+        if (options.region) {
+          requestParam.region = options.region;
+        }
+
+        if (options.sig) {
+          requestParam.sig = Utils.getSig(requestParam, options.sig, 'geocoder');
+        }
+
+        wx.request(Utils.buildWxRequestConfig(options, {
+            url: URL_GET_GEOCODER,
+            data: requestParam
+        },'geocoder'));
+    };
+
+
+    /**
+     * 获取城市列表
+     *
+     * @param {Object} options 接口参数对象
+     * 
+     * 请求参数结构可以参考
+     * http://lbs.qq.com/webservice_v1/guide-region.html
+     */
+    getCityList(options) {
+        var that = this;
+        options = options || {};
+        Utils.polyfillParam(options);
+        var requestParam = {
+            output: 'json',
+            key: that.key
+        };
+
+        if (options.sig) {
+          requestParam.sig = Utils.getSig(requestParam, options.sig, 'getCityList');
+        }
+
+        wx.request(Utils.buildWxRequestConfig(options, {
+            url: URL_CITY_LIST,
+            data: requestParam
+        },'getCityList'));
+    };
+
+    /**
+     * 获取对应城市ID的区县列表
+     *
+     * @param {Object} options 接口参数对象
+     * 
+     * 请求参数结构可以参考
+     * http://lbs.qq.com/webservice_v1/guide-region.html
+     */
+    getDistrictByCityId(options) {
+        var that = this;
+        options = options || {};
+        Utils.polyfillParam(options);
+
+        if (Utils.checkParamKeyEmpty(options, 'id')) {
+            return;
+        }
+
+        var requestParam = {
+            id: options.id || '',
+            output: 'json',
+            key: that.key
+        };
+
+        if (options.sig) {
+          requestParam.sig = Utils.getSig(requestParam, options.sig, 'getDistrictByCityId');
+        }
+
+        wx.request(Utils.buildWxRequestConfig(options, {
+            url: URL_AREA_LIST,
+            data: requestParam
+        },'getDistrictByCityId'));
+    };
+
+    /**
+     * 用于单起点到多终点的路线距离(非直线距离)计算:
+     * 支持两种距离计算方式:步行和驾车。
+     * 起点到终点最大限制直线距离10公里。
+     *
+     * 新增直线距离计算。
+     * 
+     * @param {Object} options 接口参数对象
+     * 
+     * 请求参数结构可以参考
+     * http://lbs.qq.com/webservice_v1/guide-distance.html
+     */
+    calculateDistance(options) {
+        var that = this;
+        options = options || {};
+        Utils.polyfillParam(options);
+
+        if (Utils.checkParamKeyEmpty(options, 'to')) {
+            return;
+        }
+
+        var requestParam = {
+            mode: options.mode || 'walking',
+            to: Utils.location2query(options.to),
+            output: 'json',
+            key: that.key
+        };
+
+        if (options.from) {
+          options.location = options.from;
+        }
+
+        //计算直线距离
+        if(requestParam.mode == 'straight'){        
+          var locationsuccess = function (result) {
+            var locationTo = Utils.getEndLocation(requestParam.to);//处理终点坐标
+            var data = {
+              message:"query ok",
+              result:{
+                elements:[]
+              },
+              status:0
+            };
+            for (var i = 0; i < locationTo.length; i++) {
+              data.result.elements.push({//将坐标存入
+                distance: Utils.getDistance(result.latitude, result.longitude, locationTo[i].lat, locationTo[i].lng),
+                duration:0,
+                from:{
+                  lat: result.latitude,
+                  lng:result.longitude
+                },
+                to:{
+                  lat: locationTo[i].lat,
+                  lng: locationTo[i].lng
+                }
+              });            
+            }
+            var calculateResult = data.result.elements;
+            var distanceResult = [];
+            for (var i = 0; i < calculateResult.length; i++) {
+              distanceResult.push(calculateResult[i].distance);
+            }  
+            return options.success(data,{
+              calculateResult: calculateResult,
+              distanceResult: distanceResult
+            });
+          };
+          
+          Utils.locationProcess(options, locationsuccess);
+        } else {
+          var locationsuccess = function (result) {
+            requestParam.from = result.latitude + ',' + result.longitude;
+            if (options.sig) {
+              requestParam.sig = Utils.getSig(requestParam, options.sig, 'calculateDistance');
+            }
+            wx.request(Utils.buildWxRequestConfig(options, {
+              url: URL_DISTANCE,
+              data: requestParam
+            },'calculateDistance'));
+          };
+
+          Utils.locationProcess(options, locationsuccess);
+        }      
+    };
+
+  /**
+   * 路线规划:
+   * 
+   * @param {Object} options 接口参数对象
+   * 
+   * 请求参数结构可以参考
+   * https://lbs.qq.com/webservice_v1/guide-road.html
+   */
+  direction(options) {
+    var that = this;
+    options = options || {};
+    Utils.polyfillParam(options);
+
+    if (Utils.checkParamKeyEmpty(options, 'to')) {
+      return;
+    }
+
+    var requestParam = {
+      output: 'json',
+      key: that.key
+    };
+
+    //to格式处理
+    if (typeof options.to == 'string') {
+      requestParam.to = options.to;
+    } else {
+      requestParam.to = options.to.latitude + ',' + options.to.longitude;
+    }
+    //初始化局部请求域名
+    var SET_URL_DIRECTION = null;
+    //设置默认mode属性
+    options.mode = options.mode || MODE.driving;
+
+    //设置请求域名
+    SET_URL_DIRECTION = URL_DIRECTION + options.mode;
+
+    if (options.from) {
+      options.location = options.from;
+    }
+
+    if (options.mode == MODE.driving) {
+      if (options.from_poi) {
+        requestParam.from_poi = options.from_poi;
+      }
+      if (options.heading) {
+        requestParam.heading = options.heading;
+      }
+      if (options.speed) {
+        requestParam.speed = options.speed;
+      }
+      if (options.accuracy) {
+        requestParam.accuracy = options.accuracy;
+      }
+      if (options.road_type) {
+        requestParam.road_type = options.road_type;
+      }
+      if (options.to_poi) {
+        requestParam.to_poi = options.to_poi;
+      }
+      if (options.from_track) {
+        requestParam.from_track = options.from_track;
+      }
+      if (options.waypoints) {
+        requestParam.waypoints = options.waypoints;
+      }
+      if (options.policy) {
+        requestParam.policy = options.policy;
+      }
+      if (options.plate_number) {
+        requestParam.plate_number = options.plate_number;
+      }
+    }
+
+    if (options.mode == MODE.transit) {
+      if (options.departure_time) {
+        requestParam.departure_time = options.departure_time;
+      }
+      if (options.policy) {
+        requestParam.policy = options.policy;
+      }
+    } 
+
+    var locationsuccess = function (result) {
+      requestParam.from = result.latitude + ',' + result.longitude;
+      if (options.sig) {
+        requestParam.sig = Utils.getSig(requestParam, options.sig, 'direction',options.mode);
+      }
+      wx.request(Utils.buildWxRequestConfig(options, {
+        url: SET_URL_DIRECTION,
+        data: requestParam
+      }, 'direction'));
+    };
+
+    Utils.locationProcess(options, locationsuccess);
+  }
+};
+
+module.exports = QQMapWX;

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
js_sdk/qqmap-wx-jssdk.min.js


+ 272 - 0
js_sdk/wa-permission/permission.js

@@ -0,0 +1,272 @@
+/**
+ * 本模块封装了Android、iOS的应用权限判断、打开应用权限设置界面、以及位置系统服务是否开启
+ */
+
+var isIos
+// #ifdef APP-PLUS
+isIos = (plus.os.name == "iOS")
+// #endif
+
+// 判断推送权限是否开启
+function judgeIosPermissionPush() {
+	var result = false;
+	var UIApplication = plus.ios.import("UIApplication");
+	var app = UIApplication.sharedApplication();
+	var enabledTypes = 0;
+	if (app.currentUserNotificationSettings) {
+		var settings = app.currentUserNotificationSettings();
+		enabledTypes = settings.plusGetAttribute("types");
+		console.log("enabledTypes1:" + enabledTypes);
+		if (enabledTypes == 0) {
+			console.log("推送权限没有开启");
+		} else {
+			result = true;
+			console.log("已经开启推送功能!")
+		}
+		plus.ios.deleteObject(settings);
+	} else {
+		enabledTypes = app.enabledRemoteNotificationTypes();
+		if (enabledTypes == 0) {
+			console.log("推送权限没有开启!");
+		} else {
+			result = true;
+			console.log("已经开启推送功能!")
+		}
+		console.log("enabledTypes2:" + enabledTypes);
+	}
+	plus.ios.deleteObject(app);
+	plus.ios.deleteObject(UIApplication);
+	return result;
+}
+
+// 判断定位权限是否开启
+function judgeIosPermissionLocation() {
+	var result = false;
+	var cllocationManger = plus.ios.import("CLLocationManager");
+	var status = cllocationManger.authorizationStatus();
+	result = (status != 2)
+	console.log("定位权限开启:" + result);
+	// 以下代码判断了手机设备的定位是否关闭,推荐另行使用方法 checkSystemEnableLocation
+	/* var enable = cllocationManger.locationServicesEnabled();
+	var status = cllocationManger.authorizationStatus();
+	console.log("enable:" + enable);
+	console.log("status:" + status);
+	if (enable && status != 2) {
+		result = true;
+		console.log("手机定位服务已开启且已授予定位权限");
+	} else {
+		console.log("手机系统的定位没有打开或未给予定位权限");
+	} */
+	plus.ios.deleteObject(cllocationManger);
+	return result;
+}
+
+// 判断麦克风权限是否开启
+function judgeIosPermissionRecord() {
+	var result = false;
+	var avaudiosession = plus.ios.import("AVAudioSession");
+	var avaudio = avaudiosession.sharedInstance();
+	var permissionStatus = avaudio.recordPermission();
+	console.log("permissionStatus:" + permissionStatus);
+	if (permissionStatus == 1684369017 || permissionStatus == 1970168948) {
+		console.log("麦克风权限没有开启");
+	} else {
+		result = true;
+		console.log("麦克风权限已经开启");
+	}
+	plus.ios.deleteObject(avaudiosession);
+	return result;
+}
+
+// 判断相机权限是否开启
+function judgeIosPermissionCamera() {
+	var result = false;
+	var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
+	var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
+	console.log("authStatus:" + authStatus);
+	if (authStatus == 3) {
+		result = true;
+		console.log("相机权限已经开启");
+	} else {
+		console.log("相机权限没有开启");
+	}
+	plus.ios.deleteObject(AVCaptureDevice);
+	return result;
+}
+
+// 判断相册权限是否开启
+function judgeIosPermissionPhotoLibrary() {
+	var result = false;
+	var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
+	var authStatus = PHPhotoLibrary.authorizationStatus();
+	console.log("authStatus:" + authStatus);
+	if (authStatus == 3) {
+		result = true;
+		console.log("相册权限已经开启");
+	} else {
+		console.log("相册权限没有开启");
+	}
+	plus.ios.deleteObject(PHPhotoLibrary);
+	return result;
+}
+
+// 判断通讯录权限是否开启
+function judgeIosPermissionContact() {
+	var result = false;
+	var CNContactStore = plus.ios.import("CNContactStore");
+	var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
+	if (cnAuthStatus == 3) {
+		result = true;
+		console.log("通讯录权限已经开启");
+	} else {
+		console.log("通讯录权限没有开启");
+	}
+	plus.ios.deleteObject(CNContactStore);
+	return result;
+}
+
+// 判断日历权限是否开启
+function judgeIosPermissionCalendar() {
+	var result = false;
+	var EKEventStore = plus.ios.import("EKEventStore");
+	var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
+	if (ekAuthStatus == 3) {
+		result = true;
+		console.log("日历权限已经开启");
+	} else {
+		console.log("日历权限没有开启");
+	}
+	plus.ios.deleteObject(EKEventStore);
+	return result;
+}
+
+// 判断备忘录权限是否开启
+function judgeIosPermissionMemo() {
+	var result = false;
+	var EKEventStore = plus.ios.import("EKEventStore");
+	var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
+	if (ekAuthStatus == 3) {
+		result = true;
+		console.log("备忘录权限已经开启");
+	} else {
+		console.log("备忘录权限没有开启");
+	}
+	plus.ios.deleteObject(EKEventStore);
+	return result;
+}
+
+// Android权限查询
+function requestAndroidPermission(permissionID) {
+	return new Promise((resolve, reject) => {
+		plus.android.requestPermissions(
+			[permissionID], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装
+			function(resultObj) {
+				var result = 0;
+				for (var i = 0; i < resultObj.granted.length; i++) {
+					var grantedPermission = resultObj.granted[i];
+					console.log('已获取的权限:' + grantedPermission);
+					result = 1
+				}
+				for (var i = 0; i < resultObj.deniedPresent.length; i++) {
+					var deniedPresentPermission = resultObj.deniedPresent[i];
+					console.log('拒绝本次申请的权限:' + deniedPresentPermission);
+					result = 0
+				}
+				for (var i = 0; i < resultObj.deniedAlways.length; i++) {
+					var deniedAlwaysPermission = resultObj.deniedAlways[i];
+					console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
+					result = -1
+				}
+				resolve(result);
+				// 若所需权限被拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限
+				// if (result != 1) {
+				// gotoAppPermissionSetting()
+				// }
+			},
+			function(error) {
+				console.log('申请权限错误:' + error.code + " = " + error.message);
+				resolve({
+					code: error.code,
+					message: error.message
+				});
+			}
+		);
+	});
+}
+
+// 使用一个方法,根据参数判断权限
+function judgeIosPermission(permissionID) {
+	if (permissionID == "location") {
+		return judgeIosPermissionLocation()
+	} else if (permissionID == "camera") {
+		return judgeIosPermissionCamera()
+	} else if (permissionID == "photoLibrary") {
+		return judgeIosPermissionPhotoLibrary()
+	} else if (permissionID == "record") {
+		return judgeIosPermissionRecord()
+	} else if (permissionID == "push") {
+		return judgeIosPermissionPush()
+	} else if (permissionID == "contact") {
+		return judgeIosPermissionContact()
+	} else if (permissionID == "calendar") {
+		return judgeIosPermissionCalendar()
+	} else if (permissionID == "memo") {
+		return judgeIosPermissionMemo()
+	}
+	return false;
+}
+
+// 跳转到**应用**的权限页面
+function gotoAppPermissionSetting() {
+	if (isIos) {
+		var UIApplication = plus.ios.import("UIApplication");
+		var application2 = UIApplication.sharedApplication();
+		var NSURL2 = plus.ios.import("NSURL");
+		// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");		
+		var setting2 = NSURL2.URLWithString("app-settings:");
+		application2.openURL(setting2);
+
+		plus.ios.deleteObject(setting2);
+		plus.ios.deleteObject(NSURL2);
+		plus.ios.deleteObject(application2);
+	} else {
+		// console.log(plus.device.vendor);
+		var Intent = plus.android.importClass("android.content.Intent");
+		var Settings = plus.android.importClass("android.provider.Settings");
+		var Uri = plus.android.importClass("android.net.Uri");
+		var mainActivity = plus.android.runtimeMainActivity();
+		var intent = new Intent();
+		intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+		var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
+		intent.setData(uri);
+		mainActivity.startActivity(intent);
+	}
+}
+
+// 检查系统的设备服务是否开启
+// var checkSystemEnableLocation = async function () {
+function checkSystemEnableLocation() {
+	if (isIos) {
+		var result = false;
+		var cllocationManger = plus.ios.import("CLLocationManager");
+		var result = cllocationManger.locationServicesEnabled();
+		console.log("系统定位开启:" + result);
+		plus.ios.deleteObject(cllocationManger);
+		return result;
+	} else {
+		var context = plus.android.importClass("android.content.Context");
+		var locationManager = plus.android.importClass("android.location.LocationManager");
+		var main = plus.android.runtimeMainActivity();
+		var mainSvr = main.getSystemService(context.LOCATION_SERVICE);
+		var result = mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER);
+		console.log("系统定位开启:" + result);
+		return result
+	}
+}
+
+module.exports = {
+	judgeIosPermission: judgeIosPermission,
+	requestAndroidPermission: requestAndroidPermission,
+	checkSystemEnableLocation: checkSystemEnableLocation,
+	gotoAppPermissionSetting: gotoAppPermissionSetting
+}

+ 594 - 0
js_sdk/xfl-DateFormat/DateFormat.js

@@ -0,0 +1,594 @@
+
+
+/**
+ * 日期格式转换模块,还可以从字符串中提取日期,同时提供了几个数字格式转换函数。 
+ * 2019.5.12  v1.0.0
+ * 
+## 背景
+当需要将日期转换为指定的格式时,或者需要从某个字符串中按照指定的格式提取日期,均可使用本模块。
+
+## 使用说明  
+格式标记的含意:   
+yyyy-四位阿拉伯数字的年份,如: 2018  
+YYYY-四位中文数字的年份,如: 二零一八  
+yy-两位阿拉伯数字的年份,如: 18  
+YY-两位中文数字的年份,如: 一八  
+mm-一位或两位阿拉伯数字的月数,如: 3 、 12   
+0m-两位阿拉伯数字的月数,如: 03 、 12  
+MM-一位或两位中文数字的月数,如:  三、 十二   
+dd-一位或两位阿拉伯数字的日数,如: 3 、 12   
+0d-两位阿拉伯数字的日数,如: 03 、 12、28   
+DD-中文数字的日数,如:  三、 十二、二十八   
+hh-一位或两位阿拉伯数字的小时数,如: 3 、 12   
+0h-两位阿拉伯数字的小时数,如: 03 、 12   
+HH-中文数字的小时数,如:  三、 十二、二十三   
+ff-一位或两位阿拉伯数字的分钟数,如: 3 、 12、56  
+0f-两位阿拉伯数字的分钟数,如: 03 、 12  
+0F-中文数字的分钟,如:  零三、 十二、二十三  
+FF-中文数字的分钟,如:  三、 十二、二十三   
+ss-一位或两位阿拉伯数字的秒数,如: 3 、 12   
+0s-两位阿拉伯数字的秒数,如: 03 、 12   
+0S-中文数字的秒数,如:  零三、 十二、二十三   
+SS-中文数字的秒数,如:  三、 十二、二十三   
+w-阿拉伯数字的星期数,如:  1、6、7  
+W-中文数字的星期数,如:  一、三、六、日   
+WT-中文数字的星期数,如:  一、三、六、天   
+  
+### 属性  
+这些属性是可读可写的,如果直接更改,则会自动计算其关联的其它值。  
+dataformat.year; //年 [1000, 3000]  
+dataformat.moth; //月 [1, 12]  
+dataformat.day; //日 [1, 31]  
+dataformat.hour; //时 [0, 23]  
+dataformat.minute; //分 [0, 59]  
+dataformat.second; //秒 [0, 59]  
+dataformat.mscond; //毫秒 [0, 999]  
+dataformat.week; //星期 [1, 7]  
+dataformat.timestamp; //时间戳  
+
+### 构造函数
+构造函数内部直接调用的 setTime(ms, formatStr);
+  
+### toString(formatStr)  
+  
+使用举例:   
+1. toString() //结果: '2014-11-7 03:08:01'  
+2. toString('yyyy-mm-dd hh:ff:ss') //结果: '2014-11-11 13:12:34'  
+3. toString('yyyy年mm月dd日') //结果: '2014年11月11日'  
+4. toString('yy年mm月dd日') //结果: '14年11月11日'  
+5. toString('YY年MM月DD日') //结果: '一四年十一月八日'  
+6. toString('星期W') //结果: '星期日'  或 '星期三'
+7. toString('星期WT')  //结果: '星期天'  或 '星期三'
+8. toString('星期w') //结果: '星期1'   或 '星期7'
+9. toString('hh:0f:ss') //结果: '3:04:5'  
+10. toString('date') //结果: '2014-11-7'  
+11. toString('time') //结果: '03:08:01'  
+  
+### setTime(ms, formtStr)  
+设置时间。如果存在 formatStr, 则表示从字符串 ms 中按 formatStr中的规则提取时间。  
+  
+使用举例:  
+1. setTime( new Date() ); //传入一个时间对象  
+2. setTime( new Date().getTime() ); //传入一个毫秒数  
+3. setTime( new Date().getTime() ); //传入一个毫秒数  
+4. setTime( new Date().getTime() + '' ); //传入一个毫秒数的字符串  
+5. setTime(); //传入其它的值或不传入,则创建当前时间  
+6. setTime(str, formatStr); //传入两个参数,第一个为字符串,第二个为格式规则,表示按照规则从字符串中提取时间  
+7. setTime('今天是2014年8月9号,天气特别好,但明天是2014年8月10号,....', '明天是yyyy年mm月dd号');  // 得到  2014.8.10 0:0:0
+  
+### fillChar(num, len, char)  
+字符填充  
+  
+使用举例:   
+1. fillChar(44, 5, '*'); //结果: '***44'   
+2. fillChar(44, 5); //结果: '00044'   
+3. fillChar('44', 5); //结果: '   44'   
+4. fillChar('aaa', 5); //结果: '   aa'   
+5. fillChar('aaa', 5, '$'); //结果: '$$aaa'   
+5. fillChar('aaa', 1, '$'); //结果: 'aaa'  //指定的长度太小,不做任何操作。  
+  
+### perNumToChinese(num)  
+将阿拉伯数字每一位对应转换为中文的格式。  
+  
+使用举例:   
+1. perNumToChinese(34); //结果: '三四'  
+2. perNumToChinese(3874); //结果: '三八七四'  
+3. perNumToChinese('3874'); //结果: '三八七四'  
+4. perNumToChinese('404'); //结果: '四零四'    
+5. perNumToChinese('0  04   '); //结果: '零零四'  
+6. perNumToChinese('  '); //结果: ''  
+  
+### perChineseToNum(numStr)
+将中文的格式的数字每一位对应转换为阿拉伯数字。 是perNumToChinese(num)的逆操作。  
+  
+使用举例:   
+1. perChineseToNum('三四'); //结果: 34  
+2. perChineseToNum('三八七四'); //结果: 3874  
+3. perChineseToNum('四零四'); //结果: 404  
+4. perChineseToNum('零零四'); //结果: 4  
+5. perChineseToNum('  '); //结果: NaN  
+  
+### numToChinese(num)  
+将阿拉伯数字转换为中文的格式 , 最多只能处理13位数(万亿)  
+  
+使用举例:   
+1. numToChinese(0); //结果: '零'  
+2. numToChinese(5); //结果: '五'  
+3. numToChinese(16); //结果: '十六'  
+4. numToChinese(34); //结果: '三十四'  
+5. numToChinese(106); //结果: '一百零六'  
+6. numToChinese(886); //结果: '八百八十六'  
+7. numToChinese(1004); //结果: '一千零四'  
+8. numToChinese(1000); //结果: '一千'  
+9. numToChinese(9904); //结果: '九千九百零四'  
+10. numToChinese(19904); //结果: '一万九千九百零四'  
+11. numToChinese(10004); //结果: '一万零四'  
+12. numToChinese(10000); //结果: '一万'  
+13. numToChinese(100404); //结果: '十万零四百零四'  
+14. numToChinese(9000000); //结果: '九百万'  
+15. numToChinese(90000000); //结果: '九千万'  
+16. numToChinese(900000000); //结果: '九亿'  
+17. numToChinese(9000000000); //结果: '九十亿'  
+18. numToChinese(9020030401); //结果: '九十亿零二千零三万零四百零一'  
+19. numToChinese(90000000000); //结果: '九百亿'  
+20. numToChinese(900000000000); //结果: '九千亿'  
+21. numToChinese(9000000000000); //结果: '九万亿'  
+22. numToChinese(90000000000000); //结果: undefined  
+  
+  
+### chineseToNum (numStr)
+将中文的格式的数字转换为阿拉伯数字, 最多只能处理13位数(万亿)。 是 numToChinese(num) 函数的逆操作。  
+  
+使用举例:   
+1. chineseToNum('十六'); //结果: 16  
+2. chineseToNum('一万九千九百零四'); //结果: 19904  
+3. chineseToNum('十万零四百零四'); //结果: 100404   
+4. chineseToNum('九十亿零二千零三万零四百零一'); //结果: 9020030401  
+ */
+export default class DateFormat{
+    constructor(ms, formatStr){
+        this.setTime(ms, formatStr);
+    }
+
+    set year(y){ if(this.y !== y){ this.__date.setFullYear(y); this.setTime(this.__date); }  }
+    get year(){ return this.y; }
+
+    set month(y){ if(this.m !== y){ this.__date.setMonth(y-1); this.setTime(this.__date); }  }
+    get month(){ return this.m; }
+
+    set day(y){ if(this.d !== y){ this.__date.setDate(y); this.setTime(this.__date); }  }
+    get day(){ return this.d; }
+
+    set hour(y){ if(this.h !== y){ this.__date.setHours(y); this.setTime(this.__date); }  }
+    get hour(){ return this.h; }
+
+    set minute(y){ if(this.f !== y){ this.__date.setMinutes(y); this.setTime(this.__date); }  }
+    get minute(){ return this.f; }
+
+    set second(y){ if(this.s !== y){ this.__date.setSeconds(y); this.setTime(this.__date); }  }
+    get second(){ return this.s; }
+
+    set msecond(y){ if(this.ms !== y){ this.__date.setMilliseconds(y); this.setTime(this.__date); }  }
+    get msecond(){ return this.ms; }
+
+    get week(){ return this.w; }
+
+     setTime(ms, formatStr){  //切换时间
+        if(formatStr && typeof ms === 'string'){
+            let result;
+            if(this.__analysis){
+                result = this.__analysis(ms, formatStr);
+            }
+            if(!result){
+                console.log('匹配时间失败,默认设置为当前时间');
+                ms = '';
+            }else{
+                ms = result;
+            }
+        }
+        let date;
+        if(ms instanceof Date){
+            date = ms;
+        }else if(typeof ms === 'number'){
+            date = new Date(ms);
+        }else if( parseInt(ms) > 0 ){
+            date = new Date(parseInt(ms));
+        }else{
+            if(typeof ms === 'object' && ms.y && ms.m && ms.d){
+                date = new Date(ms.y, ms.m-1, ms.d, ms.h || 0, ms.f || 0, ms.s || 0, ms.ms || 0);
+            }else{
+                date = new Date();
+            }
+        }
+
+        this.y = date.getFullYear(), //年 [1000, 3000]
+        this.m = date.getMonth() + 1, //月 [1, 12]
+        this.d = date.getDate(), //日 [1, 31]
+        this.h = date.getHours(), //时 [0, 23]
+        this.f = date.getMinutes(), //分 [0, 59]
+        this.s = date.getSeconds(), //秒 [0, 59]
+        this.ms = date.getMilliseconds(), //秒 [0, 9999]
+        this.w = date.getDay(); //星期 [1, 7]
+        if(this.w === 0){  //0表示星期天
+            this.w = 7;
+        }
+        this.timestamp = date.getTime(); //时间戳
+
+        this.__date = date;
+        
+        return this;
+     }
+
+/***************************************************** 时间格式化 *********************************************** */
+     toString(formatStr){  //将时间转换为格式的字符串
+         if(formatStr){
+            if(formatStr === 'time'){
+                //03:00:01
+                return this.fillChar(this.h, 2) + ':' + this.fillChar(this.f, 2) + ':' + this.fillChar(this.s, 2);
+            }else if(formatStr === 'date'){
+                //2014-11-7
+                return this.y + '-' + this.m + '-' + this.d;
+            }else{
+               return this.__replaceTime(formatStr);
+            }
+            
+        }else{
+            //2014-11-7 03:00:01
+            return this.y + '-' + this.m + '-' + this.d + ' ' + this.fillChar(this.h, 2) + ':' + this.fillChar(this.f, 2) + ':' + this.fillChar(this.s, 2);
+        }
+     }
+
+     valueOf(){
+         return this.toString();
+     }
+ 
+     __replaceTime(format){
+        var str = 
+        'y{4}|Y{4}|y{2}|Y{2}|mm|MM|0m|dd|DD|0d|hh|HH|0h|ff|FF|0F|0f|ss|SS|0S|0s|w|WT|W';
+        var reg = new RegExp(str, 'g');
+
+
+        format = format.replace(reg, (mstr)=>{
+           switch(mstr){
+              case 'yyyy': return this.y;                        // [0000, 9999]
+              case 'YYYY': return this.perNumToChinese(this.y);  // [零零零零-九九九九]   
+              case 'yy': return ('' + this.y).slice(-2);         // [00, 99]
+              case 'YY': return this.perNumToChinese( +('' + this.y).slice(-2) ); // [零零-九九]    
+              case 'mm': return this.m;                          // [1, 12]
+              case 'MM': return this.numToChinese(this.m);       // [一-十二]    
+              case '0m': return this.m < 10 ? '0'+ this.m : this.m; // [01, 12]
+              case 'dd': return this.d;                          // [1, 31]
+              case 'DD': return this.numToChinese(this.d);       // [一-三十一]  
+              case '0d': return this.d < 10 ? '0' + this.d : this.d; // [01, 31]
+              case 'hh': return this.h;                          // [0, 24]
+              case 'HH': return this.numToChinese(this.h);       // [零-二十四]  
+              case '0h': return this.h < 10 ? '0' + this.h : this.h; // [00, 24]
+              case 'ff': return this.f;                          // [0, 60]
+              case 'FF': return this.numToChinese(this.f);       // [零-六十]
+              case '0F': return this.f<10&&this.f>0 ? ('零' + this.numToChinese(this.f) ):this.numToChinese(this.f); // [零零-六十]   
+              case '0f': return this.f < 10 ? '0' + this.f : this.f; // [00, 60]
+              case 'ss': return this.s;                          // [0, 60]
+              case 'SS': return this.numToChinese(this.s);       // [零-六十]
+              case '0S': return this.s<10&&this.s>0 ? ('零' + this.numToChinese(this.s) ):this.numToChinese(this.s); // [零零-六十]    
+              case '0s': return this.s < 10 ? '0' + this.s : this.s;  // [00, 60]
+              case 'w': return this.w;                           // [7]
+              case 'W': return this.w == 7 ? '日' : this.numToChinese(this.w);  // [天|日]
+              case 'WT': return this.w == 7 ? '天' : this.numToChinese(this.w);  // [天|日]
+           }
+        });
+         return format;
+     }
+
+/***************************************************** 时间提取 *********************************************** */
+     __analysis(str, rule){  //从指定的字符串中根据规则提取日期和时间。
+         let resultObj;
+        if(typeof rule === 'function'){
+            resultObj = rule(str);
+            if(!resultObj){
+                return null;
+            }
+        }else{
+            resultObj = {};
+            var str11 = 
+            'y{4}|Y{4}|y{2}|Y{2}|mm|MM|0m|dd|DD|0d|hh|HH|0h|ff|FF|0F|0f|ss|SS|0S|0s|w|WT|W';
+            var reg = new RegExp(str11, 'g');
+
+            // rule = 'yyyy年mm月'
+            var namesArr = [null];   // [null, 'yyyy', 'mm']
+            rule = rule.replace(reg, (mstr)=>{
+                var result = this.__ANALYREGSTR[mstr];
+                if(result){
+                    namesArr.push(mstr);
+                    return `(${result})`;
+                }
+             });
+            //  rule = '(\\d{4})年([1-9]|1[012])月'
+
+            var resultArr = str.match( new RegExp(rule) );
+             if(resultArr){
+                for(let i=1,val; i<namesArr.length; i++){
+                    val = resultArr[i];
+                    if(val == null){
+                        continue;
+                    }
+                    switch(namesArr[i]){
+                        case 'yyyy': resultObj.y = +val; break;                        // [0000, 9999]
+                        case 'YYYY': resultObj.y = this.perChineseToNum(val); break;   // [零零零零-九九九九]   
+                        case 'yy': resultObj.y = +val + 2000; break;          // [00, 99]
+                        case 'YY': resultObj.y = this.perChineseToNum(val) + 2000; break;  // [零零-九九]    
+                        case 'mm': resultObj.m = +val;    break;                        // [1, 12]
+                        case 'MM': resultObj.m = this.chineseToNum(val);  break;       // [一-十二]    
+                        case '0m': resultObj.m = +val;  break; // [01, 12]
+                        case 'dd': resultObj.d = +val; break;                           // [1, 31]
+                        case 'DD': resultObj.d = this.chineseToNum(val); break;        // [一-三十一]  
+                        case '0d': resultObj.d = +val; break;  // [01, 31]
+                        case 'hh': resultObj.h = +val;  break;                           // [0, 24]
+                        case 'HH': resultObj.h = this.chineseToNum(val); break;        // [零-二十四]  
+                        case '0h': resultObj.h = +val; break;  // [00, 24]
+                        case 'ff': resultObj.f = +val;   break;                         // [0, 60]
+                        case 'FF': resultObj.f = this.chineseToNum(val); break;        // [零-六十]
+                        case '0F': resultObj.f = this.chineseToNum(val); break;  // [零零-六十]   
+                        case '0f': resultObj.f = +val; break;   // [00, 60]
+                        case 'ss': resultObj.s = +val;   break;                         // [0, 60]
+                        case 'SS': resultObj.s = this.chineseToNum(val);  break;       // [零-六十]
+                        case '0S': resultObj.s = this.chineseToNum(val); break;  // [零零-六十]    
+                        case '0s': resultObj.s = +val; break;    // [00, 60]
+                        case 'w': resultObj.w = 7; break;                             // [7]
+                        case 'W': resultObj.w = 7; break;      // [天|日]
+                        case 'WT': resultObj.w = 7; break;      // [天|日]
+                    }
+                }
+             }else{
+                return null;
+             }
+             
+        }
+        //   'yyyy年mm月'  '(\\d{4})年([1-9]|1[012])月'
+        return resultObj;
+     }
+
+     __ANALYREGSTR = {
+         'yyyy': '\\d{4}',                   // [0000, 9999]
+         'YYYY': '[零一二三四五六七八九]{4}',              // [零零零零-九九九九]    
+         'yy': '\\d{2}',                     // [00, 99]
+         'YY': '[零一二三四五六七八九]{2}',                // [零零-九九]    
+         'mm': '[1-9]|1[012]',               // [1, 12]
+         'MM': '十[一二]?|[一二三四五六七八九]',// [一-十二]    
+         '0m': '[0][1-9]|1[012]',             // [01, 12]
+         'dd': '3[01]|[12]0|[12]?[1-9]',            //  [1, 31]
+         'DD': '三十[一]?|二?十[一二三四五六七八九]|[零一二三四五六七八九十]',     // [一-三十一]  
+         '0d': '[012][0-9]|3[01]',            //  [01, 31]
+         'hh': '2[0-4]|[1]?[0-9]',            //  [0, 24]
+         'HH': '二十[一二三]?|十[一二三四五六七八九]|[零一二三四五六七八九十]',   // [零-二十四]  
+         '0h': '[01][0-9]|2[0-4]',             // [00, 24]
+         'ff': '60|[1-5]?[0-9]',               // [0, 60]
+         'FF': '[二三四五]十[一二三四五六七八九]|十[一二三四五六七八九]|[二三四五六]十|[零一二三四五六七八九十]',    // [零-六十]
+         '0F': '零[零一二三四五六七八九]|十[一二三四五六七八九]|[二三四五]十[一二三四五六七八九]|[二三四五六]十|十', // [零零-六十]     
+         '0f': '[0-5][0-9]|60',               // [00, 60]
+         'ss': '60|[1-5]?[0-9]',              // [0, 60]
+         'SS': '[二三四五]十[一二三四五六七八九]|十[一二三四五六七八九]|[二三四五六]十|[零一二三四五六七八九十]',    // [零-六十] 
+         '0S': '零[零一二三四五六七八九]|十[一二三四五六七八九]|[二三四五]十[一二三四五六七八九]|[二三四五六]十|十', // [零零-六十]    
+         '0s': '[0-5][0-9]|60',               // [00, 60]
+         'w': '7',                            // [7]
+         'W': '日',                        //[天|日]
+         'WT': '天',                        //[天|日]
+     }
+
+/***************************************************** 字符填充部分 *********************************************** */
+      /**
+     * 在一个字符前面补充指定个数的字符。
+     * <br><font color="red">ref: null</font> <br>
+     * @public
+     * @param {string|number} num -  要操作的数字或字符串。
+     * @param {number} len -  要保留的最大长度。如果指定的长度不比传入的参数的本身的长度大,则不做任何操作。
+     * @param {string} [char=' '|'0'] -  当长度不足时,要在最前面补足的字符。第一个参数为数字时,默认为0,否则默认为' '。
+     * @example
+       1. fillChar(44, 5, '*'); //结果: '***44' 
+       2. fillChar(44, 5); //结果: '00044' 
+       3. fillChar('44', 5); //结果: '   44' 
+       4. fillChar('aaa', 5); //结果: '   aa' 
+       5. fillChar('aaa', 5, '$'); //结果: '$$aaa' 
+       5. fillChar('aaa', 1, '$'); //结果: 'aaa'  //指定的长度太小,不做任何操作。
+     */
+    fillChar(num, len, char){
+        if(typeof num === 'number'){
+            char = char || '0';
+        }else{
+            char = char || ' ';
+        }
+        let str = '' + num, charStr = '';
+
+        if(str.length < len){
+            for(let i=0, len1=len-str.length; i<len1; i++){
+                charStr += char;
+            }
+            return charStr + str;
+        }
+
+        return str;
+    }
+
+/***************************************************** 数字转换部分 *********************************************** */
+
+    __SIZEPOW = {'十': 1, '百': 2, '千': 3, '万': 4, '亿': 8};
+    /** 
+     * 将中文的格式的数字转换为阿拉伯数字, 最多只能处理13位数(万亿)。 是 numToChinese(num) 函数的逆操作。 
+     *  @example
+        1. chineseToNum('十六'); //结果: 16  
+        2. chineseToNum('一万九千九百零四'); //结果: 19904  
+        3. chineseToNum('十万零四百零四'); //结果: 100404   
+        4. chineseToNum('九十亿零二千零三万零四百零一'); //结果: 9020030401  
+     */
+    chineseToNum (numStr){   //目前只能处理万亿以下的数据
+        if(numStr.length === 1){
+            return this.perChineseToNum(numStr);
+        }
+        if(numStr.length === 2 && numStr[0] === '十'){
+            return this.perChineseToNum(numStr[1]) + 10;
+        }
+
+        let arr;
+        if(!numStr.match(/[亿万]/)){
+            arr = numStr.split('零'); 
+            let num = 0, val = 0;
+            for(let i=0, str=''; i<arr.length; i++){
+                str = arr[i];
+                for(let j=0; j<str.length; j++){
+                    if(str[j].match(/[一二三四五六七八九]/)){
+                        num += val;
+                        val = 1;
+                        val *= this.perChineseToNum(str[j]);
+                    }else{
+                        val *= Math.pow(10, this.__SIZEPOW[str[j]] )
+                    }
+                }
+            }
+            num += val;
+            return num;
+        }else{
+            arr = numStr.split('亿');
+            if(arr.length > 1){
+                var arr1 = arr[1].split('万');
+                return this.chineseToNum(arr[0])*Math.pow(10, 8) + this.chineseToNum(arr1[0])*10000 + this.chineseToNum(arr1[1]);
+            }else{
+                return this.chineseToNum(arr[0])*Math.pow(10, 8) + this.chineseToNum(arr[1]);
+            }
+        }
+    }
+
+     /** 
+     * 将中文的格式的数字每一位对应转换为阿拉伯数字。 是perNumToChinese(num)的逆操作。  
+     *  @example
+    1. perChineseToNum('三四'); //结果: 34  
+    2. perChineseToNum('三八七四'); //结果: 3874  
+    3. perChineseToNum('四零四'); //结果: 404  
+    4. perChineseToNum('零零四'); //结果: 4  
+    5. perChineseToNum('  '); //结果: NaN  
+     */
+    perChineseToNum(numStr){
+        return +numStr.replace(/[零一二三四五六七八九十\s]/g, (str)=>{
+            var index =  this.__CHINANUM.indexOf(str);
+            return index == null ? '' : index;
+        });
+    }
+
+    /**
+    * 将阿拉伯数字转换为对应的汉字, 最多只能处理13位数(万亿)。
+    * <br><font color="red">ref: this.numToChina()、 this.weiNum() </font> <br>
+    * @public
+    * @param {number} num - 要转换的数字
+    * @example
+        1. numToChinese(0); //结果: '零'  
+        2. numToChinese(5); //结果: '五'  
+        3. numToChinese(16); //结果: '十六'  
+        4. numToChinese(34); //结果: '三十四'  
+        5. numToChinese(106); //结果: '一百零六'  
+        6. numToChinese(886); //结果: '八百八十六'  
+        7. numToChinese(1004); //结果: '一千零四'  
+        8. numToChinese(1000); //结果: '一千'  
+        9. numToChinese(9904); //结果: '九千九百零四'  
+        10. numToChinese(19904); //结果: '一万九千九百零四'  
+        11. numToChinese(10004); //结果: '一万零四'  
+        12. numToChinese(10000); //结果: '一万'  
+        13. numToChinese(100404); //结果: '十万零四百零四'  
+        14. numToChinese(9000000); //结果: '九百万'  
+        15. numToChinese(90000000); //结果: '九千万'  
+        16. numToChinese(900000000); //结果: '九亿'  
+        17. numToChinese(9000000000); //结果: '九十亿'  
+        18. numToChinese(9020030401); //结果: '九十亿零二千零三万零四百零一'  
+        19. numToChinese(90000000000); //结果: '九百亿'  
+        20. numToChinese(900000000000); //结果: '九千亿'  
+        21. numToChinese(9000000000000); //结果: '九万亿'  
+        22. numToChinese(90000000000000); //结果: undefined  
+    */
+    numToChinese (num){
+        let numStr = '' + num;
+        let len = numStr.length, result;
+        if(num <= 10){ //处理0~9
+            return this.__CHINANUM[num]; 
+        }else if(num < 20){ //处理10~20
+            return '十' + this.__CHINANUM[numStr[1]]; 
+        }else if(len < 6){ //处理5位数(万)及以下
+            if(!this.__WEIARR){
+                this.__WEIARR = this.__WEISTR.split(', ');
+            }
+            let lastStr, resultStr = '', index;
+            for(let i=0; i<len; i++){
+                lastStr = this.__CHINANUM[numStr[i]];
+                if(numStr[i] === '0'){
+                    index = null;
+                    for(let j=i+1; j<len; j++){
+                        if( numStr[j] !== '0' ){
+                            index = j - 1;
+                            break;
+                        }
+                    }
+                    if(index === null){
+                        break;
+                    }else{
+                        i = index;
+                        resultStr += '零';
+                    }
+                }else{
+                    resultStr += ( lastStr + this.__WEIARR[len - i - 1] );
+                }
+            }
+            return resultStr;
+        }else if(len < 9){ //处理8位数(千万)及以下
+            let low4Str = numStr.slice(-4), heigh4Str = numStr.slice(0, -4), isZero = '';
+            isZero = heigh4Str.slice(-1) === '0' ? '零' : ''; //必须用这个,如504000,应该是 五十万零四千 而不是 五十万四千
+            if(isZero){
+                low4Str = +low4Str;
+                if(low4Str === 0){
+                    isZero = '';
+                }
+            }
+            if(+heigh4Str !== 0){
+                isZero = '万' + isZero;
+            }
+            result = this.numToChinese( +heigh4Str ) + isZero  + this.numToChinese( +low4Str );
+            return result.slice(-1) === '零' ? result.slice(0, -1) : result;
+
+        }else if(len < 14){ //处理13位数(万亿)及以下
+            let low8Str = numStr.slice(-8), heigh4Str = numStr.slice(0, -8),  isZero = '';
+            isZero = heigh4Str.slice(-1) === '0' ? '零' : ''; //必须用这个
+            if(isZero){
+                low8Str = +low8Str;
+                if(low8Str === 0){
+                    isZero = '';
+                }
+            }
+            if(+heigh4Str !== 0){
+                isZero = '亿' + isZero;
+            }
+            result = this.numToChinese( +heigh4Str ) + isZero  + this.numToChinese( +low8Str );
+            return result.slice(-1) === '零' ? result.slice(0, -1) : result;
+        }
+    }
+
+    /**
+     * 按数字的位数将每一位数字转换为中文(注意,可以转换多位数字)
+     * <br><font color="red">ref: this.numToChina() </font> <br>
+     * @public
+     * @param {number|string} num - 要转换的数字
+     * @example
+    1. perNumToChinese(34); //结果: '三四'
+    2. perNumToChinese(3874); //结果: '三八七四'
+    3. perNumToChinese('3874'); //结果: '三八七四'
+    4. perNumToChinese('404'); //结果: '四零四'
+    5. perNumToChinese('0  04   '); //结果: '零零四'
+    6. perNumToChinese('  '); //结果: ''
+    */
+    perNumToChinese(num){
+        return ('' + num).replace(/[\d\s]/g, (index)=>{
+            var result = this.__CHINANUM[index];
+            return result ? result : '';
+        });
+    }
+    __CHINANUM =  '零一二三四五六七八九十'; 
+    __WEISTR =', 十, 百, 千, 万, 十万, 百万, 千万, 亿, 十亿, 百亿, 千亿, 万亿, 十万亿, 百万亿, 千万亿, 亿亿, 十亿亿, 百亿亿, 千亿亿, 万亿亿';
+    __WEIARR = null;
+ 
+ }
+
+

+ 30 - 0
main.js

@@ -0,0 +1,30 @@
+
+// #ifndef VUE3
+import Vue from 'vue'
+import App from './App'
+
+Vue.config.productionTip = false
+import request from '@/util/request.js'
+import WXBizDataCrypt from '@/util/WXBizDataCrypt.js'
+Vue.prototype.$request = request
+import store from './store'
+Vue.prototype.$store = store
+
+App.mpType = 'app'
+
+const app = new Vue({
+    ...App
+})
+app.$mount()
+// #endif
+
+// #ifdef VUE3
+import { createSSRApp } from 'vue'
+import App from './App.vue'
+export function createApp() {
+  const app = createSSRApp(App)
+  return {
+    app
+  }
+}
+// #endif

+ 60 - 0
manifest.json

@@ -0,0 +1,60 @@
+{
+    "name" : "businessard",
+    "appid" : "__UNI__9AAF800",
+    "description" : "",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    "app-plus" : {
+        /* 5+App特有相关 */
+        "usingComponents" : true,
+        "nvueCompiler" : "uni-app",
+        "nvueStyleCompiler" : "uni-app",
+        "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打包配置 */
+            "sdkConfigs" : {}
+        }
+    },
+    /* SDK配置 */
+    "quickapp" : {},
+    /* 快应用特有相关 */
+    "mp-weixin" : {
+        /* 小程序特有相关 */
+        "appid" : "",
+        "setting" : {
+            "urlCheck" : false
+        },
+        "usingComponents" : true
+    },
+    "vueVersion" : "2"
+}

+ 17 - 0
pages.json

@@ -0,0 +1,17 @@
+{
+	"pages": [{
+		"path": "pages/index/index",
+		"style": {
+			"navigationBarTitleText": "uni-app"
+		}
+	}],
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "uni-app",
+		"navigationBarBackgroundColor": "#F8F8F8",
+		"backgroundColor": "#F8F8F8",
+		"app-plus": {
+			"background": "#efeff4"
+		}
+	}
+}

+ 92 - 0
pages/index/index.vue

@@ -0,0 +1,92 @@
+<template>
+	<view class="container">
+
+		<view class="intro">本项目已包含uni ui组件,无需import和注册,可直接使用。在代码区键入字母u,即可通过代码助手列出所有可用组件。光标置于组件名称处按F1,即可查看组件文档。</view>
+		<text class="intro">详见:</text>
+		<uni-link :href="href" :text="href"></uni-link>
+		<!-- <mescroll-body ref="mescrollRef" @init="mescrollInit" @up="upCallback" @down="downCallback">
+			<view class="content3" v-for="(item,index) in infoList">
+
+			</view>
+		</mescroll-body> -->
+	</view>
+</template>
+
+<script>
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	import {
+		mapState,
+		mapMutations
+	} from 'vuex';
+	export default {
+		mixins: [MescrollMixin], // 使用mixin
+		computed: {
+			...mapState(['hasLogin'])
+		},
+		data() {
+			return {
+				href: 'https://uniapp.dcloud.io/component/README?id=uniui',
+				canReset: false,
+				userInfo: {},
+				infoList: []
+			}
+		},
+		onShow() {
+			// this.$nextTick(function() {
+			// 	this.canReset && this.mescroll.resetUpScroll() // 重置列表数据为第一页  
+			// 	this.canReset && this.mescroll.scrollTo(0, 0) // 重置列表数据为第一页时,建议把滚动条也重置到顶部,避免无法再次翻页的问题  
+			// 	this.canReset = true // 过滤第一次的onShow事件,避免初始化界面时重复触发upCallback, 无需配置auto:false
+			// });
+			// if (this.hasLogin || uni.getStorageSync("userInfo").username) {
+			// 	this.userInfo = uni.getStorageSync("userInfo")
+			// 	this.isLogin = true
+			// } else {
+			// 	uni.navigateTo({
+			// 		url: "/pages/login/login"
+			// 	})
+			// }
+		},
+		methods: {
+			upCallback(page) {
+				uni.showLoading({
+					title: '数据加载中'
+				})
+				this.$request.baseRequest('pincheCarSharingApp', 'list', {
+					remark2: this.route,
+					carpoolingType: this.type,
+					companyId: 1,
+					pageNum: page.num,
+					pageSize: page.size,
+				}, failres => {
+					console.log('res+++++', failres.errmsg)
+					this.$refs.uToast.show({
+						type: 'error',
+						message: failres.errmsg,
+					})
+					uni.hideLoading()
+				}).then(res => {
+					// if (res.errno == 200) {
+					uni.hideLoading()
+					console.log(11)
+					let curPageData = res.data.items;
+					let totalPage = res.data.total;
+					let curPageLen = curPageData.length;
+					this.mescroll.endByPage(curPageLen, totalPage);
+					console.log(res.data)
+					// this.makeData(res.data)
+					if (page.num == 1) this.infoList = []; //如果是第一页需手动置空列表
+					this.infoList = this.infoList.concat(curPageData); //追加新数据
+					// }
+				})
+			},
+		}
+	}
+</script>
+
+<style>
+	.container {
+		padding: 20px;
+		font-size: 14px;
+		line-height: 24px;
+	}
+</style>

BIN
static/c1.png


BIN
static/c2.png


BIN
static/c3.png


BIN
static/c4.png


BIN
static/c5.png


BIN
static/c6.png


BIN
static/c7.png


BIN
static/c8.png


BIN
static/c9.png


+ 20 - 0
static/customicons.css

@@ -0,0 +1,20 @@
+@font-face {
+  font-family: "customicons"; /* Project id 2878519 */
+  src:url('/static/customicons.ttf') format('truetype');
+}
+
+.customicons {
+  font-family: "customicons" !important;
+}
+
+.youxi:before {
+  content: "\e60e";
+}
+
+.wenjian:before {
+  content: "\e60f";
+}
+
+.zhuanfa:before {
+  content: "\e610";
+}

BIN
static/customicons.ttf


BIN
static/logo.png


BIN
static/uni.png


+ 109 - 0
store/index.js

@@ -0,0 +1,109 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+Vue.use(Vuex)
+let lifeData = {};
+
+try {
+	// 尝试获取本地是否存在lifeData变量,第一次启动APP时是不存在的
+	lifeData = uni.getStorageSync('lifeData');
+} catch (e) {}
+
+// 需要永久存储,且下次APP启动需要取出的,在state中的变量名
+let saveStateKeys = ['userInfo', 'goods',
+	'isHaveService'
+];
+
+// 保存变量到本地存储中
+const saveLifeData = function(key, value) {
+	// 判断变量名是否在需要存储的数组中
+	if (saveStateKeys.indexOf(key) != -1) {
+		// 获取本地存储的lifeData对象,将变量添加到对象中
+		let tmp = uni.getStorageSync('lifeData');
+		// 第一次打开APP,不存在lifeData变量,故放一个{}空对象
+		tmp = tmp ? tmp : {};
+		// if(key == 'contractTip' && tmp[key] < value){
+		// 	uni.vibrateLong({
+		// 	    success: function () {
+		// 	        console.log('震动成功');
+		// 	    }
+		// 	});
+		// }
+		// if(key == 'taskTip' && tmp[key] < value){
+		// 	uni.vibrateLong({
+		// 	    success: function () {
+		// 	        console.log('震动成功');
+		// 	    }
+		// 	});
+		// }
+		tmp[key] = value;
+		// 执行这一步后,所有需要存储的变量,都挂载在本地的lifeData对象中
+		uni.setStorageSync('lifeData', tmp);
+	}
+}
+
+
+const store = new Vuex.Store({
+	modules: {},
+	state: {
+		sysinfo: uni.getSystemInfoSync(), //系统信息
+		hasLogin: false,
+		isHaveService: false,
+		goods: []
+	},
+	mutations: {
+		$uStore(state, payload) {
+			// 判断是否多层级调用,state中为对象存在的情况,诸如user.info.score = 1
+			let nameArr = payload.name.split('.');
+			let saveKey = '';
+			let len = nameArr.length;
+			if (nameArr.length >= 2) {
+				let obj = state[nameArr[0]];
+				for (let i = 1; i < len - 1; i++) {
+					obj = obj[nameArr[i]];
+				}
+				obj[nameArr[len - 1]] = payload.value;
+				saveKey = nameArr[0];
+			} else {
+				// 单层级变量,在state就是一个普通变量的情况
+				state[payload.name] = payload.value;
+				saveKey = payload.name;
+			}
+			// 保存变量到本地,见顶部函数定义
+			saveLifeData(saveKey, state[saveKey])
+		},
+		login(state, provider) {
+			state.hasLogin = true;
+			state.userInfo = provider;
+			uni.setStorageSync('userInfo', provider)
+		},
+
+		logout(state) {
+			state.hasLogin = false;
+			state.userInfo = {};
+			uni.removeStorageSync('userInfo')
+		},
+
+		goodCar(state, provider) {
+			
+			state.goods = provider;
+			uni.setStorageSync('goods', provider)
+		},
+		goodService(state, provider) {
+			state.isHaveService = provider;
+			uni.setStorageSync('isHaveService', provider)
+		},
+
+
+
+	},
+	actions: {
+
+	},
+	getters: {
+		getUserInfo(state) {
+			return state.userInfo
+		}
+	}
+})
+
+export default store

+ 1 - 0
uni.scss

@@ -0,0 +1 @@
+@import '@/uni_modules/uni-scss/variables.scss';

+ 6 - 0
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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
uni_modules/mescroll-uni/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~

+ 31 - 0
uni_modules/uni-badge/changelog.md

@@ -0,0 +1,31 @@
+## 1.2.1(2022-09-05)
+- 修复 当 text 超过 max-num 时,badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473)
+## 1.2.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-badge](https://uniapp.dcloud.io/component/uniui/uni-badge)
+## 1.1.7(2021-11-08)
+- 优化 升级ui
+- 修改 size 属性默认值调整为 small
+- 修改 type 属性,默认值调整为 error,info 替换 default
+## 1.1.6(2021-09-22)
+- 修复 在字节小程序上样式不生效的 bug
+## 1.1.5(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.1.4(2021-07-29)
+- 修复 去掉 nvue 不支持css 的 align-self 属性,nvue 下不暂支持 absolute 属性
+## 1.1.3(2021-06-24)
+- 优化 示例项目
+## 1.1.1(2021-05-12)
+- 新增 组件示例地址
+## 1.1.0(2021-05-12)
+- 新增 uni-badge 的 absolute 属性,支持定位
+- 新增 uni-badge 的 offset 属性,支持定位偏移
+- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点
+- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+
+- 优化 uni-badge 属性 custom-style, 支持以对象形式自定义样式
+## 1.0.7(2021-05-07)
+- 修复 uni-badge 在 App 端,数字小于10时不是圆形的bug
+- 修复 uni-badge 在父元素不是 flex 布局时,宽度缩小的bug
+- 新增 uni-badge 属性 custom-style, 支持自定义样式
+## 1.0.6(2021-02-04)
+- 调整为uni_modules目录规范

+ 268 - 0
uni_modules/uni-badge/components/uni-badge/uni-badge.vue

@@ -0,0 +1,268 @@
+<template>
+	<view class="uni-badge--x">
+		<slot />
+		<text v-if="text" :class="classNames" :style="[positionStyle, customStyle, dotStyle]"
+			class="uni-badge" @click="onClick()">{{displayValue}}</text>
+	</view>
+</template>
+
+<script>
+	/**
+	 * Badge 数字角标
+	 * @description 数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=21
+	 * @property {String} text 角标内容
+	 * @property {String} size = [normal|small] 角标内容
+	 * @property {String} type = [info|primary|success|warning|error] 颜色类型
+	 * 	@value info 灰色
+	 * 	@value primary 蓝色
+	 * 	@value success 绿色
+	 * 	@value warning 黄色
+	 * 	@value error 红色
+	 * @property {String} inverted = [true|false] 是否无需背景颜色
+	 * @property {Number} maxNum 展示封顶的数字值,超过 99 显示 99+
+	 * @property {String} absolute = [rightTop|rightBottom|leftBottom|leftTop] 开启绝对定位, 角标将定位到其包裹的标签的四角上		
+	 * 	@value rightTop 右上
+	 * 	@value rightBottom 右下
+	 * 	@value leftTop 左上
+	 * 	@value leftBottom 左下
+	 * @property {Array[number]} offset	距定位角中心点的偏移量,只有存在 absolute 属性时有效,例如:[-10, -10] 表示向外偏移 10px,[10, 10] 表示向 absolute 指定的内偏移 10px
+	 * @property {String} isDot = [true|false] 是否显示为一个小点
+	 * @event {Function} click 点击 Badge 触发事件
+	 * @example <uni-badge text="1"></uni-badge>
+	 */
+
+	export default {
+		name: 'UniBadge',
+		emits: ['click'],
+		props: {
+			type: {
+				type: String,
+				default: 'error'
+			},
+			inverted: {
+				type: Boolean,
+				default: false
+			},
+			isDot: {
+				type: Boolean,
+				default: false
+			},
+			maxNum: {
+				type: Number,
+				default: 99
+			},
+			absolute: {
+				type: String,
+				default: ''
+			},
+			offset: {
+				type: Array,
+				default () {
+					return [0, 0]
+				}
+			},
+			text: {
+				type: [String, Number],
+				default: ''
+			},
+			size: {
+				type: String,
+				default: 'small'
+			},
+			customStyle: {
+				type: Object,
+				default () {
+					return {}
+				}
+			}
+		},
+		data() {
+			return {};
+		},
+		computed: {
+			width() {
+				return String(this.text).length * 8 + 12
+			},
+			classNames() {
+				const {
+					inverted,
+					type,
+					size,
+					absolute
+				} = this
+				return [
+					inverted ? 'uni-badge--' + type + '-inverted' : '',
+					'uni-badge--' + type,
+					'uni-badge--' + size,
+					absolute ? 'uni-badge--absolute' : ''
+				].join(' ')
+			},
+			positionStyle() {
+				if (!this.absolute) return {}
+				let w = this.width / 2,
+					h = 10
+				if (this.isDot) {
+					w = 5
+					h = 5
+				}
+				const x = `${- w  + this.offset[0]}px`
+				const y = `${- h + this.offset[1]}px`
+
+				const whiteList = {
+					rightTop: {
+						right: x,
+						top: y
+					},
+					rightBottom: {
+						right: x,
+						bottom: y
+					},
+					leftBottom: {
+						left: x,
+						bottom: y
+					},
+					leftTop: {
+						left: x,
+						top: y
+					}
+				}
+				const match = whiteList[this.absolute]
+				return match ? match : whiteList['rightTop']
+			},
+			dotStyle() {
+				if (!this.isDot) return {}
+				return {
+					width: '10px',
+					minWidth: '0',
+					height: '10px',
+					padding: '0',
+					borderRadius: '10px'
+				}
+			},
+			displayValue() {
+				const {
+					isDot,
+					text,
+					maxNum
+				} = this
+				return isDot ? '' : (Number(text) > maxNum ? `${maxNum}+` : text)
+			}
+		},
+		methods: {
+			onClick() {
+				this.$emit('click');
+			}
+		}
+	};
+</script>
+
+<style lang="scss" >
+	$uni-primary: #2979ff !default;
+	$uni-success: #4cd964 !default;
+	$uni-warning: #f0ad4e !default;
+	$uni-error: #dd524d !default;
+	$uni-info: #909399 !default;
+
+
+	$bage-size: 12px;
+	$bage-small: scale(0.8);
+
+	.uni-badge--x {
+		/* #ifdef APP-NVUE */
+		// align-self: flex-start;
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		display: inline-block;
+		/* #endif */
+		position: relative;
+	}
+
+	.uni-badge--absolute {
+		position: absolute;
+	}
+
+	.uni-badge--small {
+		transform: $bage-small;
+		transform-origin: center center;
+	}
+
+	.uni-badge {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		overflow: hidden;
+		box-sizing: border-box;
+		/* #endif */
+		justify-content: center;
+		flex-direction: row;
+		height: 20px;
+		min-width: 20px;
+		padding: 0 4px;
+		line-height: 18px;
+		color: #fff;
+		border-radius: 100px;
+		background-color: $uni-info;
+		background-color: transparent;
+		border: 1px solid #fff;
+		text-align: center;
+		font-family: 'Helvetica Neue', Helvetica, sans-serif;
+		font-feature-settings: "tnum";
+		font-size: $bage-size;
+		/* #ifdef H5 */
+		z-index: 999;
+		cursor: pointer;
+		/* #endif */
+
+		&--info {
+			color: #fff;
+			background-color: $uni-info;
+		}
+
+		&--primary {
+			background-color: $uni-primary;
+		}
+
+		&--success {
+			background-color: $uni-success;
+		}
+
+		&--warning {
+			background-color: $uni-warning;
+		}
+
+		&--error {
+			background-color: $uni-error;
+		}
+
+		&--inverted {
+			padding: 0 5px 0 0;
+			color: $uni-info;
+		}
+
+		&--info-inverted {
+			color: $uni-info;
+			background-color: transparent;
+		}
+
+		&--primary-inverted {
+			color: $uni-primary;
+			background-color: transparent;
+		}
+
+		&--success-inverted {
+			color: $uni-success;
+			background-color: transparent;
+		}
+
+		&--warning-inverted {
+			color: $uni-warning;
+			background-color: transparent;
+		}
+
+		&--error-inverted {
+			color: $uni-error;
+			background-color: transparent;
+		}
+
+	}
+</style>

+ 85 - 0
uni_modules/uni-badge/package.json

@@ -0,0 +1,85 @@
+{
+  "id": "uni-badge",
+  "displayName": "uni-badge 数字角标",
+  "version": "1.2.1",
+  "description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。",
+  "keywords": [
+    "",
+    "badge",
+    "uni-ui",
+    "uniui",
+    "数字角标",
+    "徽章"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
+    "type": "component-vue"
+  },
+  "uni_modules": {
+    "dependencies": ["uni-scss"],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "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"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 10 - 0
uni_modules/uni-badge/readme.md

@@ -0,0 +1,10 @@
+## Badge 数字角标
+> **组件名:uni-badge**
+> 代码块: `uBadge`
+
+数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景,
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-badge)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 
+
+

+ 6 - 0
uni_modules/uni-breadcrumb/changelog.md

@@ -0,0 +1,6 @@
+## 0.1.2(2022-06-08)
+- 修复 微信小程序 separator 不显示问题
+## 0.1.1(2022-06-02)
+- 新增 支持 uni.scss 修改颜色
+## 0.1.0(2022-04-21)
+- 初始化

+ 121 - 0
uni_modules/uni-breadcrumb/components/uni-breadcrumb-item/uni-breadcrumb-item.vue

@@ -0,0 +1,121 @@
+<template>
+	<view class="uni-breadcrumb-item">
+		<view :class="{
+			'uni-breadcrumb-item--slot': true,
+			'uni-breadcrumb-item--slot-link': to && currentPage !== to
+			}" @click="navTo">
+			<slot />
+		</view>
+		<i v-if="separatorClass" class="uni-breadcrumb-item--separator" :class="separatorClass" />
+		<text v-else class="uni-breadcrumb-item--separator">{{ separator }}</text>
+	</view>
+</template>
+<script>
+	/**
+	 * BreadcrumbItem 面包屑导航子组件
+	 * @property {String/Object} to 路由跳转页面路径/对象
+	 * @property {Boolean} replace 在使用 to 进行路由跳转时,启用 replace 将不会向 history 添加新记录(仅 h5 支持)
+	 */
+	export default {
+		data() {
+			return {
+				currentPage: ""
+			}
+		},
+		options: {
+			virtualHost: true
+		},
+		props: {
+			to: {
+				type: String,
+				default: ''
+			},
+			replace:{
+				type: Boolean,
+				default: false
+			}
+		},
+		inject: {
+			uniBreadcrumb: {
+				from: "uniBreadcrumb",
+				default: null
+			}
+		},
+		created(){
+			const pages = getCurrentPages()
+			const page = pages[pages.length-1]
+
+			if(page){
+				this.currentPage = `/${page.route}`
+			}
+		},
+		computed: {
+			separator() {
+				return this.uniBreadcrumb.separator
+			},
+			separatorClass() {
+				return this.uniBreadcrumb.separatorClass
+			}
+		},
+		methods: {
+			navTo() {
+				const { to } = this
+
+				if (!to || this.currentPage === to){
+					return
+				}
+
+				if(this.replace){
+					uni.redirectTo({
+						url:to
+					})
+				}else{
+					uni.navigateTo({
+						url:to
+					})
+				}
+			}
+		}
+	}
+</script>
+<style lang="scss">
+	$uni-primary: #2979ff !default;
+	$uni-base-color: #6a6a6a !default;
+	$uni-main-color: #3a3a3a !default;
+	.uni-breadcrumb-item {
+		display: flex;
+		align-items: center;
+		white-space: nowrap;
+		font-size: 14px;
+
+		&--slot {
+			color: $uni-base-color;
+			padding: 0 10px;
+
+			&-link {
+				color: $uni-main-color;
+				font-weight: bold;
+				/* #ifndef APP-NVUE */
+				cursor: pointer;
+				/* #endif */
+
+				&:hover {
+					color: $uni-primary;
+				}
+			}
+		}
+
+		&--separator {
+			font-size: 12px;
+			color: $uni-base-color;
+		}
+
+		&:first-child &--slot {
+			padding-left: 0;
+		}
+		
+		&:last-child &--separator {
+			display: none;
+		}
+	}
+</style>

+ 41 - 0
uni_modules/uni-breadcrumb/components/uni-breadcrumb/uni-breadcrumb.vue

@@ -0,0 +1,41 @@
+<template>
+	<view class="uni-breadcrumb">
+		<slot />
+	</view>
+</template>
+<script>
+	/**
+	 * Breadcrumb 面包屑导航父组件
+	 * @description 显示当前页面的路径,快速返回之前的任意页面
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
+	 * @property {String} separator 分隔符,默认为斜杠'/'
+	 * @property {String} separatorClass 图标分隔符 class
+	 */
+	export default {
+		options: {
+			virtualHost: true
+		},
+		props: {
+			separator: {
+				type: String,
+				default: '/'
+			},
+			separatorClass: {
+				type: String,
+				default: ''
+			}
+		},
+
+		provide() {
+			return {
+				uniBreadcrumb: this
+			}
+		}
+
+	}
+</script>
+<style lang="scss">
+	.uni-breadcrumb {
+		display: flex;
+	}
+</style>

+ 88 - 0
uni_modules/uni-breadcrumb/package.json

@@ -0,0 +1,88 @@
+{
+  "id": "uni-breadcrumb",
+  "displayName": "uni-breadcrumb 面包屑",
+  "version": "0.1.2",
+  "description": "Breadcrumb  面包屑",
+  "keywords": [
+    "uni-breadcrumb",
+    "breadcrumb",
+    "uni-ui",
+    "面包屑导航",
+    "面包屑"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+  "dcloudext": {
+    "category": [
+        "前端组件",
+        "通用组件"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "Vue": {
+          "vue2": "y",
+          "vue3": "y"
+        },
+        "App": {
+          "app-vue": "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",
+          "阿里": "u",
+          "百度": "u",
+          "字节跳动": "u",
+        "QQ": "u",
+        "京东": "u"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        }
+      }
+    }
+  }
+}

+ 66 - 0
uni_modules/uni-breadcrumb/readme.md

@@ -0,0 +1,66 @@
+
+## breadcrumb 面包屑导航
+> **组件名:uni-breadcrumb**
+> 代码块: `ubreadcrumb`
+
+显示当前页面的路径,快速返回之前的任意页面。
+
+### 安装方式
+
+本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
+
+如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
+
+### 基本用法
+
+在 ``template`` 中使用组件
+
+```html
+<uni-breadcrumb separator="/">
+	<uni-breadcrumb-item v-for="(route,index) in routes" :key="index" :to="route.to">{{route.name}}</uni-breadcrumb-item>
+</uni-breadcrumb>
+```
+
+```js
+export default {
+		name: "uni-stat-breadcrumb",
+		data() {
+			return {
+				routes: [{
+					to: '/A',
+					name: 'A页面'
+				}, {
+					to: '/B',
+					name: 'B页面'
+				}, {
+					to: '/C',
+					name: 'C页面'
+				}]
+			};
+		}
+	}
+```
+
+
+## API
+
+### Breadcrumb Props
+
+|属性名			|类型	|默认值	|说明				|
+|:-:			|:-:	|:-:	|:-:				|
+|separator		|String	|斜杠'/' |分隔符				|
+|separatorClass	|String	|		|图标分隔符 class	    |
+
+### Breadcrumb Item Props
+
+|属性名	|类型			|默认值	|说明																			|
+|:-:	|:-:			|:-:	|:-:																			|
+|to		|String     	|		|路由跳转页面路径           														|
+|replace|Boolean		|		|在使用 to 进行路由跳转时,启用 replace 将不会向 history 添加新记录(仅 h5 支持)         |
+
+
+
+
+## 组件示例
+
+点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/breadcrumb/breadcrumb](https://hellouniapp.dcloud.net.cn/pages/extUI/breadcrumb/breadcrumb)

+ 20 - 0
uni_modules/uni-calendar/changelog.md

@@ -0,0 +1,20 @@
+## 1.4.7(2022-09-16)
+- 可以使用 uni-scss 控制主题色
+## 1.4.6(2022-09-08)
+- fix: 表头年月切换,导致改变当前日期为选择月1号,且未触发change事件
+## 1.4.5(2022-02-25)
+- 修复 条件编译 nvue 不支持的 css 样式
+## 1.4.4(2022-02-25)
+- 修复 条件编译 nvue 不支持的 css 样式
+## 1.4.3(2021-09-22)
+- 修复 startDate、 endDate 属性失效的 bug
+## 1.4.2(2021-08-24)
+- 新增 支持国际化
+## 1.4.1(2021-08-05)
+- 修复 弹出层被 tabbar 遮盖 bug
+## 1.4.0(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.3.16(2021-05-12)
+- 新增 组件示例地址
+## 1.3.15(2021-02-04)
+- 调整为uni_modules目录规范 

+ 546 - 0
uni_modules/uni-calendar/components/uni-calendar/calendar.js

@@ -0,0 +1,546 @@
+/**
+* @1900-2100区间内的公历、农历互转
+* @charset UTF-8
+* @github  https://github.com/jjonline/calendar.js
+* @Author  Jea杨(JJonline@JJonline.Cn)
+* @Time    2014-7-21
+* @Time    2016-8-13 Fixed 2033hex、Attribution Annals
+* @Time    2016-9-25 Fixed lunar LeapMonth Param Bug
+* @Time    2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
+* @Version 1.0.3
+* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
+* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
+*/
+/* eslint-disable */
+var calendar = {
+
+  /**
+      * 农历1900-2100的润大小信息表
+      * @Array Of Property
+      * @return Hex
+      */
+  lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
+    0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
+    0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
+    0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
+    0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
+    0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
+    0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
+    0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
+    0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
+    0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
+    0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
+    0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
+    0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
+    0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
+    0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
+    /** Add By JJonline@JJonline.Cn**/
+    0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
+    0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
+    0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
+    0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
+    0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
+    0x0d520], // 2100
+
+  /**
+      * 公历每个月份的天数普通表
+      * @Array Of Property
+      * @return Number
+      */
+  solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
+
+  /**
+      * 天干地支之天干速查表
+      * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
+      * @return Cn string
+      */
+  Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
+
+  /**
+      * 天干地支之地支速查表
+      * @Array Of Property
+      * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
+      * @return Cn string
+      */
+  Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
+
+  /**
+      * 天干地支之地支速查表<=>生肖
+      * @Array Of Property
+      * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
+      * @return Cn string
+      */
+  Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
+
+  /**
+      * 24节气速查表
+      * @Array Of Property
+      * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
+      * @return Cn string
+      */
+  solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
+
+  /**
+      * 1900-2100各年的24节气日期速查表
+      * @Array Of Property
+      * @return 0x string For splice
+      */
+  sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
+    '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
+    'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
+    '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
+    '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
+    '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
+    '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
+    '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
+    '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
+    '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
+    '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+    '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+    '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
+    '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+    '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
+    '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+    '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+    '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
+    '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+    '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+    '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+    '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+    '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+    '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+    '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+    '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+    '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+    '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
+    '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
+    '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+    '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
+    '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+    '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+    '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
+    '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+    '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+    '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
+    '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
+    '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
+    '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+    '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+    '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
+    '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+    '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
+    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
+    '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
+    '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+    '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
+    '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
+    '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
+    '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+    '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+    '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
+    '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
+
+  /**
+      * 数字转中文速查表
+      * @Array Of Property
+      * @trans ['日','一','二','三','四','五','六','七','八','九','十']
+      * @return Cn string
+      */
+  nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
+
+  /**
+      * 日期转农历称呼速查表
+      * @Array Of Property
+      * @trans ['初','十','廿','卅']
+      * @return Cn string
+      */
+  nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
+
+  /**
+      * 月份转农历称呼速查表
+      * @Array Of Property
+      * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
+      * @return Cn string
+      */
+  nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
+
+  /**
+      * 返回农历y年一整年的总天数
+      * @param lunar Year
+      * @return Number
+      * @eg:var count = calendar.lYearDays(1987) ;//count=387
+      */
+  lYearDays: function (y) {
+    var i; var sum = 348
+    for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
+    return (sum + this.leapDays(y))
+  },
+
+  /**
+      * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
+      * @param lunar Year
+      * @return Number (0-12)
+      * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
+      */
+  leapMonth: function (y) { // 闰字编码 \u95f0
+    return (this.lunarInfo[y - 1900] & 0xf)
+  },
+
+  /**
+      * 返回农历y年闰月的天数 若该年没有闰月则返回0
+      * @param lunar Year
+      * @return Number (0、29、30)
+      * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
+      */
+  leapDays: function (y) {
+    if (this.leapMonth(y)) {
+      return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
+    }
+    return (0)
+  },
+
+  /**
+      * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
+      * @param lunar Year
+      * @return Number (-1、29、30)
+      * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
+      */
+  monthDays: function (y, m) {
+    if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1
+    return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
+  },
+
+  /**
+      * 返回公历(!)y年m月的天数
+      * @param solar Year
+      * @return Number (-1、28、29、30、31)
+      * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
+      */
+  solarDays: function (y, m) {
+    if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
+    var ms = m - 1
+    if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
+      return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
+    } else {
+      return (this.solarMonth[ms])
+    }
+  },
+
+  /**
+     * 农历年份转换为干支纪年
+     * @param  lYear 农历年的年份数
+     * @return Cn string
+     */
+  toGanZhiYear: function (lYear) {
+    var ganKey = (lYear - 3) % 10
+    var zhiKey = (lYear - 3) % 12
+    if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
+    if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
+    return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
+  },
+
+  /**
+     * 公历月、日判断所属星座
+     * @param  cMonth [description]
+     * @param  cDay [description]
+     * @return Cn string
+     */
+  toAstro: function (cMonth, cDay) {
+    var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
+    var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
+    return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
+  },
+
+  /**
+      * 传入offset偏移量返回干支
+      * @param offset 相对甲子的偏移量
+      * @return Cn string
+      */
+  toGanZhi: function (offset) {
+    return this.Gan[offset % 10] + this.Zhi[offset % 12]
+  },
+
+  /**
+      * 传入公历(!)y年获得该年第n个节气的公历日期
+      * @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
+      * @return day Number
+      * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
+      */
+  getTerm: function (y, n) {
+    if (y < 1900 || y > 2100) { return -1 }
+    if (n < 1 || n > 24) { return -1 }
+    var _table = this.sTermInfo[y - 1900]
+    var _info = [
+      parseInt('0x' + _table.substr(0, 5)).toString(),
+      parseInt('0x' + _table.substr(5, 5)).toString(),
+      parseInt('0x' + _table.substr(10, 5)).toString(),
+      parseInt('0x' + _table.substr(15, 5)).toString(),
+      parseInt('0x' + _table.substr(20, 5)).toString(),
+      parseInt('0x' + _table.substr(25, 5)).toString()
+    ]
+    var _calday = [
+      _info[0].substr(0, 1),
+      _info[0].substr(1, 2),
+      _info[0].substr(3, 1),
+      _info[0].substr(4, 2),
+
+      _info[1].substr(0, 1),
+      _info[1].substr(1, 2),
+      _info[1].substr(3, 1),
+      _info[1].substr(4, 2),
+
+      _info[2].substr(0, 1),
+      _info[2].substr(1, 2),
+      _info[2].substr(3, 1),
+      _info[2].substr(4, 2),
+
+      _info[3].substr(0, 1),
+      _info[3].substr(1, 2),
+      _info[3].substr(3, 1),
+      _info[3].substr(4, 2),
+
+      _info[4].substr(0, 1),
+      _info[4].substr(1, 2),
+      _info[4].substr(3, 1),
+      _info[4].substr(4, 2),
+
+      _info[5].substr(0, 1),
+      _info[5].substr(1, 2),
+      _info[5].substr(3, 1),
+      _info[5].substr(4, 2)
+    ]
+    return parseInt(_calday[n - 1])
+  },
+
+  /**
+      * 传入农历数字月份返回汉语通俗表示法
+      * @param lunar month
+      * @return Cn string
+      * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
+      */
+  toChinaMonth: function (m) { // 月 => \u6708
+    if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
+    var s = this.nStr3[m - 1]
+    s += '\u6708'// 加上月字
+    return s
+  },
+
+  /**
+      * 传入农历日期数字返回汉字表示法
+      * @param lunar day
+      * @return Cn string
+      * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
+      */
+  toChinaDay: function (d) { // 日 => \u65e5
+    var s
+    switch (d) {
+      case 10:
+        s = '\u521d\u5341'; break
+      case 20:
+        s = '\u4e8c\u5341'; break
+        break
+      case 30:
+        s = '\u4e09\u5341'; break
+        break
+      default :
+        s = this.nStr2[Math.floor(d / 10)]
+        s += this.nStr1[d % 10]
+    }
+    return (s)
+  },
+
+  /**
+      * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
+      * @param y year
+      * @return Cn string
+      * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
+      */
+  getAnimal: function (y) {
+    return this.Animals[(y - 4) % 12]
+  },
+
+  /**
+      * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
+      * @param y  solar year
+      * @param m  solar month
+      * @param d  solar day
+      * @return JSON object
+      * @eg:console.log(calendar.solar2lunar(1987,11,01));
+      */
+  solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
+    // 年份限定、上限
+    if (y < 1900 || y > 2100) {
+      return -1// undefined转换为数字变为NaN
+    }
+    // 公历传参最下限
+    if (y == 1900 && m == 1 && d < 31) {
+      return -1
+    }
+    // 未传参  获得当天
+    if (!y) {
+      var objDate = new Date()
+    } else {
+      var objDate = new Date(y, parseInt(m) - 1, d)
+    }
+    var i; var leap = 0; var temp = 0
+    // 修正ymd参数
+    var y = objDate.getFullYear()
+    var m = objDate.getMonth() + 1
+    var d = objDate.getDate()
+    var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
+    for (i = 1900; i < 2101 && offset > 0; i++) {
+      temp = this.lYearDays(i)
+      offset -= temp
+    }
+    if (offset < 0) {
+      offset += temp; i--
+    }
+
+    // 是否今天
+    var isTodayObj = new Date()
+    var isToday = false
+    if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
+      isToday = true
+    }
+    // 星期几
+    var nWeek = objDate.getDay()
+    var cWeek = this.nStr1[nWeek]
+    // 数字表示周几顺应天朝周一开始的惯例
+    if (nWeek == 0) {
+      nWeek = 7
+    }
+    // 农历年
+    var year = i
+    var leap = this.leapMonth(i) // 闰哪个月
+    var isLeap = false
+
+    // 效验闰月
+    for (i = 1; i < 13 && offset > 0; i++) {
+      // 闰月
+      if (leap > 0 && i == (leap + 1) && isLeap == false) {
+        --i
+        isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
+      } else {
+        temp = this.monthDays(year, i)// 计算农历普通月天数
+      }
+      // 解除闰月
+      if (isLeap == true && i == (leap + 1)) { isLeap = false }
+      offset -= temp
+    }
+    // 闰月导致数组下标重叠取反
+    if (offset == 0 && leap > 0 && i == leap + 1) {
+      if (isLeap) {
+        isLeap = false
+      } else {
+        isLeap = true; --i
+      }
+    }
+    if (offset < 0) {
+      offset += temp; --i
+    }
+    // 农历月
+    var month = i
+    // 农历日
+    var day = offset + 1
+    // 天干地支处理
+    var sm = m - 1
+    var gzY = this.toGanZhiYear(year)
+
+    // 当月的两个节气
+    // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
+    var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
+    var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
+
+    // 依据12节气修正干支月
+    var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
+    if (d >= firstNode) {
+      gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
+    }
+
+    // 传入的日期的节气与否
+    var isTerm = false
+    var Term = null
+    if (firstNode == d) {
+      isTerm = true
+      Term = this.solarTerm[m * 2 - 2]
+    }
+    if (secondNode == d) {
+      isTerm = true
+      Term = this.solarTerm[m * 2 - 1]
+    }
+    // 日柱 当月一日与 1900/1/1 相差天数
+    var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
+    var gzD = this.toGanZhi(dayCyclical + d - 1)
+    // 该日期所属的星座
+    var astro = this.toAstro(m, d)
+
+    return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
+  },
+
+  /**
+      * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
+      * @param y  lunar year
+      * @param m  lunar month
+      * @param d  lunar day
+      * @param isLeapMonth  lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
+      * @return JSON object
+      * @eg:console.log(calendar.lunar2solar(1987,9,10));
+      */
+  lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
+    var isLeapMonth = !!isLeapMonth
+    var leapOffset = 0
+    var leapMonth = this.leapMonth(y)
+    var leapDay = this.leapDays(y)
+    if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
+    if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
+    var day = this.monthDays(y, m)
+    var _day = day
+    // bugFix 2016-9-25
+    // if month is leap, _day use leapDays method
+    if (isLeapMonth) {
+      _day = this.leapDays(y, m)
+    }
+    if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
+
+    // 计算农历的时间差
+    var offset = 0
+    for (var i = 1900; i < y; i++) {
+      offset += this.lYearDays(i)
+    }
+    var leap = 0; var isAdd = false
+    for (var i = 1; i < m; i++) {
+      leap = this.leapMonth(y)
+      if (!isAdd) { // 处理闰月
+        if (leap <= i && leap > 0) {
+          offset += this.leapDays(y); isAdd = true
+        }
+      }
+      offset += this.monthDays(y, i)
+    }
+    // 转换闰月农历 需补充该年闰月的前一个月的时差
+    if (isLeapMonth) { offset += day }
+    // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
+    var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
+    var calObj = new Date((offset + d - 31) * 86400000 + stmap)
+    var cY = calObj.getUTCFullYear()
+    var cM = calObj.getUTCMonth() + 1
+    var cD = calObj.getUTCDate()
+
+    return this.solar2lunar(cY, cM, cD)
+  }
+}
+
+export default calendar

+ 12 - 0
uni_modules/uni-calendar/components/uni-calendar/i18n/en.json

@@ -0,0 +1,12 @@
+{
+	"uni-calender.ok": "ok",
+	"uni-calender.cancel": "cancel",
+	"uni-calender.today": "today",
+	"uni-calender.MON": "MON",
+	"uni-calender.TUE": "TUE",
+	"uni-calender.WED": "WED",
+	"uni-calender.THU": "THU",
+	"uni-calender.FRI": "FRI",
+	"uni-calender.SAT": "SAT",
+	"uni-calender.SUN": "SUN"
+}

+ 8 - 0
uni_modules/uni-calendar/components/uni-calendar/i18n/index.js

@@ -0,0 +1,8 @@
+import en from './en.json'
+import zhHans from './zh-Hans.json'
+import zhHant from './zh-Hant.json'
+export default {
+	en,
+	'zh-Hans': zhHans,
+	'zh-Hant': zhHant
+}

+ 12 - 0
uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hans.json

@@ -0,0 +1,12 @@
+{
+	"uni-calender.ok": "确定",
+	"uni-calender.cancel": "取消",
+	"uni-calender.today": "今日",
+	"uni-calender.SUN": "日",
+	"uni-calender.MON": "一",
+	"uni-calender.TUE": "二",
+	"uni-calender.WED": "三",
+	"uni-calender.THU": "四",
+	"uni-calender.FRI": "五",
+	"uni-calender.SAT": "六"
+}

+ 12 - 0
uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hant.json

@@ -0,0 +1,12 @@
+{
+	"uni-calender.ok": "確定",
+	"uni-calender.cancel": "取消",
+	"uni-calender.today": "今日",
+	"uni-calender.SUN": "日",
+	"uni-calender.MON": "一",
+	"uni-calender.TUE": "二",
+	"uni-calender.WED": "三",
+	"uni-calender.THU": "四",
+	"uni-calender.FRI": "五",
+	"uni-calender.SAT": "六"
+}

+ 188 - 0
uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue

@@ -0,0 +1,188 @@
+<template>
+	<view class="uni-calendar-item__weeks-box" :class="{
+		'uni-calendar-item--disable':weeks.disable,
+		'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+		'uni-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay) ,
+		'uni-calendar-item--before-checked':weeks.beforeMultiple,
+		'uni-calendar-item--multiple': weeks.multiple,
+		'uni-calendar-item--after-checked':weeks.afterMultiple,
+		}"
+	 @click="choiceDate(weeks)">
+		<view class="uni-calendar-item__weeks-box-item">
+			<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
+			<text class="uni-calendar-item__weeks-box-text" :class="{
+				'uni-calendar-item--isDay-text': weeks.isDay,
+				'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+				'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
+				'uni-calendar-item--before-checked':weeks.beforeMultiple,
+				'uni-calendar-item--multiple': weeks.multiple,
+				'uni-calendar-item--after-checked':weeks.afterMultiple,
+				'uni-calendar-item--disable':weeks.disable,
+				}">{{weeks.date}}</text>
+			<text v-if="!lunar&&!weeks.extraInfo && weeks.isDay" class="uni-calendar-item__weeks-lunar-text" :class="{
+				'uni-calendar-item--isDay-text':weeks.isDay,
+				'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+				'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
+				'uni-calendar-item--before-checked':weeks.beforeMultiple,
+				'uni-calendar-item--multiple': weeks.multiple,
+				'uni-calendar-item--after-checked':weeks.afterMultiple,
+				}">{{todayText}}</text>
+			<text v-if="lunar&&!weeks.extraInfo" class="uni-calendar-item__weeks-lunar-text" :class="{
+				'uni-calendar-item--isDay-text':weeks.isDay,
+				'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+				'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
+				'uni-calendar-item--before-checked':weeks.beforeMultiple,
+				'uni-calendar-item--multiple': weeks.multiple,
+				'uni-calendar-item--after-checked':weeks.afterMultiple,
+				'uni-calendar-item--disable':weeks.disable,
+				}">{{weeks.isDay ? todayText : (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>
+			<text v-if="weeks.extraInfo&&weeks.extraInfo.info" class="uni-calendar-item__weeks-lunar-text" :class="{
+				'uni-calendar-item--extra':weeks.extraInfo.info,
+				'uni-calendar-item--isDay-text':weeks.isDay,
+				'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
+				'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
+				'uni-calendar-item--before-checked':weeks.beforeMultiple,
+				'uni-calendar-item--multiple': weeks.multiple,
+				'uni-calendar-item--after-checked':weeks.afterMultiple,
+				'uni-calendar-item--disable':weeks.disable,
+				}">{{weeks.extraInfo.info}}</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+	initVueI18n
+	} from '@dcloudio/uni-i18n'
+	import messages from './i18n/index.js'
+	const {	t	} = initVueI18n(messages)
+	export default {
+		emits:['change'],
+		props: {
+			weeks: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			calendar: {
+				type: Object,
+				default: () => {
+					return {}
+				}
+			},
+			selected: {
+				type: Array,
+				default: () => {
+					return []
+				}
+			},
+			lunar: {
+				type: Boolean,
+				default: false
+			}
+		},
+		computed: {
+			todayText() {
+				return t("uni-calender.today")
+			},
+		},
+		methods: {
+			choiceDate(weeks) {
+				this.$emit('change', weeks)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	$uni-font-size-base:14px;
+	$uni-text-color:#333;
+	$uni-font-size-sm:12px;
+	$uni-color-error: #e43d33;
+	$uni-opacity-disabled: 0.3;
+	$uni-text-color-disable:#c0c0c0;
+	$uni-primary: #2979ff !default;
+	.uni-calendar-item__weeks-box {
+		flex: 1;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.uni-calendar-item__weeks-box-text {
+		font-size: $uni-font-size-base;
+		color: $uni-text-color;
+	}
+
+	.uni-calendar-item__weeks-lunar-text {
+		font-size: $uni-font-size-sm;
+		color: $uni-text-color;
+	}
+
+	.uni-calendar-item__weeks-box-item {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		width: 100rpx;
+		height: 100rpx;
+	}
+
+	.uni-calendar-item__weeks-box-circle {
+		position: absolute;
+		top: 5px;
+		right: 5px;
+		width: 8px;
+		height: 8px;
+		border-radius: 8px;
+		background-color: $uni-color-error;
+
+	}
+
+	.uni-calendar-item--disable {
+		background-color: rgba(249, 249, 249, $uni-opacity-disabled);
+		color: $uni-text-color-disable;
+	}
+
+	.uni-calendar-item--isDay-text {
+		color: $uni-primary;
+	}
+
+	.uni-calendar-item--isDay {
+		background-color: $uni-primary;
+		opacity: 0.8;
+		color: #fff;
+	}
+
+	.uni-calendar-item--extra {
+		color: $uni-color-error;
+		opacity: 0.8;
+	}
+
+	.uni-calendar-item--checked {
+		background-color: $uni-primary;
+		color: #fff;
+		opacity: 0.8;
+	}
+
+	.uni-calendar-item--multiple {
+		background-color: $uni-primary;
+		color: #fff;
+		opacity: 0.8;
+	}
+	.uni-calendar-item--before-checked {
+		background-color: #ff5a5f;
+		color: #fff;
+	}
+	.uni-calendar-item--after-checked {
+		background-color: #ff5a5f;
+		color: #fff;
+	}
+</style>

+ 562 - 0
uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue

@@ -0,0 +1,562 @@
+<template>
+	<view class="uni-calendar">
+		<view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}" @click="clean"></view>
+		<view v-if="insert || show" class="uni-calendar__content" :class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow}">
+			<view v-if="!insert" class="uni-calendar__header uni-calendar--fixed-top">
+				<view class="uni-calendar__header-btn-box" @click="close">
+					<text class="uni-calendar__header-text uni-calendar--fixed-width">{{cancelText}}</text>
+				</view>
+				<view class="uni-calendar__header-btn-box" @click="confirm">
+					<text class="uni-calendar__header-text uni-calendar--fixed-width">{{okText}}</text>
+				</view>
+			</view>
+			<view class="uni-calendar__header">
+				<view class="uni-calendar__header-btn-box" @click.stop="pre">
+					<view class="uni-calendar__header-btn uni-calendar--left"></view>
+				</view>
+				<picker mode="date" :value="date" fields="month" @change="bindDateChange">
+					<text class="uni-calendar__header-text">{{ (nowDate.year||'') +' / '+( nowDate.month||'')}}</text>
+				</picker>
+				<view class="uni-calendar__header-btn-box" @click.stop="next">
+					<view class="uni-calendar__header-btn uni-calendar--right"></view>
+				</view>
+				<text class="uni-calendar__backtoday" @click="backtoday">{{todayText}}</text>
+
+			</view>
+			<view class="uni-calendar__box">
+				<view v-if="showMonth" class="uni-calendar__box-bg">
+					<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
+				</view>
+				<view class="uni-calendar__weeks">
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{SUNText}}</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{monText}}</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{TUEText}}</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{WEDText}}</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{THUText}}</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{FRIText}}</text>
+					</view>
+					<view class="uni-calendar__weeks-day">
+						<text class="uni-calendar__weeks-day-text">{{SATText}}</text>
+					</view>
+				</view>
+				<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
+					<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
+						<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" :selected="selected" :lunar="lunar" @change="choiceDate"></calendar-item>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import Calendar from './util.js';
+	import calendarItem from './uni-calendar-item.vue'
+	import {
+	initVueI18n
+	} from '@dcloudio/uni-i18n'
+	import messages from './i18n/index.js'
+	const {	t	} = initVueI18n(messages)
+	/**
+	 * Calendar 日历
+	 * @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=56
+	 * @property {String} date 自定义当前时间,默认为今天
+	 * @property {Boolean} lunar 显示农历
+	 * @property {String} startDate 日期选择范围-开始日期
+	 * @property {String} endDate 日期选择范围-结束日期
+	 * @property {Boolean} range 范围选择
+	 * @property {Boolean} insert = [true|false] 插入模式,默认为false
+	 * 	@value true 弹窗模式
+	 * 	@value false 插入模式
+	 * @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
+	 * @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
+	 * @property {Boolean} showMonth 是否选择月份为背景
+	 * @event {Function} change 日期改变,`insert :ture` 时生效
+	 * @event {Function} confirm 确认选择`insert :false` 时生效
+	 * @event {Function} monthSwitch 切换月份时触发
+	 * @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
+	 */
+	export default {
+		components: {
+			calendarItem
+		},
+		emits:['close','confirm','change','monthSwitch'],
+		props: {
+			date: {
+				type: String,
+				default: ''
+			},
+			selected: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			lunar: {
+				type: Boolean,
+				default: false
+			},
+			startDate: {
+				type: String,
+				default: ''
+			},
+			endDate: {
+				type: String,
+				default: ''
+			},
+			range: {
+				type: Boolean,
+				default: false
+			},
+			insert: {
+				type: Boolean,
+				default: true
+			},
+			showMonth: {
+				type: Boolean,
+				default: true
+			},
+			clearDate: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			return {
+				show: false,
+				weeks: [],
+				calendar: {},
+				nowDate: '',
+				aniMaskShow: false
+			}
+		},
+		computed:{
+			/**
+			 * for i18n
+			 */
+
+			okText() {
+				return t("uni-calender.ok")
+			},
+			cancelText() {
+				return t("uni-calender.cancel")
+			},
+			todayText() {
+				return t("uni-calender.today")
+			},
+			monText() {
+				return t("uni-calender.MON")
+			},
+			TUEText() {
+				return t("uni-calender.TUE")
+			},
+			WEDText() {
+				return t("uni-calender.WED")
+			},
+			THUText() {
+				return t("uni-calender.THU")
+			},
+			FRIText() {
+				return t("uni-calender.FRI")
+			},
+			SATText() {
+				return t("uni-calender.SAT")
+			},
+			SUNText() {
+				return t("uni-calender.SUN")
+			},
+		},
+		watch: {
+			date(newVal) {
+				// this.cale.setDate(newVal)
+				this.init(newVal)
+			},
+			startDate(val){
+				this.cale.resetSatrtDate(val)
+				this.cale.setDate(this.nowDate.fullDate)
+				this.weeks = this.cale.weeks
+			},
+			endDate(val){
+				this.cale.resetEndDate(val)
+				this.cale.setDate(this.nowDate.fullDate)
+				this.weeks = this.cale.weeks
+			},
+			selected(newVal) {
+				this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
+				this.weeks = this.cale.weeks
+			}
+		},
+		created() {
+			// 获取日历方法实例
+			this.cale = new Calendar({
+				// date: new Date(),
+				selected: this.selected,
+				startDate: this.startDate,
+				endDate: this.endDate,
+				range: this.range,
+			})
+			// 选中某一天
+			// this.cale.setDate(this.date)
+			this.init(this.date)
+			// this.setDay
+		},
+		methods: {
+			// 取消穿透
+			clean() {},
+			bindDateChange(e) {
+				const value = e.detail.value + '-1'
+				console.log(this.cale.getDate(value));
+				this.setDate(value)
+			},
+			/**
+			 * 初始化日期显示
+			 * @param {Object} date
+			 */
+			init(date) {
+				this.cale.setDate(date)
+				this.weeks = this.cale.weeks
+				this.nowDate = this.calendar = this.cale.getInfo(date)
+			},
+			/**
+			 * 打开日历弹窗
+			 */
+			open() {
+				// 弹窗模式并且清理数据
+				if (this.clearDate && !this.insert) {
+					this.cale.cleanMultipleStatus()
+					// this.cale.setDate(this.date)
+					this.init(this.date)
+				}
+				this.show = true
+				this.$nextTick(() => {
+					setTimeout(() => {
+						this.aniMaskShow = true
+					}, 50)
+				})
+			},
+			/**
+			 * 关闭日历弹窗
+			 */
+			close() {
+				this.aniMaskShow = false
+				this.$nextTick(() => {
+					setTimeout(() => {
+						this.show = false
+						this.$emit('close')
+					}, 300)
+				})
+			},
+			/**
+			 * 确认按钮
+			 */
+			confirm() {
+				this.setEmit('confirm')
+				this.close()
+			},
+			/**
+			 * 变化触发
+			 */
+			change() {
+				if (!this.insert) return
+				this.setEmit('change')
+			},
+			/**
+			 * 选择月份触发
+			 */
+			monthSwitch() {
+				let {
+					year,
+					month
+				} = this.nowDate
+				this.$emit('monthSwitch', {
+					year,
+					month: Number(month)
+				})
+			},
+			/**
+			 * 派发事件
+			 * @param {Object} name
+			 */
+			setEmit(name) {
+				let {
+					year,
+					month,
+					date,
+					fullDate,
+					lunar,
+					extraInfo
+				} = this.calendar
+				this.$emit(name, {
+					range: this.cale.multipleStatus,
+					year,
+					month,
+					date,
+					fulldate: fullDate,
+					lunar,
+					extraInfo: extraInfo || {}
+				})
+			},
+			/**
+			 * 选择天触发
+			 * @param {Object} weeks
+			 */
+			choiceDate(weeks) {
+				if (weeks.disable) return
+				this.calendar = weeks
+				// 设置多选
+				this.cale.setMultiple(this.calendar.fullDate)
+				this.weeks = this.cale.weeks
+				this.change()
+			},
+			/**
+			 * 回到今天
+			 */
+			backtoday() {
+				console.log(this.cale.getDate(new Date()).fullDate);
+				let date = this.cale.getDate(new Date()).fullDate
+				// this.cale.setDate(date)
+				this.init(date)
+				this.change()
+			},
+			/**
+			 * 上个月
+			 */
+			pre() {
+				const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
+				this.setDate(preDate)
+				this.monthSwitch()
+
+			},
+			/**
+			 * 下个月
+			 */
+			next() {
+				const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
+				this.setDate(nextDate)
+				this.monthSwitch()
+			},
+			/**
+			 * 设置日期
+			 * @param {Object} date
+			 */
+			setDate(date) {
+				this.cale.setDate(date)
+				this.weeks = this.cale.weeks
+				this.nowDate = this.cale.getInfo(date)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	$uni-bg-color-mask: rgba($color: #000000, $alpha: 0.4);
+	$uni-border-color: #EDEDED;
+	$uni-text-color: #333;
+	$uni-bg-color-hover:#f1f1f1;
+	$uni-font-size-base:14px;
+	$uni-text-color-placeholder: #808080;
+	$uni-color-subtitle: #555555;
+	$uni-text-color-grey:#999;
+	.uni-calendar {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+	}
+
+	.uni-calendar__mask {
+		position: fixed;
+		bottom: 0;
+		top: 0;
+		left: 0;
+		right: 0;
+		background-color: $uni-bg-color-mask;
+		transition-property: opacity;
+		transition-duration: 0.3s;
+		opacity: 0;
+		/* #ifndef APP-NVUE */
+		z-index: 99;
+		/* #endif */
+	}
+
+	.uni-calendar--mask-show {
+		opacity: 1
+	}
+
+	.uni-calendar--fixed {
+		position: fixed;
+		/* #ifdef APP-NVUE */
+		bottom: 0;
+		/* #endif */
+		left: 0;
+		right: 0;
+		transition-property: transform;
+		transition-duration: 0.3s;
+		transform: translateY(460px);
+		/* #ifndef APP-NVUE */
+		bottom: calc(var(--window-bottom));
+		z-index: 99;
+		/* #endif */
+	}
+
+	.uni-calendar--ani-show {
+		transform: translateY(0);
+	}
+
+	.uni-calendar__content {
+		background-color: #fff;
+	}
+
+	.uni-calendar__header {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		height: 50px;
+		border-bottom-color: $uni-border-color;
+		border-bottom-style: solid;
+		border-bottom-width: 1px;
+	}
+
+	.uni-calendar--fixed-top {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: space-between;
+		border-top-color: $uni-border-color;
+		border-top-style: solid;
+		border-top-width: 1px;
+	}
+
+	.uni-calendar--fixed-width {
+		width: 50px;
+		// padding: 0 15px;
+	}
+
+	.uni-calendar__backtoday {
+		position: absolute;
+		right: 0;
+		top: 25rpx;
+		padding: 0 5px;
+		padding-left: 10px;
+		height: 25px;
+		line-height: 25px;
+		font-size: 12px;
+		border-top-left-radius: 25px;
+		border-bottom-left-radius: 25px;
+		color: $uni-text-color;
+		background-color: $uni-bg-color-hover;
+	}
+
+	.uni-calendar__header-text {
+		text-align: center;
+		width: 100px;
+		font-size: $uni-font-size-base;
+		color: $uni-text-color;
+	}
+
+	.uni-calendar__header-btn-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		width: 50px;
+		height: 50px;
+	}
+
+	.uni-calendar__header-btn {
+		width: 10px;
+		height: 10px;
+		border-left-color: $uni-text-color-placeholder;
+		border-left-style: solid;
+		border-left-width: 2px;
+		border-top-color: $uni-color-subtitle;
+		border-top-style: solid;
+		border-top-width: 2px;
+	}
+
+	.uni-calendar--left {
+		transform: rotate(-45deg);
+	}
+
+	.uni-calendar--right {
+		transform: rotate(135deg);
+	}
+
+
+	.uni-calendar__weeks {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+	}
+
+	.uni-calendar__weeks-item {
+		flex: 1;
+	}
+
+	.uni-calendar__weeks-day {
+		flex: 1;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		height: 45px;
+		border-bottom-color: #F5F5F5;
+		border-bottom-style: solid;
+		border-bottom-width: 1px;
+	}
+
+	.uni-calendar__weeks-day-text {
+		font-size: 14px;
+	}
+
+	.uni-calendar__box {
+		position: relative;
+	}
+
+	.uni-calendar__box-bg {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center;
+		position: absolute;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+	}
+
+	.uni-calendar__box-bg-text {
+		font-size: 200px;
+		font-weight: bold;
+		color: $uni-text-color-grey;
+		opacity: 0.1;
+		text-align: center;
+		/* #ifndef APP-NVUE */
+		line-height: 1;
+		/* #endif */
+	}
+</style>

+ 350 - 0
uni_modules/uni-calendar/components/uni-calendar/util.js

@@ -0,0 +1,350 @@
+import CALENDAR from './calendar.js'
+
+class Calendar {
+	constructor({
+		date,
+		selected,
+		startDate,
+		endDate,
+		range
+	} = {}) {
+		// 当前日期
+		this.date = this.getDate(new Date()) // 当前初入日期
+		// 打点信息
+		this.selected = selected || [];
+		// 范围开始
+		this.startDate = startDate
+		// 范围结束
+		this.endDate = endDate
+		this.range = range
+		// 多选状态
+		this.cleanMultipleStatus()
+		// 每周日期
+		this.weeks = {}
+		// this._getWeek(this.date.fullDate)
+	}
+	/**
+	 * 设置日期
+	 * @param {Object} date
+	 */
+	setDate(date) {
+		this.selectDate = this.getDate(date)
+		this._getWeek(this.selectDate.fullDate)
+	}
+
+	/**
+	 * 清理多选状态
+	 */
+	cleanMultipleStatus() {
+		this.multipleStatus = {
+			before: '',
+			after: '',
+			data: []
+		}
+	}
+
+	/**
+	 * 重置开始日期
+	 */
+	resetSatrtDate(startDate) {
+		// 范围开始
+		this.startDate = startDate
+
+	}
+
+	/**
+	 * 重置结束日期
+	 */
+	resetEndDate(endDate) {
+		// 范围结束
+		this.endDate = endDate
+	}
+
+	/**
+	 * 获取任意时间
+	 */
+	getDate(date, AddDayCount = 0, str = 'day') {
+		if (!date) {
+			date = new Date()
+		}
+		if (typeof date !== 'object') {
+			date = date.replace(/-/g, '/')
+		}
+		const dd = new Date(date)
+		switch (str) {
+			case 'day':
+				dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
+				break
+			case 'month':
+				if (dd.getDate() === 31) {
+					dd.setDate(dd.getDate() + AddDayCount)
+				} else {
+					dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期
+				}
+				break
+			case 'year':
+				dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
+				break
+		}
+		const y = dd.getFullYear()
+		const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
+		const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
+		return {
+			fullDate: y + '-' + m + '-' + d,
+			year: y,
+			month: m,
+			date: d,
+			day: dd.getDay()
+		}
+	}
+
+
+	/**
+	 * 获取上月剩余天数
+	 */
+	_getLastMonthDays(firstDay, full) {
+		let dateArr = []
+		for (let i = firstDay; i > 0; i--) {
+			const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
+			dateArr.push({
+				date: beforeDate,
+				month: full.month - 1,
+				lunar: this.getlunar(full.year, full.month - 1, beforeDate),
+				disable: true
+			})
+		}
+		return dateArr
+	}
+	/**
+	 * 获取本月天数
+	 */
+	_currentMonthDys(dateData, full) {
+		let dateArr = []
+		let fullDate = this.date.fullDate
+		for (let i = 1; i <= dateData; i++) {
+			let nowDate = full.year + '-' + (full.month < 10 ?
+				full.month : full.month) + '-' + (i < 10 ?
+				'0' + i : i)
+			// 是否今天
+			let isDay = fullDate === nowDate
+			// 获取打点信息
+			let info = this.selected && this.selected.find((item) => {
+				if (this.dateEqual(nowDate, item.date)) {
+					return item
+				}
+			})
+
+			// 日期禁用
+			let disableBefore = true
+			let disableAfter = true
+			if (this.startDate) {
+				// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
+				// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
+				disableBefore = this.dateCompare(this.startDate, nowDate)
+			}
+
+			if (this.endDate) {
+				// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
+				// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
+				disableAfter = this.dateCompare(nowDate, this.endDate)
+			}
+			let multiples = this.multipleStatus.data
+			let checked = false
+			let multiplesStatus = -1
+			if (this.range) {
+				if (multiples) {
+					multiplesStatus = multiples.findIndex((item) => {
+						return this.dateEqual(item, nowDate)
+					})
+				}
+				if (multiplesStatus !== -1) {
+					checked = true
+				}
+			}
+			let data = {
+				fullDate: nowDate,
+				year: full.year,
+				date: i,
+				multiple: this.range ? checked : false,
+				beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate),
+				afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate),
+				month: full.month,
+				lunar: this.getlunar(full.year, full.month, i),
+				disable: !(disableBefore && disableAfter),
+				isDay
+			}
+			if (info) {
+				data.extraInfo = info
+			}
+
+			dateArr.push(data)
+		}
+		return dateArr
+	}
+	/**
+	 * 获取下月天数
+	 */
+	_getNextMonthDays(surplus, full) {
+		let dateArr = []
+		for (let i = 1; i < surplus + 1; i++) {
+			dateArr.push({
+				date: i,
+				month: Number(full.month) + 1,
+				lunar: this.getlunar(full.year, Number(full.month) + 1, i),
+				disable: true
+			})
+		}
+		return dateArr
+	}
+
+	/**
+	 * 获取当前日期详情
+	 * @param {Object} date
+	 */
+	getInfo(date) {
+		if (!date) {
+			date = new Date()
+		}
+		const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
+		return dateInfo
+	}
+
+	/**
+	 * 比较时间大小
+	 */
+	dateCompare(startDate, endDate) {
+		// 计算截止时间
+		startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
+		// 计算详细项的截止时间
+		endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
+		if (startDate <= endDate) {
+			return true
+		} else {
+			return false
+		}
+	}
+
+	/**
+	 * 比较时间是否相等
+	 */
+	dateEqual(before, after) {
+		// 计算截止时间
+		before = new Date(before.replace('-', '/').replace('-', '/'))
+		// 计算详细项的截止时间
+		after = new Date(after.replace('-', '/').replace('-', '/'))
+		if (before.getTime() - after.getTime() === 0) {
+			return true
+		} else {
+			return false
+		}
+	}
+
+
+	/**
+	 * 获取日期范围内所有日期
+	 * @param {Object} begin
+	 * @param {Object} end
+	 */
+	geDateAll(begin, end) {
+		var arr = []
+		var ab = begin.split('-')
+		var ae = end.split('-')
+		var db = new Date()
+		db.setFullYear(ab[0], ab[1] - 1, ab[2])
+		var de = new Date()
+		de.setFullYear(ae[0], ae[1] - 1, ae[2])
+		var unixDb = db.getTime() - 24 * 60 * 60 * 1000
+		var unixDe = de.getTime() - 24 * 60 * 60 * 1000
+		for (var k = unixDb; k <= unixDe;) {
+			k = k + 24 * 60 * 60 * 1000
+			arr.push(this.getDate(new Date(parseInt(k))).fullDate)
+		}
+		return arr
+	}
+	/**
+	 * 计算阴历日期显示
+	 */
+	getlunar(year, month, date) {
+		return CALENDAR.solar2lunar(year, month, date)
+	}
+	/**
+	 * 设置打点
+	 */
+	setSelectInfo(data, value) {
+		this.selected = value
+		this._getWeek(data)
+	}
+
+	/**
+	 *  获取多选状态
+	 */
+	setMultiple(fullDate) {
+		let {
+			before,
+			after
+		} = this.multipleStatus
+
+		if (!this.range) return
+		if (before && after) {
+			this.multipleStatus.before = ''
+			this.multipleStatus.after = ''
+			this.multipleStatus.data = []
+		} else {
+			if (!before) {
+				this.multipleStatus.before = fullDate
+			} else {
+				this.multipleStatus.after = fullDate
+				if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
+					this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
+				} else {
+					this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
+				}
+			}
+		}
+		this._getWeek(fullDate)
+	}
+
+	/**
+	 * 获取每周数据
+	 * @param {Object} dateData
+	 */
+	_getWeek(dateData) {
+		const {
+			year,
+			month
+		} = this.getDate(dateData)
+		let firstDay = new Date(year, month - 1, 1).getDay()
+		let currentDay = new Date(year, month, 0).getDate()
+		let dates = {
+			lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
+			currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
+			nextMonthDays: [], // 下个月开始几天
+			weeks: []
+		}
+		let canlender = []
+		const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
+		dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
+		canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
+		let weeks = {}
+		// 拼接数组  上个月开始几天 + 本月天数+ 下个月开始几天
+		for (let i = 0; i < canlender.length; i++) {
+			if (i % 7 === 0) {
+				weeks[parseInt(i / 7)] = new Array(7)
+			}
+			weeks[parseInt(i / 7)][i % 7] = canlender[i]
+		}
+		this.canlender = canlender
+		this.weeks = weeks
+	}
+
+	//静态方法
+	// static init(date) {
+	// 	if (!this.instance) {
+	// 		this.instance = new Calendar(date);
+	// 	}
+	// 	return this.instance;
+	// }
+}
+
+
+export default Calendar

+ 85 - 0
uni_modules/uni-calendar/package.json

@@ -0,0 +1,85 @@
+{
+  "id": "uni-calendar",
+  "displayName": "uni-calendar 日历",
+  "version": "1.4.7",
+  "description": "日历组件",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "日历",
+    "",
+    "打卡",
+    "日历选择"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
+    "type": "component-vue"
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "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"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 103 - 0
uni_modules/uni-calendar/readme.md

@@ -0,0 +1,103 @@
+
+
+## Calendar 日历
+> **组件名:uni-calendar**
+> 代码块: `uCalendar`
+
+
+日历组件
+
+> **注意事项**
+> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
+> - 本组件农历转换使用的js是 [@1900-2100区间内的公历、农历互转](https://github.com/jjonline/calendar.js)  
+> - 仅支持自定义组件模式
+> - `date`属性传入的应该是一个 String ,如: 2019-06-27 ,而不是 new Date()
+> - 通过 `insert` 属性来确定当前的事件是 @change 还是 @confirm 。理应合并为一个事件,但是为了区分模式,现使用两个事件,这里需要注意
+> - 弹窗模式下无法阻止后面的元素滚动,如有需要阻止,请在弹窗弹出后,手动设置滚动元素为不可滚动
+
+
+### 安装方式
+
+本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
+
+如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
+
+### 基本用法
+
+在 ``template`` 中使用组件
+
+```html
+<view>
+	<uni-calendar 
+	:insert="true"
+	:lunar="true" 
+	:start-date="'2019-3-2'"
+	:end-date="'2019-5-20'"
+	@change="change"
+	 />
+</view>
+```
+
+### 通过方法打开日历
+
+需要设置 `insert` 为 `false`
+
+```html
+<view>
+	<uni-calendar 
+	ref="calendar"
+	:insert="false"
+	@confirm="confirm"
+	 />
+	 <button @click="open">打开日历</button>
+</view>
+```
+
+```javascript
+
+export default {
+	data() {
+		return {};
+	},
+	methods: {
+		open(){
+			this.$refs.calendar.open();
+		},
+		confirm(e) {
+			console.log(e);
+		}
+	}
+};
+
+```
+
+
+## API
+
+### Calendar Props
+
+|  属性名	|    类型	| 默认值| 说明																													|
+| 		| 																													|
+| date		| String	|-		| 自定义当前时间,默认为今天																							|
+| lunar		| Boolean	| false	| 显示农历																												|
+| startDate	| String	|-		| 日期选择范围-开始日期																									|
+| endDate	| String	|-		| 日期选择范围-结束日期																									|
+| range		| Boolean	| false	| 范围选择																												|
+| insert	| Boolean	| false	| 插入模式,可选值,ture:插入模式;false:弹窗模式;默认为插入模式														|
+|clearDate	|Boolean	|true	|弹窗模式是否清空上次选择内容	|
+| selected	| Array		|-		| 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]	|
+|showMonth	| Boolean	| true	| 是否显示月份为背景																									|
+
+### Calendar Events
+
+|  事件名		| 说明								|返回值|
+| 								|		| 									|
+| open	| 弹出日历组件,`insert :false` 时生效|- 	|
+
+
+
+
+
+## 组件示例
+
+点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar](https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar)

+ 26 - 0
uni_modules/uni-card/changelog.md

@@ -0,0 +1,26 @@
+## 1.3.1(2021-12-20)
+- 修复 在vue页面下略缩图显示不正常的bug
+## 1.3.0(2021-11-19)
+- 重构插槽的用法 ,header 替换为 title 
+- 新增 actions 插槽
+- 新增 cover 封面图属性和插槽
+- 新增 padding 内容默认内边距离
+- 新增 margin 卡片默认外边距离
+- 新增 spacing 卡片默认内边距
+- 新增 shadow 卡片阴影属性
+- 取消 mode 属性,可使用组合插槽代替
+- 取消 note 属性 ,使用actions插槽代替
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-card](https://uniapp.dcloud.io/component/uniui/uni-card)
+## 1.2.1(2021-07-30)
+- 优化 vue3下事件警告的问题
+## 1.2.0(2021-07-13)
+- 组件兼容 vue3,如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.1.8(2021-07-01)
+- 优化 图文卡片无图片加载时,提供占位图标
+- 新增 header 插槽,自定义卡片头部( 图文卡片 mode="style" 时,不支持)
+- 修复 thumbnail 不存在仍然占位的 bug
+## 1.1.7(2021-05-12)
+- 新增 组件示例地址
+## 1.1.6(2021-02-04)
+- 调整为uni_modules目录规范

+ 270 - 0
uni_modules/uni-card/components/uni-card/uni-card.vue

@@ -0,0 +1,270 @@
+<template>
+	<view class="uni-card" :class="{ 'uni-card--full': isFull, 'uni-card--shadow': isShadow,'uni-card--border':border}"
+		:style="{'margin':isFull?0:margin,'padding':spacing,'box-shadow':isShadow?shadow:''}">
+		<!-- 封面 -->
+		<slot name="cover">
+			<view v-if="cover" class="uni-card__cover">
+				<image class="uni-card__cover-image" mode="widthFix" @click="onClick('cover')" :src="cover"></image>
+			</view>
+		</slot>
+		<slot name="title">
+			<view v-if="title || extra" class="uni-card__header">
+				<!-- 卡片标题 -->
+				<view class="uni-card__header-box" @click="onClick('title')">
+					<view v-if="thumbnail" class="uni-card__header-avatar">
+						<image class="uni-card__header-avatar-image" :src="thumbnail" mode="aspectFit" />
+					</view>
+					<view class="uni-card__header-content">
+						<text class="uni-card__header-content-title uni-ellipsis">{{ title }}</text>
+						<text v-if="title&&subTitle"
+							class="uni-card__header-content-subtitle uni-ellipsis">{{ subTitle }}</text>
+					</view>
+				</view>
+				<view class="uni-card__header-extra" @click="onClick('extra')">
+					<text class="uni-card__header-extra-text">{{ extra }}</text>
+				</view>
+			</view>
+		</slot>
+		<!-- 卡片内容 -->
+		<view class="uni-card__content" :style="{padding:padding}" @click="onClick('content')">
+			<slot></slot>
+		</view>
+		<view class="uni-card__actions" @click="onClick('actions')">
+			<slot name="actions"></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	/**
+	 * Card 卡片
+	 * @description 卡片视图组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=22
+	 * @property {String} title 标题文字
+	 * @property {String} subTitle 副标题
+	 * @property {Number} padding 内容内边距
+	 * @property {Number} margin 卡片外边距
+	 * @property {Number} spacing 卡片内边距
+	 * @property {String} extra 标题额外信息
+	 * @property {String} cover 封面图(本地路径需要引入)
+	 * @property {String} thumbnail 标题左侧缩略图
+	 * @property {Boolean} is-full = [true | false] 卡片内容是否通栏,为 true 时将去除padding值
+	 * @property {Boolean} is-shadow = [true | false] 卡片内容是否开启阴影
+	 * @property {String} shadow 卡片阴影
+	 * @property {Boolean} border 卡片边框
+	 * @event {Function} click 点击 Card 触发事件
+	 */
+	export default {
+		name: 'UniCard',
+		emits: ['click'],
+		props: {
+			title: {
+				type: String,
+				default: ''
+			},
+			subTitle: {
+				type: String,
+				default: ''
+			},
+			padding: {
+				type: String,
+				default: '10px'
+			},
+			margin: {
+				type: String,
+				default: '15px'
+			},
+			spacing: {
+				type: String,
+				default: '0 10px'
+			},
+			extra: {
+				type: String,
+				default: ''
+			},
+			cover: {
+				type: String,
+				default: ''
+			},
+			thumbnail: {
+				type: String,
+				default: ''
+			},
+			isFull: {
+				// 内容区域是否通栏
+				type: Boolean,
+				default: false
+			},
+			isShadow: {
+				// 是否开启阴影
+				type: Boolean,
+				default: true
+			},
+			shadow: {
+				type: String,
+				default: '0px 0px 3px 1px rgba(0, 0, 0, 0.08)'
+			},
+			border: {
+				type: Boolean,
+				default: true
+			}
+		},
+		methods: {
+			onClick(type) {
+				this.$emit('click', type)
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	$uni-border-3: #EBEEF5 !default;
+	$uni-shadow-base:0 0px 6px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
+	$uni-main-color: #3a3a3a !default;
+	$uni-base-color: #6a6a6a !default;
+	$uni-secondary-color: #909399 !default;
+	$uni-spacing-sm: 8px !default;
+	$uni-border-color:$uni-border-3;
+	$uni-shadow: $uni-shadow-base;
+	$uni-card-title: 15px;
+	$uni-cart-title-color:$uni-main-color;
+	$uni-card-subtitle: 12px;
+	$uni-cart-subtitle-color:$uni-secondary-color;
+	$uni-card-spacing: 10px;
+	$uni-card-content-color: $uni-base-color;
+
+	.uni-card {
+		margin: $uni-card-spacing;
+		padding: 0 $uni-spacing-sm;
+		border-radius: 4px;
+		overflow: hidden;
+		font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
+		background-color: #fff;
+		flex: 1;
+
+		.uni-card__cover {
+			position: relative;
+			margin-top: $uni-card-spacing;
+			flex-direction: row;
+			overflow: hidden;
+			border-radius: 4px;
+			.uni-card__cover-image {
+				flex: 1;
+				// width: 100%;
+				/* #ifndef APP-PLUS */
+				vertical-align: middle;
+				/* #endif */
+			}
+		}
+
+		.uni-card__header {
+			display: flex;
+			border-bottom: 1px $uni-border-color solid;
+			flex-direction: row;
+			align-items: center;
+			padding: $uni-card-spacing;
+			overflow: hidden;
+
+			.uni-card__header-box {
+				/* #ifndef APP-NVUE */
+				display: flex;
+				/* #endif */
+				flex: 1;
+				flex-direction: row;
+				align-items: center;
+				overflow: hidden;
+			}
+
+			.uni-card__header-avatar {
+				width: 40px;
+				height: 40px;
+				overflow: hidden;
+				border-radius: 5px;
+				margin-right: $uni-card-spacing;
+				.uni-card__header-avatar-image {
+					flex: 1;
+					width: 40px;
+					height: 40px;
+				}
+			}
+
+			.uni-card__header-content {
+				/* #ifndef APP-NVUE */
+				display: flex;
+				/* #endif */
+				flex-direction: column;
+				justify-content: center;
+				flex: 1;
+				// height: 40px;
+				overflow: hidden;
+
+				.uni-card__header-content-title {
+					font-size: $uni-card-title;
+					color: $uni-cart-title-color;
+					// line-height: 22px;
+				}
+
+				.uni-card__header-content-subtitle {
+					font-size: $uni-card-subtitle;
+					margin-top: 5px;
+					color: $uni-cart-subtitle-color;
+				}
+			}
+
+			.uni-card__header-extra {
+				line-height: 12px;
+
+				.uni-card__header-extra-text {
+					font-size: 12px;
+					color: $uni-cart-subtitle-color;
+				}
+			}
+		}
+
+		.uni-card__content {
+			padding: $uni-card-spacing;
+			font-size: 14px;
+			color: $uni-card-content-color;
+			line-height: 22px;
+		}
+
+		.uni-card__actions {
+			font-size: 12px;
+		}
+	}
+
+	.uni-card--border {
+		border: 1px solid $uni-border-color;
+	}
+
+	.uni-card--shadow {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		box-shadow: $uni-shadow;
+		/* #endif */
+	}
+
+	.uni-card--full {
+		margin: 0;
+		border-left-width: 0;
+		border-left-width: 0;
+		border-radius: 0;
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-card--full:after {
+		border-radius: 0;
+	}
+
+	/* #endif */
+	.uni-ellipsis {
+		/* #ifndef APP-NVUE */
+		overflow: hidden;
+		white-space: nowrap;
+		text-overflow: ellipsis;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		lines: 1;
+		/* #endif */
+	}
+</style>

+ 90 - 0
uni_modules/uni-card/package.json

@@ -0,0 +1,90 @@
+{
+  "id": "uni-card",
+  "displayName": "uni-card 卡片",
+  "version": "1.3.1",
+  "description": "Card 组件,提供常见的卡片样式。",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "card",
+    "",
+    "卡片"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+  "dcloudext": {
+    "category": [
+      "前端组件",
+      "通用组件"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uni-icons",
+			"uni-scss"
+		],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "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"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 12 - 0
uni_modules/uni-card/readme.md

@@ -0,0 +1,12 @@
+
+
+## Card 卡片
+> **组件名:uni-card**
+> 代码块: `uCard`
+
+卡片视图组件。
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 
+
+

+ 36 - 0
uni_modules/uni-collapse/changelog.md

@@ -0,0 +1,36 @@
+## 1.4.3(2022-01-25)
+- 修复 初始化的时候 ,open 属性失效的bug
+## 1.4.2(2022-01-21)
+- 修复 微信小程序resize后组件收起的bug
+## 1.4.1(2021-11-22)
+- 修复 vue3中个别scss变量无法找到的问题
+## 1.4.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-collapse](https://uniapp.dcloud.io/component/uniui/uni-collapse)
+## 1.3.3(2021-08-17)
+- 优化 show-arrow 属性默认为true
+## 1.3.2(2021-08-17)
+- 新增 show-arrow 属性,控制是否显示右侧箭头
+## 1.3.1(2021-07-30)
+- 优化 vue3下小程序事件警告的问题
+## 1.3.0(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.2.2(2021-07-21)
+- 修复 由1.2.0版本引起的 change 事件返回 undefined 的Bug
+## 1.2.1(2021-07-21)
+- 优化 组件示例
+## 1.2.0(2021-07-21)
+- 新增 组件折叠动画
+- 新增 value\v-model 属性 ,动态修改面板折叠状态
+- 新增 title 插槽 ,可定义面板标题
+- 新增 border 属性 ,显示隐藏面板内容分隔线
+- 新增 title-border 属性 ,显示隐藏面板标题分隔线
+- 修复 resize 方法失效的Bug
+- 修复 change 事件返回参数不正确的Bug
+- 优化 H5、App 平台自动更具内容更新高度,无需调用 reszie() 方法
+## 1.1.7(2021-05-12)
+- 新增 组件示例地址
+## 1.1.6(2021-02-05)
+- 优化 组件引用关系,通过uni_modules引用组件
+## 1.1.5(2021-02-05)
+- 调整为uni_modules目录规范

+ 402 - 0
uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue

@@ -0,0 +1,402 @@
+<template>
+	<view class="uni-collapse-item">
+		<!-- onClick(!isOpen) -->
+		<view @click="onClick(!isOpen)" class="uni-collapse-item__title"
+			:class="{'is-open':isOpen &&titleBorder === 'auto' ,'uni-collapse-item-border':titleBorder !== 'none'}">
+			<view class="uni-collapse-item__title-wrap">
+				<slot name="title">
+					<view class="uni-collapse-item__title-box" :class="{'is-disabled':disabled}">
+						<image v-if="thumb" :src="thumb" class="uni-collapse-item__title-img" />
+						<text class="uni-collapse-item__title-text">{{ title }}</text>
+					</view>
+				</slot>
+			</view>
+			<view v-if="showArrow"
+				:class="{ 'uni-collapse-item__title-arrow-active': isOpen, 'uni-collapse-item--animation': showAnimation === true }"
+				class="uni-collapse-item__title-arrow">
+				<uni-icons :color="disabled?'#ddd':'#bbb'" size="14" type="bottom" />
+			</view>
+		</view>
+		<view class="uni-collapse-item__wrap" :class="{'is--transition':showAnimation}"
+			:style="{height: (isOpen?height:0) +'px'}">
+			<view :id="elId" ref="collapse--hook" class="uni-collapse-item__wrap-content"
+				:class="{open:isheight,'uni-collapse-item--border':border&&isOpen}">
+				<slot></slot>
+			</view>
+		</view>
+
+	</view>
+</template>
+
+<script>
+	// #ifdef APP-NVUE
+	const dom = weex.requireModule('dom')
+	// #endif
+	/**
+	 * CollapseItem 折叠面板子组件
+	 * @description 折叠面板子组件
+	 * @property {String} title 标题文字
+	 * @property {String} thumb 标题左侧缩略图
+	 * @property {String} name 唯一标志符
+	 * @property {Boolean} open = [true|false] 是否展开组件
+	 * @property {Boolean} titleBorder = [true|false] 是否显示标题分隔线
+	 * @property {Boolean} border = [true|false] 是否显示分隔线
+	 * @property {Boolean} disabled = [true|false] 是否展开面板
+	 * @property {Boolean} showAnimation = [true|false] 开启动画
+	 * @property {Boolean} showArrow = [true|false] 是否显示右侧箭头
+	 */
+	export default {
+		name: 'uniCollapseItem',
+		props: {
+			// 列表标题
+			title: {
+				type: String,
+				default: ''
+			},
+			name: {
+				type: [Number, String],
+				default: ''
+			},
+			// 是否禁用
+			disabled: {
+				type: Boolean,
+				default: false
+			},
+			// #ifdef APP-PLUS
+			// 是否显示动画,app 端默认不开启动画,卡顿严重
+			showAnimation: {
+				type: Boolean,
+				default: false
+			},
+			// #endif
+			// #ifndef APP-PLUS
+			// 是否显示动画
+			showAnimation: {
+				type: Boolean,
+				default: true
+			},
+			// #endif
+			// 是否展开
+			open: {
+				type: Boolean,
+				default: false
+			},
+			// 缩略图
+			thumb: {
+				type: String,
+				default: ''
+			},
+			// 标题分隔线显示类型
+			titleBorder: {
+				type: String,
+				default: 'auto'
+			},
+			border: {
+				type: Boolean,
+				default: true
+			},
+			showArrow: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			// TODO 随机生生元素ID,解决百度小程序获取同一个元素位置信息的bug
+			const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
+			return {
+				isOpen: false,
+				isheight: null,
+				height: 0,
+				elId,
+				nameSync: 0
+			}
+		},
+		watch: {
+			open(val) {
+				this.isOpen = val
+				this.onClick(val, 'init')
+			}
+		},
+		updated(e) {
+			this.$nextTick(() => {
+				this.init(true)
+			})
+		},
+		created() {
+			this.collapse = this.getCollapse()
+			this.oldHeight = 0
+			this.onClick(this.open, 'init')
+		},
+		// #ifndef VUE3
+		// TODO vue2
+		destroyed() {
+			if (this.__isUnmounted) return
+			this.uninstall()
+		},
+		// #endif
+		// #ifdef VUE3
+		// TODO vue3
+		unmounted() {
+			this.__isUnmounted = true
+			this.uninstall()
+		},
+		// #endif
+		mounted() {
+			if (!this.collapse) return
+			if (this.name !== '') {
+				this.nameSync = this.name
+			} else {
+				this.nameSync = this.collapse.childrens.length + ''
+			}
+			if (this.collapse.names.indexOf(this.nameSync) === -1) {
+				this.collapse.names.push(this.nameSync)
+			} else {
+				console.warn(`name 值 ${this.nameSync} 重复`);
+			}
+			if (this.collapse.childrens.indexOf(this) === -1) {
+				this.collapse.childrens.push(this)
+			}
+			this.init()
+		},
+		methods: {
+			init(type) {
+				// #ifndef APP-NVUE
+				this.getCollapseHeight(type)
+				// #endif
+				// #ifdef APP-NVUE
+				this.getNvueHwight(type)
+				// #endif
+			},
+			uninstall() {
+				if (this.collapse) {
+					this.collapse.childrens.forEach((item, index) => {
+						if (item === this) {
+							this.collapse.childrens.splice(index, 1)
+						}
+					})
+					this.collapse.names.forEach((item, index) => {
+						if (item === this.nameSync) {
+							this.collapse.names.splice(index, 1)
+						}
+					})
+				}
+			},
+			onClick(isOpen, type) {
+				if (this.disabled) return
+				this.isOpen = isOpen
+				if (this.isOpen && this.collapse) {
+					this.collapse.setAccordion(this)
+				}
+				if (type !== 'init') {
+					this.collapse.onChange(isOpen, this)
+				}
+			},
+			getCollapseHeight(type, index = 0) {
+				const views = uni.createSelectorQuery().in(this)
+				views
+					.select(`#${this.elId}`)
+					.fields({
+						size: true
+					}, data => {
+						// TODO 百度中可能获取不到节点信息 ,需要循环获取
+						if (index >= 10) return
+						if (!data) {
+							index++
+							this.getCollapseHeight(false, index)
+							return
+						}
+						// #ifdef APP-NVUE
+						this.height = data.height + 1
+						// #endif
+						// #ifndef APP-NVUE
+						this.height = data.height
+						// #endif
+						this.isheight = true
+						if (type) return
+						this.onClick(this.isOpen, 'init')
+					})
+					.exec()
+			},
+			getNvueHwight(type) {
+				const result = dom.getComponentRect(this.$refs['collapse--hook'], option => {
+					if (option && option.result && option.size) {
+						// #ifdef APP-NVUE
+						this.height = option.size.height + 1
+						// #endif
+						// #ifndef APP-NVUE
+						this.height = option.size.height
+						// #endif
+						this.isheight = true
+						if (type) return
+						this.onClick(this.open, 'init')
+					}
+				})
+			},
+			/**
+			 * 获取父元素实例
+			 */
+			getCollapse(name = 'uniCollapse') {
+				let parent = this.$parent;
+				let parentName = parent.$options.name;
+				while (parentName !== name) {
+					parent = parent.$parent;
+					if (!parent) return false;
+					parentName = parent.$options.name;
+				}
+				return parent;
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.uni-collapse-item {
+		/* #ifndef APP-NVUE */
+		box-sizing: border-box;
+
+		/* #endif */
+		&__title {
+			/* #ifndef APP-NVUE */
+			display: flex;
+			width: 100%;
+			box-sizing: border-box;
+			/* #endif */
+			flex-direction: row;
+			align-items: center;
+			transition: border-bottom-color .3s;
+
+			// transition-property: border-bottom-color;
+			// transition-duration: 5s;
+			&-wrap {
+				width: 100%;
+				flex: 1;
+
+			}
+
+			&-box {
+				padding: 0 15px;
+				/* #ifndef APP-NVUE */
+				display: flex;
+				width: 100%;
+				box-sizing: border-box;
+				/* #endif */
+				flex-direction: row;
+				justify-content: space-between;
+				align-items: center;
+				height: 48px;
+				line-height: 48px;
+				background-color: #fff;
+				color: #303133;
+				font-size: 13px;
+				font-weight: 500;
+				/* #ifdef H5 */
+				cursor: pointer;
+				outline: none;
+
+				/* #endif */
+				&.is-disabled {
+					.uni-collapse-item__title-text {
+						color: #999;
+					}
+				}
+
+			}
+
+			&.uni-collapse-item-border {
+				border-bottom: 1px solid #ebeef5;
+			}
+
+			&.is-open {
+				border-bottom-color: transparent;
+			}
+
+			&-img {
+				height: 22px;
+				width: 22px;
+				margin-right: 10px;
+			}
+
+			&-text {
+				flex: 1;
+				font-size: 14px;
+				/* #ifndef APP-NVUE */
+				white-space: nowrap;
+				color: inherit;
+				/* #endif */
+				/* #ifdef APP-NVUE */
+				lines: 1;
+				/* #endif */
+				overflow: hidden;
+				text-overflow: ellipsis;
+			}
+
+			&-arrow {
+				/* #ifndef APP-NVUE */
+				display: flex;
+				box-sizing: border-box;
+				/* #endif */
+				align-items: center;
+				justify-content: center;
+				width: 20px;
+				height: 20px;
+				margin-right: 10px;
+				transform: rotate(0deg);
+
+				&-active {
+					transform: rotate(-180deg);
+				}
+			}
+
+
+		}
+
+		&__wrap {
+			/* #ifndef APP-NVUE */
+			will-change: height;
+			box-sizing: border-box;
+			/* #endif */
+			background-color: #fff;
+			overflow: hidden;
+			position: relative;
+			height: 0;
+
+			&.is--transition {
+				// transition: all 0.3s;
+				transition-property: height, border-bottom-width;
+				transition-duration: 0.3s;
+				/* #ifndef APP-NVUE */
+				will-change: height;
+				/* #endif */
+			}
+
+
+
+			&-content {
+				position: absolute;
+				font-size: 13px;
+				color: #303133;
+				// transition: height 0.3s;
+				border-bottom-color: transparent;
+				border-bottom-style: solid;
+				border-bottom-width: 0;
+
+				&.uni-collapse-item--border {
+					border-bottom-width: 1px;
+					border-bottom-color: red;
+					border-bottom-color: #ebeef5;
+				}
+
+				&.open {
+					position: relative;
+				}
+			}
+		}
+
+		&--animation {
+			transition-property: transform;
+			transition-duration: 0.3s;
+			transition-timing-function: ease;
+		}
+
+	}
+</style>

+ 147 - 0
uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue

@@ -0,0 +1,147 @@
+<template>
+	<view class="uni-collapse">
+		<slot />
+	</view>
+</template>
+<script>
+	/**
+	 * Collapse 折叠面板
+	 * @description 展示可以折叠 / 展开的内容区域
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=23
+	 * @property {String|Array} value 当前激活面板改变时触发(如果是手风琴模式,参数类型为string,否则为array)
+	 * @property {Boolean} accordion = [true|false] 是否开启手风琴效果是否开启手风琴效果
+	 * @event {Function} change 切换面板时触发,如果是手风琴模式,返回类型为string,否则为array
+	 */
+	export default {
+		name: 'uniCollapse',
+		emits:['change','activeItem','input','update:modelValue'],
+		props: {
+			value: {
+				type: [String, Array],
+				default: ''
+			},
+			modelValue: {
+				type: [String, Array],
+				default: ''
+			},
+			accordion: {
+				// 是否开启手风琴效果
+				type: [Boolean, String],
+				default: false
+			},
+		},
+		data() {
+			return {}
+		},
+		computed: {
+			// TODO 兼容 vue2 和 vue3
+			dataValue() {
+				let value = (typeof this.value === 'string' && this.value === '') ||
+					(Array.isArray(this.value) && this.value.length === 0)
+				let modelValue = (typeof this.modelValue === 'string' && this.modelValue === '') ||
+					(Array.isArray(this.modelValue) && this.modelValue.length === 0)
+				if (value) {
+					return this.modelValue
+				}
+				if (modelValue) {
+					return this.value
+				}
+
+				return this.value
+			}
+		},
+		watch: {
+			dataValue(val) {
+				this.setOpen(val)
+			}
+		},
+		created() {
+			this.childrens = []
+			this.names = []
+		},
+		mounted() {
+			this.$nextTick(()=>{
+				this.setOpen(this.dataValue)
+			})
+		},
+		methods: {
+			setOpen(val) {
+				let str = typeof val === 'string'
+				let arr = Array.isArray(val)
+				this.childrens.forEach((vm, index) => {
+					if (str) {
+						if (val === vm.nameSync) {
+							if (!this.accordion) {
+								console.warn('accordion 属性为 false ,v-model 类型应该为 array')
+								return
+							}
+							vm.isOpen = true
+						}
+					}
+					if (arr) {
+						val.forEach(v => {
+							if (v === vm.nameSync) {
+								if (this.accordion) {
+									console.warn('accordion 属性为 true ,v-model 类型应该为 string')
+									return
+								}
+								vm.isOpen = true
+							}
+						})
+					}
+				})
+				this.emit(val)
+			},
+			setAccordion(self) {
+				if (!this.accordion) return
+				this.childrens.forEach((vm, index) => {
+					if (self !== vm) {
+						vm.isOpen = false
+					}
+				})
+			},
+			resize() {
+				this.childrens.forEach((vm, index) => {
+					// #ifndef APP-NVUE
+					vm.getCollapseHeight()
+					// #endif
+					// #ifdef APP-NVUE
+					vm.getNvueHwight()
+					// #endif
+				})
+			},
+			onChange(isOpen, self) {
+				let activeItem = []
+
+				if (this.accordion) {
+					activeItem = isOpen ? self.nameSync : ''
+				} else {
+					this.childrens.forEach((vm, index) => {
+						if (vm.isOpen) {
+							activeItem.push(vm.nameSync)
+						}
+					})
+				}
+				this.$emit('change', activeItem)
+				this.emit(activeItem)
+			},
+			emit(val){
+				this.$emit('input', val)
+				this.$emit('update:modelValue', val)
+			}
+		}
+	}
+</script>
+<style lang="scss" >
+	.uni-collapse {
+		/* #ifndef APP-NVUE */
+		width: 100%;
+		display: flex;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		flex: 1;
+		/* #endif */
+		flex-direction: column;
+		background-color: #fff;
+	}
+</style>

+ 89 - 0
uni_modules/uni-collapse/package.json

@@ -0,0 +1,89 @@
+{
+  "id": "uni-collapse",
+  "displayName": "uni-collapse 折叠面板",
+  "version": "1.4.3",
+  "description": "Collapse 组件,可以折叠 / 展开的内容区域。",
+  "keywords": [
+    "uni-ui",
+    "折叠",
+    "折叠面板",
+    "手风琴"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+  "dcloudext": {
+    "category": [
+      "前端组件",
+      "通用组件"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uni-scss",
+      "uni-icons"
+    ],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "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"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 12 - 0
uni_modules/uni-collapse/readme.md

@@ -0,0 +1,12 @@
+
+
+## Collapse 折叠面板
+> **组件名:uni-collapse**
+> 代码块: `uCollapse`
+> 关联组件:`uni-collapse-item`、`uni-icons`。
+
+
+折叠面板用来折叠/显示过长的内容或者是列表。通常是在多内容分类项使用,折叠不重要的内容,显示重要内容。点击可以展开折叠部分。
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-collapse)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 15 - 0
uni_modules/uni-combox/changelog.md

@@ -0,0 +1,15 @@
+## 1.0.1(2021-11-23)
+- 优化 label、label-width 属性
+## 1.0.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-combox](https://uniapp.dcloud.io/component/uniui/uni-combox)
+## 0.1.0(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 0.0.6(2021-05-12)
+- 新增 组件示例地址
+## 0.0.5(2021-04-21)
+- 优化 添加依赖 uni-icons, 导入后自动下载依赖
+## 0.0.4(2021-02-05)
+- 优化 组件引用关系,通过uni_modules引用组件
+## 0.0.3(2021-02-04)
+- 调整为uni_modules目录规范

+ 275 - 0
uni_modules/uni-combox/components/uni-combox/uni-combox.vue

@@ -0,0 +1,275 @@
+<template>
+	<view class="uni-combox" :class="border ? '' : 'uni-combox__no-border'">
+		<view v-if="label" class="uni-combox__label" :style="labelStyle">
+			<text>{{label}}</text>
+		</view>
+		<view class="uni-combox__input-box">
+			<input class="uni-combox__input" type="text" :placeholder="placeholder" 
+			placeholder-class="uni-combox__input-plac" v-model="inputVal" @input="onInput" @focus="onFocus" 
+@blur="onBlur" />
+			<uni-icons :type="showSelector? 'top' : 'bottom'" size="14" color="#999" @click="toggleSelector">
+			</uni-icons>
+		</view>
+		<view class="uni-combox__selector" v-if="showSelector">
+			<view class="uni-popper__arrow"></view>
+			<scroll-view scroll-y="true" class="uni-combox__selector-scroll">
+				<view class="uni-combox__selector-empty" v-if="filterCandidatesLength === 0">
+					<text>{{emptyTips}}</text>
+				</view>
+				<view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index" 
+				@click="onSelectorClick(index)">
+					<text>{{item}}</text>
+				</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script>
+	/**
+	 * Combox 组合输入框
+	 * @description 组合输入框一般用于既可以输入也可以选择的场景
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=1261
+	 * @property {String} label 左侧文字
+	 * @property {String} labelWidth 左侧内容宽度
+	 * @property {String} placeholder 输入框占位符
+	 * @property {Array} candidates 候选项列表
+	 * @property {String} emptyTips 筛选结果为空时显示的文字
+	 * @property {String} value 组合框的值
+	 */
+	export default {
+		name: 'uniCombox',
+		emits: ['input', 'update:modelValue'],
+		props: {
+			border: {
+				type: Boolean,
+				default: true
+			},
+			label: {
+				type: String,
+				default: ''
+			},
+			labelWidth: {
+				type: String,
+				default: 'auto'
+			},
+			placeholder: {
+				type: String,
+				default: ''
+			},
+			candidates: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			emptyTips: {
+				type: String,
+				default: '无匹配项'
+			},
+			// #ifndef VUE3
+			value: {
+				type: [String, Number],
+				default: ''
+			},
+			// #endif
+			// #ifdef VUE3
+			modelValue: {
+				type: [String, Number],
+				default: ''
+			},
+			// #endif
+		},
+		data() {
+			return {
+				showSelector: false,
+				inputVal: ''
+			}
+		},
+		computed: {
+			labelStyle() {
+				if (this.labelWidth === 'auto') {
+					return ""
+				}
+				return `width: ${this.labelWidth}`
+			},
+			filterCandidates() {
+				return this.candidates.filter((item) => {
+					return item.toString().indexOf(this.inputVal) > -1
+				})
+			},
+			filterCandidatesLength() {
+				return this.filterCandidates.length
+			}
+		},
+		watch: {
+			// #ifndef VUE3
+			value: {
+				handler(newVal) {
+					this.inputVal = newVal
+				},
+				immediate: true
+			},
+			// #endif
+			// #ifdef VUE3
+			modelValue: {
+				handler(newVal) {
+					this.inputVal = newVal
+				},
+				immediate: true
+			},
+			// #endif
+		},
+		methods: {
+			toggleSelector() {
+				this.showSelector = !this.showSelector
+			},
+			onFocus() {
+				this.showSelector = true
+			},
+			onBlur() {
+				setTimeout(() => {
+					this.showSelector = false
+				}, 153)
+			},
+			onSelectorClick(index) {
+				this.inputVal = this.filterCandidates[index]
+				this.showSelector = false
+				this.$emit('input', this.inputVal)
+				this.$emit('update:modelValue', this.inputVal)
+			},
+			onInput() {
+				setTimeout(() => {
+					this.$emit('input', this.inputVal)
+					this.$emit('update:modelValue', this.inputVal)
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss" >
+	.uni-combox {
+		font-size: 14px;
+		border: 1px solid #DCDFE6;
+		border-radius: 4px;
+		padding: 6px 10px;
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		// height: 40px;
+		flex-direction: row;
+		align-items: center;
+		// border-bottom: solid 1px #DDDDDD;
+	}
+
+	.uni-combox__label {
+		font-size: 16px;
+		line-height: 22px;
+		padding-right: 10px;
+		color: #999999;
+	}
+
+	.uni-combox__input-box {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		flex-direction: row;
+		align-items: center;
+	}
+
+	.uni-combox__input {
+		flex: 1;
+		font-size: 14px;
+		height: 22px;
+		line-height: 22px;
+	}
+
+	.uni-combox__input-plac {
+		font-size: 14px;
+		color: #999;
+	}
+
+	.uni-combox__selector {
+		/* #ifndef APP-NVUE */
+		box-sizing: border-box;
+		/* #endif */
+		position: absolute;
+		top: calc(100% + 12px);
+		left: 0;
+		width: 100%;
+		background-color: #FFFFFF;
+		border: 1px solid #EBEEF5;
+		border-radius: 6px;
+		box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+		z-index: 2;
+		padding: 4px 0;
+	}
+
+	.uni-combox__selector-scroll {
+		/* #ifndef APP-NVUE */
+		max-height: 200px;
+		box-sizing: border-box;
+		/* #endif */
+	}
+
+	.uni-combox__selector-empty,
+	.uni-combox__selector-item {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		cursor: pointer;
+		/* #endif */
+		line-height: 36px;
+		font-size: 14px;
+		text-align: center;
+		// border-bottom: solid 1px #DDDDDD;
+		padding: 0px 10px;
+	}
+
+	.uni-combox__selector-item:hover {
+		background-color: #f9f9f9;
+	}
+
+	.uni-combox__selector-empty:last-child,
+	.uni-combox__selector-item:last-child {
+		/* #ifndef APP-NVUE */
+		border-bottom: none;
+		/* #endif */
+	}
+
+	// picker 弹出层通用的指示小三角
+	.uni-popper__arrow,
+	.uni-popper__arrow::after {
+		position: absolute;
+		display: block;
+		width: 0;
+		height: 0;
+		border-color: transparent;
+		border-style: solid;
+		border-width: 6px;
+	}
+
+	.uni-popper__arrow {
+		filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
+		top: -6px;
+		left: 10%;
+		margin-right: 3px;
+		border-top-width: 0;
+		border-bottom-color: #EBEEF5;
+	}
+
+	.uni-popper__arrow::after {
+		content: " ";
+		top: 1px;
+		margin-left: -6px;
+		border-top-width: 0;
+		border-bottom-color: #fff;
+	}
+
+	.uni-combox__no-border {
+		border: none;
+	}
+</style>

+ 90 - 0
uni_modules/uni-combox/package.json

@@ -0,0 +1,90 @@
+{
+  "id": "uni-combox",
+  "displayName": "uni-combox 组合框",
+  "version": "1.0.1",
+  "description": "可以选择也可以输入的表单项 ",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "combox",
+    "组合框",
+    "select"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+  "dcloudext": {
+    "category": [
+      "前端组件",
+      "通用组件"
+    ],
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uni-scss",
+			"uni-icons"
+		],
+    "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"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 11 - 0
uni_modules/uni-combox/readme.md

@@ -0,0 +1,11 @@
+
+
+## Combox 组合框
+> **组件名:uni-combox**
+> 代码块: `uCombox`
+
+
+组合框组件。
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-combox)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 24 - 0
uni_modules/uni-countdown/changelog.md

@@ -0,0 +1,24 @@
+## 1.2.2(2022-01-19)
+- 修复 在微信小程序中样式不生效的bug
+## 1.2.1(2022-01-18)
+- 新增 update 方法 ,在动态更新时间后,刷新组件
+## 1.2.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-countdown](https://uniapp.dcloud.io/component/uniui/uni-countdown)
+## 1.1.3(2021-10-18)
+- 重构
+- 新增 font-size 支持自定义字体大小
+## 1.1.2(2021-08-24)
+- 新增 支持国际化
+## 1.1.1(2021-07-30)
+- 优化 vue3下小程序事件警告的问题
+## 1.1.0(2021-07-30)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.0.5(2021-06-18)
+- 修复 uni-countdown 重复赋值跳两秒的 bug
+## 1.0.4(2021-05-12)
+- 新增 组件示例地址
+## 1.0.3(2021-05-08)
+- 修复 uni-countdown 不能控制倒计时的 bug
+## 1.0.2(2021-02-04)
+- 调整为uni_modules目录规范

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff